@@ -13,12 +13,16 @@ import {
13
13
doesSceneHaveExtrudedSketch ,
14
14
doesSceneHaveSweepableSketch ,
15
15
findAllPreviousVariables ,
16
+ findOperationPlaneArtifact ,
16
17
findUsesOfTagInPipe ,
17
18
getNodeFromPath ,
19
+ getSelectedPlaneId ,
20
+ getSelectedPlaneAsNode ,
18
21
getVariableExprsFromSelection ,
19
22
hasSketchPipeBeenExtruded ,
20
23
isCursorInFunctionDefinition ,
21
24
isNodeSafeToReplace ,
25
+ isOffsetPlane ,
22
26
retrieveSelectionsFromOpArg ,
23
27
traverse ,
24
28
} from '@src/lang/queryAst'
@@ -32,6 +36,7 @@ import { initPromise } from '@src/lang/wasmUtils'
32
36
import type { Selections , Selection } from '@src/lib/selections'
33
37
import { enginelessExecutor } from '@src/lib/testHelpers'
34
38
import { err } from '@src/lib/trap'
39
+ import type { Plane } from '@rust/kcl-lib/bindings/Artifact'
35
40
36
41
beforeAll ( async ( ) => {
37
42
await initPromise
@@ -781,6 +786,223 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
781
786
} )
782
787
} )
783
788
789
+ describe ( 'Testing findOperationArtifact' , ( ) => {
790
+ it ( 'should find the correct artifact for a given operation' , async ( ) => {
791
+ const code = `sketch001 = startSketchOn(XY)
792
+ |> startProfile(at = [0, 0])
793
+ |> line(end = [10, 0])
794
+ |> line(end = [10, 10])
795
+ |> line(end = [0, 10])
796
+ |> close()
797
+ plane001 = offsetPlane(YZ, offset = 10)
798
+ part001 = startSketchOn(plane001)
799
+ |> startProfile(at = [0, 0])
800
+ |> line(end = [5, 0])
801
+ |> line(end = [5, 5])
802
+ |> line(end = [0, 5])
803
+ |> close()
804
+ `
805
+
806
+ const ast = assertParse ( code )
807
+ const execState = await enginelessExecutor ( ast , false )
808
+ const { operations, artifactGraph } = execState
809
+
810
+ expect ( operations ) . toBeTruthy ( )
811
+ expect ( operations . length ) . toBeGreaterThan ( 0 )
812
+
813
+ // Find an offsetPlane operation
814
+ const offsetPlaneOp = operations . find (
815
+ ( op ) => op . type === 'StdLibCall' && op . name === 'offsetPlane'
816
+ )
817
+ expect ( offsetPlaneOp ) . toBeTruthy ( )
818
+
819
+ if ( offsetPlaneOp && isOffsetPlane ( offsetPlaneOp ) ) {
820
+ const artifact = findOperationPlaneArtifact ( offsetPlaneOp , artifactGraph )
821
+
822
+ expect ( artifact ) . toBeTruthy ( )
823
+ expect ( artifact ?. type ) . toBe ( 'plane' )
824
+
825
+ const artifactNodePath = JSON . stringify (
826
+ ( artifact as Plane ) ?. codeRef ?. nodePath
827
+ )
828
+ const operationNodePath = JSON . stringify ( offsetPlaneOp . nodePath )
829
+ expect ( artifactNodePath ) . toBe ( operationNodePath )
830
+ }
831
+ } )
832
+ } )
833
+
834
+ describe ( 'Testing getSelectedPlaneId' , ( ) => {
835
+ it ( 'should return the id of the selected default plane' , ( ) => {
836
+ const selections : Selections = {
837
+ otherSelections : [
838
+ {
839
+ name : 'XY' , // actually, this is lowercase during runtime ("xy")!
840
+ id : 'default-plane-xy-id' ,
841
+ } ,
842
+ ] ,
843
+ graphSelections : [ ] ,
844
+ }
845
+
846
+ const result = getSelectedPlaneId ( selections )
847
+ expect ( result ) . toBe ( 'default-plane-xy-id' )
848
+ } )
849
+
850
+ it ( 'should return the id of the selected offset plane' , ( ) => {
851
+ const selections : Selections = {
852
+ otherSelections : [ ] ,
853
+ graphSelections : [
854
+ {
855
+ artifact : {
856
+ type : 'plane' as const ,
857
+ id : 'offset-plane-id' ,
858
+ pathIds : [ ] ,
859
+ codeRef : {
860
+ nodePath : {
861
+ steps : [ ] ,
862
+ } ,
863
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
864
+ pathToNode : [
865
+ [ 'body' , '' ] ,
866
+ [ 0 , 'index' ] ,
867
+ ] as PathToNode ,
868
+ } ,
869
+ } ,
870
+ codeRef : {
871
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
872
+ pathToNode : [
873
+ [ 'body' , '' ] ,
874
+ [ 0 , 'index' ] ,
875
+ ] as PathToNode ,
876
+ } ,
877
+ } ,
878
+ ] ,
879
+ }
880
+
881
+ const result = getSelectedPlaneId ( selections )
882
+ expect ( result ) . toBe ( 'offset-plane-id' )
883
+ } )
884
+
885
+ it ( 'should prioritize default plane over offset plane when both are selected' , ( ) => {
886
+ const mockPlaneArtifact = {
887
+ type : 'plane' as const ,
888
+ id : 'offset-plane-id' ,
889
+ pathIds : [ ] ,
890
+ codeRef : {
891
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
892
+ nodePath : { steps : [ ] } ,
893
+ pathToNode : [
894
+ [ 'body' , '' ] ,
895
+ [ 0 , 'index' ] ,
896
+ ] as PathToNode ,
897
+ } ,
898
+ }
899
+
900
+ const selections : Selections = {
901
+ otherSelections : [
902
+ {
903
+ name : 'XY' ,
904
+ id : 'default-plane-xy-id' ,
905
+ } ,
906
+ ] ,
907
+ graphSelections : [
908
+ {
909
+ artifact : mockPlaneArtifact ,
910
+ codeRef : {
911
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
912
+ pathToNode : [
913
+ [ 'body' , '' ] ,
914
+ [ 0 , 'index' ] ,
915
+ ] as PathToNode ,
916
+ } ,
917
+ } ,
918
+ ] ,
919
+ }
920
+
921
+ const result = getSelectedPlaneId ( selections )
922
+ expect ( result ) . toBe ( 'default-plane-xy-id' )
923
+ } )
924
+
925
+ it ( 'should return null when no plane is selected' , ( ) => {
926
+ const selections : Selections = {
927
+ otherSelections : [ 'x-axis' ] ,
928
+ graphSelections : [
929
+ {
930
+ artifact : {
931
+ type : 'startSketchOnFace' as const ,
932
+ id : 'segment-id' ,
933
+ faceId : 'face-id' ,
934
+ codeRef : {
935
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
936
+ nodePath : { steps : [ ] } ,
937
+ pathToNode : [
938
+ [ 'body' , '' ] ,
939
+ [ 0 , 'index' ] ,
940
+ ] as PathToNode ,
941
+ } ,
942
+ } ,
943
+ codeRef : {
944
+ range : [ 0 , 10 , 0 ] as [ number , number , number ] ,
945
+ pathToNode : [
946
+ [ 'body' , '' ] ,
947
+ [ 0 , 'index' ] ,
948
+ ] as PathToNode ,
949
+ } ,
950
+ } ,
951
+ ] ,
952
+ }
953
+
954
+ const result = getSelectedPlaneId ( selections )
955
+ expect ( result ) . toBeNull ( )
956
+ } )
957
+
958
+ it ( 'should return null when selection is empty' , ( ) => {
959
+ const selections : Selections = {
960
+ otherSelections : [ ] ,
961
+ graphSelections : [ ] ,
962
+ }
963
+
964
+ const result = getSelectedPlaneId ( selections )
965
+ expect ( result ) . toBeNull ( )
966
+ } )
967
+ } )
968
+
969
+ describe ( 'Testing getSelectedPlaneAsNode' , ( ) => {
970
+ it ( 'should return a Node<Literal> for default plane selection' , async ( ) => {
971
+ const code = `sketch001 = startSketchOn(XY)
972
+ |> startProfile(at = [0, 0])
973
+ |> line(end = [10, 0])
974
+ |> line(end = [10, 10])
975
+ |> line(end = [0, 10])
976
+ |> close()
977
+ plane001 = offsetPlane(YZ, offset = 10)
978
+ `
979
+
980
+ const ast = assertParse ( code )
981
+ const execState = await enginelessExecutor ( ast , false )
982
+ const { variables } = execState
983
+
984
+ const selections : Selections = {
985
+ otherSelections : [
986
+ {
987
+ name : 'XY' ,
988
+ id : 'default-plane-xy-id' ,
989
+ } ,
990
+ ] ,
991
+ graphSelections : [ ] ,
992
+ }
993
+
994
+ const result = getSelectedPlaneAsNode ( selections , variables )
995
+ expect ( result ) . toBeTruthy ( )
996
+ expect ( result ?. type ) . toBe ( 'Literal' )
997
+ if ( result ?. type !== 'Literal' ) {
998
+ // To make TypeScript happy
999
+ throw new Error ( 'Expected result to be a Literal node' )
1000
+ }
1001
+
1002
+ expect ( result ?. value ) . toBe ( 'XY' )
1003
+ } )
1004
+ } )
1005
+
784
1006
describe ( 'Testing getVariableExprsFromSelection' , ( ) => {
785
1007
it ( 'should find the variable expr in a simple profile selection' , async ( ) => {
786
1008
const circleProfileInVar = `sketch001 = startSketchOn(XY)
0 commit comments