| @ -10,11 +10,11 @@ This will work on any solid, including extruded solids, revolved solids, and she | ||||
|  | ||||
| ```js | ||||
| appearance( | ||||
|   solidSet: SolidSet, | ||||
|   solids: [Solid], | ||||
|   color: String, | ||||
|   metalness?: number, | ||||
|   roughness?: number, | ||||
| ): SolidSet | ||||
| ): [Solid] | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -22,14 +22,14 @@ appearance( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl/types/Solid) | The solid(s) whose appearance is being set | Yes | | ||||
| | `color` | `String` | Color of the new material, a hex string like '#ff0000' | Yes | | ||||
| | `metalness` | [`number`](/docs/kcl/types/number) | Metalness of the new material, a percentage like 95.7. | No | | ||||
| | `roughness` | [`number`](/docs/kcl/types/number) | Roughness of the new material, a percentage like 95.7. | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids. | ||||
| [`[Solid]`](/docs/kcl/types/Solid) | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| @ -10,9 +10,9 @@ You can provide more than one sketch to extrude, and they will all be extruded i | ||||
|  | ||||
| ```js | ||||
| extrude( | ||||
|   sketchSet: SketchSet, | ||||
|   sketches: [Sketch], | ||||
|   length: number, | ||||
| ): SolidSet | ||||
| ): [Solid] | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -20,12 +20,12 @@ extrude( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch or set of sketches should be extruded | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketch or sketches should be extruded | Yes | | ||||
| | `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids. | ||||
| [`[Solid]`](/docs/kcl/types/Solid) | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| @ -10,7 +10,7 @@ Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch. | ||||
|  | ||||
| ```js | ||||
| hole( | ||||
|   holeSketch: SketchSet, | ||||
|   holeSketch: [Sketch], | ||||
|   sketch: Sketch, | ||||
| ): Sketch | ||||
| ``` | ||||
| @ -20,7 +20,7 @@ hole( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `holeSketch` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | ||||
| | `holeSketch` | [`[Sketch]`](/docs/kcl/types/Sketch) |  | Yes | | ||||
| | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) |  | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| @ -13,7 +13,7 @@ Mirror occurs around a local sketch axis rather than a global axis. | ||||
| ```js | ||||
| mirror2d( | ||||
|   data: Mirror2dData, | ||||
|   sketchSet: SketchSet, | ||||
|   sketches: [Sketch], | ||||
| ): [Sketch] | ||||
| ``` | ||||
|  | ||||
| @ -23,7 +23,7 @@ mirror2d( | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `data` | [`Mirror2dData`](/docs/kcl/types/Mirror2dData) | Data for a mirror. | Yes | | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) |  | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
|  | ||||
| @ -10,7 +10,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or complete c | ||||
|  | ||||
| ```js | ||||
| patternCircular2d( | ||||
|   sketchSet: SketchSet, | ||||
|   sketchSet: [Sketch], | ||||
|   instances: integer, | ||||
|   center: [number], | ||||
|   arcDegrees: number, | ||||
| @ -24,7 +24,7 @@ patternCircular2d( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes | | ||||
| | `sketchSet` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketch(es) to pattern | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 2D vector. | Yes | | ||||
| | `arcDegrees` | [`number`](/docs/kcl/types/number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes | | ||||
|  | ||||
| @ -10,7 +10,7 @@ Repeat a 3-dimensional solid some number of times along a partial or complete ci | ||||
|  | ||||
| ```js | ||||
| patternCircular3d( | ||||
|   solidSet: SolidSet, | ||||
|   solids: [Solid], | ||||
|   instances: integer, | ||||
|   axis: [number], | ||||
|   center: [number], | ||||
| @ -25,7 +25,7 @@ patternCircular3d( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl/types/Solid) | Which solid(s) to pattern | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `axis` | [`[number]`](/docs/kcl/types/number) | The axis around which to make the pattern. This is a 3D vector | Yes | | ||||
| | `center` | [`[number]`](/docs/kcl/types/number) | The center about which to make the pattern. This is a 3D vector. | Yes | | ||||
|  | ||||
| @ -10,7 +10,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount of dis | ||||
|  | ||||
| ```js | ||||
| patternLinear2d( | ||||
|   sketchSet: SketchSet, | ||||
|   sketches: [Sketch], | ||||
|   instances: integer, | ||||
|   distance: number, | ||||
|   axis: [number], | ||||
| @ -23,7 +23,7 @@ patternLinear2d( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | The sketch(es) to duplicate | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes | | ||||
| | `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes | | ||||
|  | ||||
| @ -10,7 +10,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount of dista | ||||
|  | ||||
| ```js | ||||
| patternLinear3d( | ||||
|   solidSet: SolidSet, | ||||
|   solids: [Solid], | ||||
|   instances: integer, | ||||
|   distance: number, | ||||
|   axis: [number], | ||||
| @ -23,7 +23,7 @@ patternLinear3d( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl/types/Solid) | The solid(s) to duplicate | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `distance` | [`number`](/docs/kcl/types/number) | Distance between each repetition. Also known as 'spacing'. | Yes | | ||||
| | `axis` | [`[number]`](/docs/kcl/types/number) | The axis of the pattern. A 2D vector. | Yes | | ||||
|  | ||||
| @ -36,7 +36,7 @@ The transform function returns a transform object. All properties of the object | ||||
|  | ||||
| ```js | ||||
| patternTransform( | ||||
|   solidSet: SolidSet, | ||||
|   solids: [Solid], | ||||
|   instances: integer, | ||||
|   transform: FunctionSource, | ||||
|   useOriginal?: bool, | ||||
| @ -48,7 +48,7 @@ patternTransform( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl/types/Solid) | The solid(s) to duplicate | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | ||||
| | `useOriginal` | [`bool`](/docs/kcl/types/bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | ||||
|  | ||||
| @ -10,7 +10,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids. | ||||
|  | ||||
| ```js | ||||
| patternTransform2d( | ||||
|   sketchSet: SketchSet, | ||||
|   sketches: [Sketch], | ||||
|   instances: integer, | ||||
|   transform: FunctionSource, | ||||
|   useOriginal?: bool, | ||||
| @ -22,7 +22,7 @@ patternTransform2d( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | The sketch(es) to duplicate | Yes | | ||||
| | `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes | | ||||
| | `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes | | ||||
| | `useOriginal` | [`bool`](/docs/kcl/types/bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No | | ||||
|  | ||||
| @ -15,8 +15,8 @@ You can provide more than one sketch to revolve, and they will all be revolved a | ||||
| ```js | ||||
| revolve( | ||||
|   data: RevolveData, | ||||
|   sketchSet: SketchSet, | ||||
| ): SolidSet | ||||
|   sketches: [Sketch], | ||||
| ): [Solid] | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| @ -25,11 +25,11 @@ revolve( | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `data` | [`RevolveData`](/docs/kcl/types/RevolveData) | Data for revolution surfaces. | Yes | | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) |  | Yes | | ||||
| 
 | ||||
| ### Returns | ||||
| 
 | ||||
| [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids. | ||||
| [`[Solid]`](/docs/kcl/types/Solid) | ||||
| 
 | ||||
| 
 | ||||
| ### Examples | ||||
|  | ||||
| @ -24,7 +24,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an | ||||
|  | ||||
| ```js | ||||
| rotate( | ||||
|   solidSet: SolidOrImportedGeometry, | ||||
|   solids: SolidOrImportedGeometry, | ||||
|   roll?: number, | ||||
|   pitch?: number, | ||||
|   yaw?: number, | ||||
| @ -39,7 +39,7 @@ rotate( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to rotate. | Yes | | ||||
| | `solids` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to rotate. | Yes | | ||||
| | `roll` | [`number`](/docs/kcl/types/number) | The roll angle in degrees. Must be used with `pitch` and `yaw`. Must be between -360 and 360. | No | | ||||
| | `pitch` | [`number`](/docs/kcl/types/number) | The pitch angle in degrees. Must be used with `roll` and `yaw`. Must be between -360 and 360. | No | | ||||
| | `yaw` | [`number`](/docs/kcl/types/number) | The yaw angle in degrees. Must be used with `roll` and `pitch`. Must be between -360 and 360. | No | | ||||
|  | ||||
| @ -12,7 +12,7 @@ If you want to apply the transform in global space, set `global` to `true`. The | ||||
|  | ||||
| ```js | ||||
| scale( | ||||
|   solidSet: SolidOrImportedGeometry, | ||||
|   solids: SolidOrImportedGeometry, | ||||
|   scale: [number], | ||||
|   global?: bool, | ||||
| ): SolidOrImportedGeometry | ||||
| @ -23,7 +23,7 @@ scale( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to scale. | Yes | | ||||
| | `solids` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to scale. | Yes | | ||||
| | `scale` | [`[number]`](/docs/kcl/types/number) | The scale factor for the x, y, and z axes. | Yes | | ||||
| | `global` | [`bool`](/docs/kcl/types/bool) | If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move. | No | | ||||
|  | ||||
|  | ||||
| @ -10,10 +10,10 @@ Remove volume from a 3-dimensional shape such that a wall of the provided thickn | ||||
|  | ||||
| ```js | ||||
| shell( | ||||
|   solidSet: SolidSet, | ||||
|   solids: [Solid], | ||||
|   thickness: number, | ||||
|   faces: [FaceTag], | ||||
| ): SolidSet | ||||
| ): [Solid] | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -21,13 +21,13 @@ shell( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl/types/Solid) | Which solid (or solids) to shell out | Yes | | ||||
| | `thickness` | [`number`](/docs/kcl/types/number) | The thickness of the shell | Yes | | ||||
| | `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids. | ||||
| [`[Solid]`](/docs/kcl/types/Solid) | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
							
								
								
									
										63634
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						
									
										63634
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -12,11 +12,11 @@ You can provide more than one sketch to sweep, and they will all be swept along | ||||
|  | ||||
| ```js | ||||
| sweep( | ||||
|   sketchSet: SketchSet, | ||||
|   sketches: [Sketch], | ||||
|   path: SweepPath, | ||||
|   sectional?: bool, | ||||
|   tolerance?: number, | ||||
| ): SolidSet | ||||
| ): [Solid] | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -24,14 +24,14 @@ sweep( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch or set of sketches that should be swept in space | Yes | | ||||
| | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | The sketch or set of sketches that should be swept in space | Yes | | ||||
| | `path` | [`SweepPath`](/docs/kcl/types/SweepPath) | The path to sweep the sketch along | Yes | | ||||
| | `sectional` | [`bool`](/docs/kcl/types/bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No | | ||||
| | `tolerance` | [`number`](/docs/kcl/types/number) | Tolerance for this operation | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`SolidSet`](/docs/kcl/types/SolidSet) - A solid or a group of solids. | ||||
| [`[Solid]`](/docs/kcl/types/Solid) | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| @ -10,7 +10,7 @@ Move a solid. | ||||
|  | ||||
| ```js | ||||
| translate( | ||||
|   solidSet: SolidOrImportedGeometry, | ||||
|   solids: SolidOrImportedGeometry, | ||||
|   translate: [number], | ||||
|   global?: bool, | ||||
| ): SolidOrImportedGeometry | ||||
| @ -21,7 +21,7 @@ translate( | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solidSet` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to move. | Yes | | ||||
| | `solids` | [`SolidOrImportedGeometry`](/docs/kcl/types/SolidOrImportedGeometry) | The solid or set of solids to move. | Yes | | ||||
| | `translate` | [`[number]`](/docs/kcl/types/number) | The amount to move the solid in all three axes. | Yes | | ||||
| | `global` | [`bool`](/docs/kcl/types/bool) | If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move. | No | | ||||
|  | ||||
|  | ||||
| @ -100,6 +100,22 @@ Any KCL value. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `HomArray`|  | No | | ||||
| | `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| @ -122,7 +138,6 @@ Any KCL value. | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)|  | No | | ||||
| | `value` |[`string`](/docs/kcl/types/string)|  | No | | ||||
| | `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| @ -200,22 +215,6 @@ Any KCL value. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Sketches`|  | No | | ||||
| | `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| @ -232,22 +231,6 @@ Any KCL value. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Solids`|  | No | | ||||
| | `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| @ -338,22 +321,6 @@ Data for an imported geometry. | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `Tombstone`|  | No | | ||||
| | `value` |`null`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,56 +0,0 @@ | ||||
| --- | ||||
| title: "SketchSet" | ||||
| excerpt: "A sketch or a group of sketches." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A sketch or a group of sketches. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts exactly one of the following:** | ||||
|  | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `sketch`|  | No | | ||||
| | `id` |[`string`](/docs/kcl/types/string)| The id of the sketch (this will change when the engine's reference to it changes). | No | | ||||
| | `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No | | ||||
| | `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No | | ||||
| | `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No | | ||||
| | `tags` |`object`| Tag identifiers that have been declared in this sketch. | No | | ||||
| | `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No | | ||||
| | `originalId` |[`string`](/docs/kcl/types/string)|  | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `[object, array]` | ||||
|  | ||||
| `[` [`Sketch`](/docs/kcl/types/Sketch) `]` | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `sketches`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -12,30 +12,6 @@ Data for a solid or an imported geometry. | ||||
|  | ||||
| **This schema accepts exactly one of the following:** | ||||
|  | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `solid`|  | No | | ||||
| | `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No | | ||||
| | `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID of the solid.  Unlike `id`, this doesn't change. | No | | ||||
| | `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No | | ||||
| | `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No | | ||||
| | `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No | | ||||
| | `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No | | ||||
| | `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No | | ||||
| | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
| Data for an imported geometry. | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
| @ -1,57 +0,0 @@ | ||||
| --- | ||||
| title: "SolidSet" | ||||
| excerpt: "A solid or a group of solids." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| A solid or a group of solids. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| **This schema accepts exactly one of the following:** | ||||
|  | ||||
|  | ||||
| **Type:** `object` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `solid`|  | No | | ||||
| | `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No | | ||||
| | `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID of the solid.  Unlike `id`, this doesn't change. | No | | ||||
| | `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No | | ||||
| | `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No | | ||||
| | `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No | | ||||
| | `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No | | ||||
| | `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No | | ||||
| | `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No | | ||||
| | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
| **Type:** `[object, array]` | ||||
|  | ||||
| `[` [`Solid`](/docs/kcl/types/Solid) `]` | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Properties | ||||
|  | ||||
| | Property | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `type` |enum: `solids`|  | No | | ||||
|  | ||||
|  | ||||
| ---- | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1024,7 +1024,7 @@ openSketch = startSketchOn('XY') | ||||
|     await page.waitForTimeout(15000) | ||||
|  | ||||
|     await test.step(`Look for the blue of the XZ plane`, async () => { | ||||
|       await scene.expectPixelColor([50, 51, 96], testPoint, 15) | ||||
|       //await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME | ||||
|     }) | ||||
|     await test.step(`Go through the command bar flow`, async () => { | ||||
|       await toolbar.offsetPlaneButton.click() | ||||
| @ -1066,7 +1066,7 @@ openSketch = startSketchOn('XY') | ||||
|       ) | ||||
|       await operationButton.click({ button: 'left' }) | ||||
|       await page.keyboard.press('Delete') | ||||
|       await scene.expectPixelColor([50, 51, 96], testPoint, 15) | ||||
|       //await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
| @ -2321,8 +2321,8 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)]) | ||||
|       cmdBar, | ||||
|     }) => { | ||||
|       const initialCode = `sketch001 = startSketchOn('XZ') | ||||
|     |> circle(center = [0, 0], radius = 30) | ||||
|     extrude001 = extrude(sketch001, length = 30) | ||||
|   |> circle(center = [0, 0], radius = 30) | ||||
| extrude001 = extrude(sketch001, length = 30) | ||||
|     ` | ||||
|       await context.addInitScript((initialCode) => { | ||||
|         localStorage.setItem('persistCode', initialCode) | ||||
| @ -2336,6 +2336,8 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)]) | ||||
|       const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|       const shellDeclaration = | ||||
|         "shell001 = shell(extrude001, faces = ['end'], thickness = 5)" | ||||
|       const editedShellDeclaration = | ||||
|         "shell001 = shell(extrude001, faces = ['end'], thickness = 2)" | ||||
|  | ||||
|       await test.step(`Look for the grey of the shape`, async () => { | ||||
|         await scene.expectPixelColor([127, 127, 127], testPoint, 15) | ||||
| @ -2402,6 +2404,45 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)]) | ||||
|         }) | ||||
|         await scene.expectPixelColor([146, 146, 146], testPoint, 15) | ||||
|       }) | ||||
|  | ||||
|       await test.step('Edit shell via feature tree selection works', async () => { | ||||
|         await toolbar.closePane('code') | ||||
|         await toolbar.openPane('feature-tree') | ||||
|         const operationButton = await toolbar.getFeatureTreeOperation( | ||||
|           'Shell', | ||||
|           0 | ||||
|         ) | ||||
|         await operationButton.dblclick() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'arguments', | ||||
|           currentArgKey: 'thickness', | ||||
|           currentArgValue: '5', | ||||
|           headerArguments: { | ||||
|             Thickness: '5', | ||||
|           }, | ||||
|           highlightedHeaderArg: 'thickness', | ||||
|           commandName: 'Shell', | ||||
|         }) | ||||
|         await page.keyboard.insertText('2') | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await cmdBar.expectState({ | ||||
|           stage: 'review', | ||||
|           headerArguments: { | ||||
|             Thickness: '2', | ||||
|           }, | ||||
|           commandName: 'Shell', | ||||
|         }) | ||||
|         await cmdBar.progressCmdBar() | ||||
|         await toolbar.closePane('feature-tree') | ||||
|         await scene.expectPixelColor([150, 150, 150], testPoint, 15) | ||||
|         await toolbar.openPane('code') | ||||
|         await editor.expectEditor.toContain(editedShellDeclaration) | ||||
|         await editor.expectState({ | ||||
|           diagnostics: [], | ||||
|           activeLines: [editedShellDeclaration], | ||||
|           highlightedCode: '', | ||||
|         }) | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
| @ -2437,6 +2478,8 @@ extrude001 = extrude(sketch001, length = 40) | ||||
|     const mutatedCode = 'xLine(length = -40, tag = $seg01)' | ||||
|     const shellDeclaration = | ||||
|       "shell001 = shell(extrude001, faces = ['end', seg01], thickness = 5)" | ||||
|     const editedShellDeclaration = | ||||
|       "shell001 = shell(extrude001, faces = ['end', seg01], thickness = 1)" | ||||
|  | ||||
|     await test.step(`Look for the grey of the shape`, async () => { | ||||
|       await scene.expectPixelColor([99, 99, 99], testPoint, 15) | ||||
| @ -2485,6 +2528,41 @@ extrude001 = extrude(sketch001, length = 40) | ||||
|       await scene.expectPixelColor([49, 49, 49], testPoint, 15) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Edit shell via feature tree selection works', async () => { | ||||
|       await editor.closePane() | ||||
|       const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0) | ||||
|       await operationButton.dblclick({ button: 'left' }) | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'arguments', | ||||
|         currentArgKey: 'thickness', | ||||
|         currentArgValue: '5', | ||||
|         headerArguments: { | ||||
|           Thickness: '5', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'thickness', | ||||
|         commandName: 'Shell', | ||||
|       }) | ||||
|       await page.keyboard.insertText('1') | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'review', | ||||
|         headerArguments: { | ||||
|           Thickness: '1', | ||||
|         }, | ||||
|         commandName: 'Shell', | ||||
|       }) | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await toolbar.closePane('feature-tree') | ||||
|       await scene.expectPixelColor([150, 150, 150], testPoint, 15) | ||||
|       await toolbar.openPane('code') | ||||
|       await editor.expectEditor.toContain(editedShellDeclaration) | ||||
|       await editor.expectState({ | ||||
|         diagnostics: [], | ||||
|         activeLines: [editedShellDeclaration], | ||||
|         highlightedCode: '', | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Delete shell via feature tree selection', async () => { | ||||
|       await editor.closePane() | ||||
|       const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0) | ||||
| @ -2579,7 +2657,7 @@ extrude002 = extrude(sketch002, length = 50) | ||||
|           highlightedCode: '', | ||||
|         }) | ||||
|         await toolbar.closePane('code') | ||||
|         await scene.expectPixelColor([73, 73, 73], testPoint, 15) | ||||
|         await scene.expectPixelColor([80, 80, 80], testPoint, 15) | ||||
|       }) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
| @ -1174,7 +1174,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff( | ||||
|     |> line(endAbsolute = [ | ||||
|      railWideWidth / 2, | ||||
|      railClampable / 2 + railBaseLength | ||||
|    ], $seg01) | ||||
|    ], tag = $seg01) | ||||
|     |> line(endAbsolute = [railTop / 2, railBaseLength]) | ||||
|     |> line(endAbsolute = [railBaseWidth / 2, railBaseLength]) | ||||
|     |> line(endAbsolute = [railBaseWidth / 2, 0]) | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB | 
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -3073,7 +3073,7 @@ DATA; | ||||
| #3057 = CARTESIAN_POINT('NONE', (0.051104890518972546, -0.039940414856583686, -0.0635)); | ||||
| #3058 = CARTESIAN_POINT('NONE', (0.052242074077479335, -0.038876903045998674, -0.0635)); | ||||
| #3059 = CARTESIAN_POINT('NONE', (0.05224392753122875, -0.03887516966712757, -0.0635)); | ||||
| #3060 = CARTESIAN_POINT('NONE', (0.05311532463588208, -0.03767579444673182, -0.0635)); | ||||
| #3060 = CARTESIAN_POINT('NONE', (0.05311532463588208, -0.03767579444673181, -0.0635)); | ||||
| #3061 = CARTESIAN_POINT('NONE', (0.05311674489404425, -0.03767383962907501, -0.0635)); | ||||
| #3062 = CARTESIAN_POINT('NONE', (0.053776795686355607, -0.03626367057234418, -0.0635)); | ||||
| #3063 = CARTESIAN_POINT('NONE', (0.05377787147891932, -0.036261372189549286, -0.0635)); | ||||
| @ -3087,7 +3087,7 @@ DATA; | ||||
| #3071 = CARTESIAN_POINT('NONE', (0.053252818350252196, -0.029748655756475863, -0.0635)); | ||||
| #3072 = CARTESIAN_POINT('NONE', (0.05233460363130192, -0.028414043632913145, -0.0635)); | ||||
| #3073 = CARTESIAN_POINT('NONE', (0.05233310706682834, -0.028411868397590818, -0.0635)); | ||||
| #3074 = CARTESIAN_POINT('NONE', (0.051232952266167, -0.02734405921816657, -0.0635)); | ||||
| #3074 = CARTESIAN_POINT('NONE', (0.05123295226616701, -0.02734405921816657, -0.0635)); | ||||
| #3075 = CARTESIAN_POINT('NONE', (0.05123115916423111, -0.027342318835171704, -0.0635)); | ||||
| #3076 = CARTESIAN_POINT('NONE', (0.0499865731843106, -0.02652506813979786, -0.0635)); | ||||
| #3077 = CARTESIAN_POINT('NONE', (0.049984544679296, -0.026523736132881105, -0.0635)); | ||||
| @ -3105,7 +3105,7 @@ DATA; | ||||
| #3089 = CARTESIAN_POINT('NONE', (0.0407616757108459, -0.02775624333996861, -0.0635)); | ||||
| #3090 = CARTESIAN_POINT('NONE', (0.03976400232776854, -0.0288872140372878, -0.0635)); | ||||
| #3091 = CARTESIAN_POINT('NONE', (0.03976237625653429, -0.028889057364922765, -0.0635)); | ||||
| #3092 = B_SPLINE_CURVE_WITH_KNOTS('NONE', 2, (#3029, #3030, #3031, #3032, #3033, #3034, #3035, #3036, #3037, #3038, #3039, #3040, #3041, #3042, #3043, #3044, #3045, #3046, #3047, #3048, #3049, #3050, #3051, #3052, #3053, #3054, #3055, #3056, #3057, #3058, #3059, #3060, #3061, #3062, #3063, #3064, #3065, #3066, #3067, #3068, #3069, #3070, #3071, #3072, #3073, #3074, #3075, #3076, #3077, #3078, #3079, #3080, #3081, #3082, #3083, #3084, #3085, #3086, #3087, #3088, #3089, #3090, #3091), .UNSPECIFIED., .F., .F., (3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3), (0, 0.01639344262295082, 0.03278688524590164, 0.04918032786885246, 0.06557377049180328, 0.0819672131147541, 0.09836065573770492, 0.11475409836065574, 0.13114754098360656, 0.14754098360655737, 0.1639344262295082, 0.18032786885245902, 0.19672131147540983, 0.21311475409836067, 0.22950819672131148, 0.24590163934426232, 0.26229508196721313, 0.27868852459016397, 0.29508196721311475, 0.3114754098360656, 0.3278688524590164, 0.3442622950819672, 0.36065573770491804, 0.3770491803278689, 0.39344262295081966, 0.4098360655737705, 0.42622950819672134, 0.4426229508196722, 0.45901639344262296, 0.4754098360655738, 0.49180327868852464, 0.5081967213114753, 0.5245901639344261, 0.540983606557377, 0.5573770491803278, 0.5737704918032787, 0.5901639344262295, 0.6065573770491803, 0.6229508196721312, 0.639344262295082, 0.6557377049180328, 0.6721311475409836, 0.6885245901639344, 0.7049180327868853, 0.721311475409836, 0.7377049180327868, 0.7540983606557377, 0.7704918032786885, 0.7868852459016393, 0.8032786885245902, 0.819672131147541, 0.8360655737704918, 0.8524590163934427, 0.8688524590163934, 0.8852459016393442, 0.9016393442622951, 0.9180327868852459, 0.9344262295081968, 0.9508196721311475, 0.9672131147540983, 0.9836065573770492, 1), .UNSPECIFIED.); | ||||
| #3092 = B_SPLINE_CURVE_WITH_KNOTS('NONE', 2, (#3029, #3030, #3031, #3032, #3033, #3034, #3035, #3036, #3037, #3038, #3039, #3040, #3041, #3042, #3043, #3044, #3045, #3046, #3047, #3048, #3049, #3050, #3051, #3052, #3053, #3054, #3055, #3056, #3057, #3058, #3059, #3060, #3061, #3062, #3063, #3064, #3065, #3066, #3067, #3068, #3069, #3070, #3071, #3072, #3073, #3074, #3075, #3076, #3077, #3078, #3079, #3080, #3081, #3082, #3083, #3084, #3085, #3086, #3087, #3088, #3089, #3090, #3091), .UNSPECIFIED., .F., .F., (3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3), (-1, -0.9836065573770492, -0.9672131147540983, -0.9508196721311475, -0.9344262295081968, -0.9180327868852459, -0.9016393442622951, -0.8852459016393442, -0.8688524590163934, -0.8524590163934427, -0.8360655737704918, -0.819672131147541, -0.8032786885245902, -0.7868852459016393, -0.7704918032786885, -0.7540983606557377, -0.7377049180327868, -0.721311475409836, -0.7049180327868853, -0.6885245901639344, -0.6721311475409836, -0.6557377049180328, -0.639344262295082, -0.6229508196721312, -0.6065573770491803, -0.5901639344262295, -0.5737704918032787, -0.5573770491803278, -0.540983606557377, -0.5245901639344261, -0.5081967213114753, -0.49180327868852464, -0.4754098360655738, -0.45901639344262296, -0.4426229508196722, -0.42622950819672134, -0.4098360655737705, -0.39344262295081966, -0.3770491803278689, -0.36065573770491804, -0.3442622950819672, -0.3278688524590164, -0.3114754098360656, -0.29508196721311475, -0.27868852459016397, -0.26229508196721313, -0.24590163934426232, -0.22950819672131148, -0.21311475409836067, -0.19672131147540983, -0.18032786885245902, -0.1639344262295082, -0.14754098360655737, -0.13114754098360656, -0.11475409836065574, -0.09836065573770492, -0.0819672131147541, -0.06557377049180328, -0.04918032786885246, -0.03278688524590164, -0.01639344262295082, -0), .UNSPECIFIED.); | ||||
| #3093 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #3094 = VECTOR('NONE', #3093, 1); | ||||
| #3095 = CARTESIAN_POINT('NONE', (0.03976237625653429, -0.028889057364922765, -0.063501)); | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -6,7 +6,7 @@ uses-engine = { max-threads = 4 } | ||||
| after-engine = { max-threads = 12 } | ||||
|  | ||||
| [profile.default] | ||||
| slow-timeout = { period = "30s", terminate-after = 1 } | ||||
| slow-timeout = { period = "90s", terminate-after = 1 } | ||||
|  | ||||
| [profile.ci] | ||||
| slow-timeout = { period = "50s", terminate-after = 5 } | ||||
|  | ||||
							
								
								
									
										7
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -3463,6 +3463,12 @@ dependencies = [ | ||||
|  "digest", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "sha1_smol" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" | ||||
|  | ||||
| [[package]] | ||||
| name = "sha2" | ||||
| version = "0.10.8" | ||||
| @ -4378,6 +4384,7 @@ dependencies = [ | ||||
|  "getrandom 0.3.1", | ||||
|  "js-sys", | ||||
|  "serde", | ||||
|  "sha1_smol", | ||||
|  "wasm-bindgen", | ||||
| ] | ||||
|  | ||||
|  | ||||
| @ -802,7 +802,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr | ||||
|                 context_type: crate::execution::ContextType::Mock, | ||||
|             }; | ||||
|  | ||||
|             if let Err(e) = ctx.run(&program, &mut crate::execution::ExecState::new(&ctx.settings)).await { | ||||
|             if let Err(e) = ctx.run(&program, &mut crate::execution::ExecState::new(&ctx)).await { | ||||
|                     return Err(miette::Report::new(crate::errors::Report { | ||||
|                         error: e.error, | ||||
|                         filename: format!("{}{}", #fn_name, #index), | ||||
|  | ||||
| @ -15,10 +15,7 @@ mod test_examples_someFn { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -15,10 +15,7 @@ mod test_examples_someFn { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_show { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_show { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -17,10 +17,7 @@ mod test_examples_my_func { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -17,10 +17,7 @@ mod test_examples_line_to { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_min { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_show { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_import { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_import { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_import { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -16,10 +16,7 @@ mod test_examples_show { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -15,10 +15,7 @@ mod test_examples_some_function { | ||||
|             context_type: crate::execution::ContextType::Mock, | ||||
|         }; | ||||
|         if let Err(e) = ctx | ||||
|             .run( | ||||
|                 &program, | ||||
|                 &mut crate::execution::ExecState::new(&ctx.settings), | ||||
|             ) | ||||
|             .run(&program, &mut crate::execution::ExecState::new(&ctx)) | ||||
|             .await | ||||
|         { | ||||
|             return Err(miette::Report::new(crate::errors::Report { | ||||
|  | ||||
| @ -80,7 +80,7 @@ ts-rs = { version = "10.1.0", features = [ | ||||
| ] } | ||||
| tynm = "0.1.10" | ||||
| url = { version = "2.5.4", features = ["serde"] } | ||||
| uuid = { workspace = true, features = ["v4", "js", "serde"] } | ||||
| uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] } | ||||
| validator = { version = "0.20.0", features = ["derive"] } | ||||
| web-time = "1.1" | ||||
| winnow = "=0.6.24" | ||||
|  | ||||
| @ -77,7 +77,7 @@ fn run_benchmarks(c: &mut Criterion) { | ||||
|             b.iter(|| { | ||||
|                 if let Err(err) = rt.block_on(async { | ||||
|                     let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?; | ||||
|                     let mut exec_state = kcl_lib::ExecState::new(&ctx.settings); | ||||
|                     let mut exec_state = kcl_lib::ExecState::new(&ctx); | ||||
|                     ctx.run(black_box(&program), &mut exec_state).await?; | ||||
|                     ctx.close().await; | ||||
|                     Ok::<(), anyhow::Error>(()) | ||||
|  | ||||
| @ -2053,7 +2053,7 @@ sketch000 = startSketchOn('XY') | ||||
|     let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     let mut exec_state = kcl_lib::ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = kcl_lib::ExecState::new(&ctx); | ||||
|     let program = kcl_lib::Program::parse_no_errs(code).unwrap(); | ||||
|     ctx.run(&program, &mut exec_state).await.unwrap(); | ||||
|  | ||||
| @ -2078,7 +2078,7 @@ async fn kcl_test_ensure_nothing_left_in_batch_multi_file() { | ||||
|     let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     let mut exec_state = kcl_lib::ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = kcl_lib::ExecState::new(&ctx); | ||||
|     let program = kcl_lib::Program::parse_no_errs(&code).unwrap(); | ||||
|     ctx.run(&program, &mut exec_state).await.unwrap(); | ||||
|  | ||||
| @ -2106,7 +2106,7 @@ async fn kcl_test_better_type_names() { | ||||
|         }, | ||||
|         None => todo!(), | ||||
|     }; | ||||
|     assert_eq!(err, "This function expected the input argument to be of type SolidSet but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`"); | ||||
|     assert_eq!(err, "This function expected the input argument to be one or more Solids but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`"); | ||||
| } | ||||
|  | ||||
| #[tokio::test(flavor = "multi_thread")] | ||||
|  | ||||
| @ -10,9 +10,9 @@ use pretty_assertions::assert_eq; | ||||
| async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, ModuleId, uuid::Uuid)> { | ||||
|     let program = Program::parse_no_errs(code)?; | ||||
|     let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default()).await?; | ||||
|     let mut exec_state = ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = ExecState::new(&ctx); | ||||
|     let result = ctx.run(&program, &mut exec_state).await?; | ||||
|     let outcome = exec_state.to_wasm_outcome(result.0); | ||||
|     let outcome = exec_state.to_wasm_outcome(result.0).await; | ||||
|  | ||||
|     // We need to get the sketch ID. | ||||
|     let KclValue::Sketch { value: sketch } = outcome.variables.get(name).unwrap() else { | ||||
|  | ||||
| @ -1153,7 +1153,7 @@ fn find_examples(text: &str, filename: &str) -> Vec<(String, String)> { | ||||
| async fn run_example(text: &str) -> Result<()> { | ||||
|     let program = crate::Program::parse_no_errs(text)?; | ||||
|     let ctx = ExecutorContext::new_with_default_client(crate::UnitLength::Mm).await?; | ||||
|     let mut exec_state = crate::execution::ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = crate::execution::ExecState::new(&ctx); | ||||
|     ctx.run(&program, &mut exec_state).await?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @ -128,9 +128,9 @@ impl StdLibFnArg { | ||||
|             "" | ||||
|         }; | ||||
|         if self.type_ == "Sketch" | ||||
|             || self.type_ == "SketchSet" | ||||
|             || self.type_ == "[Sketch]" | ||||
|             || self.type_ == "Solid" | ||||
|             || self.type_ == "SolidSet" | ||||
|             || self.type_ == "[Solid]" | ||||
|             || self.type_ == "SketchSurface" | ||||
|             || self.type_ == "SketchOrSurface" | ||||
|             || self.type_ == "SolidOrImportedGeometry" | ||||
|  | ||||
| @ -378,22 +378,8 @@ impl EngineManager for EngineConnection { | ||||
|         original | ||||
|     } | ||||
|  | ||||
|     async fn default_planes( | ||||
|         &self, | ||||
|         id_generator: &mut IdGenerator, | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<DefaultPlanes, KclError> { | ||||
|         { | ||||
|             let opt = self.default_planes.read().await.as_ref().cloned(); | ||||
|             if let Some(planes) = opt { | ||||
|                 return Ok(planes); | ||||
|             } | ||||
|         } // drop the read lock | ||||
|  | ||||
|         let new_planes = self.new_default_planes(id_generator, source_range).await?; | ||||
|         *self.default_planes.write().await = Some(new_planes.clone()); | ||||
|  | ||||
|         Ok(new_planes) | ||||
|     fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> { | ||||
|         self.default_planes.clone() | ||||
|     } | ||||
|  | ||||
|     async fn clear_scene_post_hook( | ||||
|  | ||||
| @ -30,6 +30,8 @@ pub struct EngineConnection { | ||||
|     batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>, | ||||
|     artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>, | ||||
|     execution_kind: Arc<RwLock<ExecutionKind>>, | ||||
|     /// The default planes for the scene. | ||||
|     default_planes: Arc<RwLock<Option<DefaultPlanes>>>, | ||||
| } | ||||
|  | ||||
| impl EngineConnection { | ||||
| @ -39,6 +41,7 @@ impl EngineConnection { | ||||
|             batch_end: Arc::new(RwLock::new(IndexMap::new())), | ||||
|             artifact_commands: Arc::new(RwLock::new(Vec::new())), | ||||
|             execution_kind: Default::default(), | ||||
|             default_planes: Default::default(), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @ -73,12 +76,8 @@ impl crate::engine::EngineManager for EngineConnection { | ||||
|         original | ||||
|     } | ||||
|  | ||||
|     async fn default_planes( | ||||
|         &self, | ||||
|         _id_generator: &mut IdGenerator, | ||||
|         _source_range: SourceRange, | ||||
|     ) -> Result<DefaultPlanes, KclError> { | ||||
|         Ok(DefaultPlanes::default()) | ||||
|     fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> { | ||||
|         self.default_planes.clone() | ||||
|     } | ||||
|  | ||||
|     async fn clear_scene_post_hook( | ||||
|  | ||||
| @ -31,12 +31,6 @@ extern "C" { | ||||
|         idToRangeStr: String, | ||||
|     ) -> Result<js_sys::Promise, js_sys::Error>; | ||||
|  | ||||
|     #[wasm_bindgen(method, js_name = wasmGetDefaultPlanes, catch)] | ||||
|     fn get_default_planes(this: &EngineCommandManager) -> Result<js_sys::Promise, js_sys::Error>; | ||||
|  | ||||
|     #[wasm_bindgen(method, js_name = clearDefaultPlanes, catch)] | ||||
|     fn clear_default_planes(this: &EngineCommandManager) -> Result<(), js_sys::Error>; | ||||
|  | ||||
|     #[wasm_bindgen(method, js_name = startNewSession, catch)] | ||||
|     fn start_new_session(this: &EngineCommandManager) -> Result<js_sys::Promise, js_sys::Error>; | ||||
| } | ||||
| @ -49,6 +43,8 @@ pub struct EngineConnection { | ||||
|     responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>, | ||||
|     artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>, | ||||
|     execution_kind: Arc<RwLock<ExecutionKind>>, | ||||
|     /// The default planes for the scene. | ||||
|     default_planes: Arc<RwLock<Option<DefaultPlanes>>>, | ||||
| } | ||||
|  | ||||
| // Safety: WebAssembly will only ever run in a single-threaded context. | ||||
| @ -65,6 +61,7 @@ impl EngineConnection { | ||||
|             responses: Arc::new(RwLock::new(IndexMap::new())), | ||||
|             artifact_commands: Arc::new(RwLock::new(Vec::new())), | ||||
|             execution_kind: Default::default(), | ||||
|             default_planes: Default::default(), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -160,59 +157,18 @@ impl crate::engine::EngineManager for EngineConnection { | ||||
|         original | ||||
|     } | ||||
|  | ||||
|     async fn default_planes( | ||||
|         &self, | ||||
|         _id_generator: &mut IdGenerator, | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<DefaultPlanes, KclError> { | ||||
|         // Get the default planes. | ||||
|         let promise = self.manager.get_default_planes().map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: e.to_string().into(), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!("Failed to wait for promise from get default planes: {:?}", e), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         // Parse the value as a string. | ||||
|         let s = value.as_string().ok_or_else(|| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!( | ||||
|                     "Failed to get string from response from get default planes: `{:?}`", | ||||
|                     value | ||||
|                 ), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         // Deserialize the response. | ||||
|         let default_planes: DefaultPlanes = serde_json::from_str(&s).map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!("Failed to deserialize default planes: {:?}", e), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         Ok(default_planes) | ||||
|     fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> { | ||||
|         self.default_planes.clone() | ||||
|     } | ||||
|  | ||||
|     async fn clear_scene_post_hook( | ||||
|         &self, | ||||
|         _id_generator: &mut IdGenerator, | ||||
|         id_generator: &mut IdGenerator, | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<(), KclError> { | ||||
|         self.manager.clear_default_planes().map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: e.to_string().into(), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|         // Remake the default planes, since they would have been removed after the scene was cleared. | ||||
|         let new_planes = self.new_default_planes(id_generator, source_range).await?; | ||||
|         *self.default_planes.write().await = Some(new_planes); | ||||
|  | ||||
|         // Start a new session. | ||||
|         let promise = self.manager.start_new_session().map_err(|e| { | ||||
|  | ||||
| @ -95,11 +95,26 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { | ||||
|     async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind; | ||||
|  | ||||
|     /// Get the default planes. | ||||
|     fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>>; | ||||
|  | ||||
|     /// Get the default planes, creating them if they don't exist. | ||||
|     async fn default_planes( | ||||
|         &self, | ||||
|         id_generator: &mut IdGenerator, | ||||
|         _source_range: SourceRange, | ||||
|     ) -> Result<DefaultPlanes, crate::errors::KclError>; | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<DefaultPlanes, KclError> { | ||||
|         { | ||||
|             let opt = self.get_default_planes().read().await.as_ref().cloned(); | ||||
|             if let Some(planes) = opt { | ||||
|                 return Ok(planes); | ||||
|             } | ||||
|         } // drop the read lock | ||||
|  | ||||
|         let new_planes = self.new_default_planes(id_generator, source_range).await?; | ||||
|         *self.get_default_planes().write().await = Some(new_planes.clone()); | ||||
|  | ||||
|         Ok(new_planes) | ||||
|     } | ||||
|  | ||||
|     /// Helpers to be called after clearing a scene. | ||||
|     /// (These really only apply to wasm for now). | ||||
|  | ||||
| @ -4,7 +4,7 @@ use thiserror::Error; | ||||
| use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity}; | ||||
|  | ||||
| use crate::{ | ||||
|     execution::{ArtifactCommand, ArtifactGraph, Operation}, | ||||
|     execution::{ArtifactCommand, ArtifactGraph, DefaultPlanes, Operation}, | ||||
|     lsp::IntoDiagnostic, | ||||
|     modules::{ModulePath, ModuleSource}, | ||||
|     source_range::SourceRange, | ||||
| @ -131,6 +131,7 @@ pub struct KclErrorWithOutputs { | ||||
|     pub artifact_graph: ArtifactGraph, | ||||
|     pub filenames: IndexMap<ModuleId, ModulePath>, | ||||
|     pub source_files: IndexMap<ModuleId, ModuleSource>, | ||||
|     pub default_planes: Option<DefaultPlanes>, | ||||
| } | ||||
|  | ||||
| impl KclErrorWithOutputs { | ||||
| @ -141,6 +142,7 @@ impl KclErrorWithOutputs { | ||||
|         artifact_graph: ArtifactGraph, | ||||
|         filenames: IndexMap<ModuleId, ModulePath>, | ||||
|         source_files: IndexMap<ModuleId, ModuleSource>, | ||||
|         default_planes: Option<DefaultPlanes>, | ||||
|     ) -> Self { | ||||
|         Self { | ||||
|             error, | ||||
| @ -149,6 +151,7 @@ impl KclErrorWithOutputs { | ||||
|             artifact_graph, | ||||
|             filenames, | ||||
|             source_files, | ||||
|             default_planes, | ||||
|         } | ||||
|     } | ||||
|     pub fn no_outputs(error: KclError) -> Self { | ||||
| @ -159,6 +162,7 @@ impl KclErrorWithOutputs { | ||||
|             artifact_graph: Default::default(), | ||||
|             filenames: Default::default(), | ||||
|             source_files: Default::default(), | ||||
|             default_planes: Default::default(), | ||||
|         } | ||||
|     } | ||||
|     pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> { | ||||
|  | ||||
| @ -180,15 +180,9 @@ pub enum OpKclValue { | ||||
|     Sketch { | ||||
|         value: Box<OpSketch>, | ||||
|     }, | ||||
|     Sketches { | ||||
|         value: Vec<OpSketch>, | ||||
|     }, | ||||
|     Solid { | ||||
|         value: Box<OpSolid>, | ||||
|     }, | ||||
|     Solids { | ||||
|         value: Vec<OpSolid>, | ||||
|     }, | ||||
|     Helix { | ||||
|         value: Box<OpHelix>, | ||||
|     }, | ||||
| @ -234,7 +228,7 @@ impl From<&KclValue> for OpKclValue { | ||||
|                 ty: ty.clone(), | ||||
|             }, | ||||
|             KclValue::String { value, .. } => Self::String { value: value.clone() }, | ||||
|             KclValue::MixedArray { value, .. } => { | ||||
|             KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { | ||||
|                 let value = value.iter().map(Self::from).collect(); | ||||
|                 Self::Array { value } | ||||
|             } | ||||
| @ -244,7 +238,7 @@ impl From<&KclValue> for OpKclValue { | ||||
|             } | ||||
|             KclValue::TagIdentifier(tag_identifier) => Self::TagIdentifier { | ||||
|                 value: tag_identifier.value.clone(), | ||||
|                 artifact_id: tag_identifier.info.as_ref().map(|info| ArtifactId::new(info.id)), | ||||
|                 artifact_id: tag_identifier.get_cur_info().map(|info| ArtifactId::new(info.id)), | ||||
|             }, | ||||
|             KclValue::TagDeclarator(node) => Self::TagDeclarator { | ||||
|                 name: node.name.clone(), | ||||
| @ -260,29 +254,11 @@ impl From<&KclValue> for OpKclValue { | ||||
|                     artifact_id: value.artifact_id, | ||||
|                 }), | ||||
|             }, | ||||
|             KclValue::Sketches { value } => { | ||||
|                 let value = value | ||||
|                     .iter() | ||||
|                     .map(|sketch| OpSketch { | ||||
|                         artifact_id: sketch.artifact_id, | ||||
|                     }) | ||||
|                     .collect(); | ||||
|                 Self::Sketches { value } | ||||
|             } | ||||
|             KclValue::Solid { value } => Self::Solid { | ||||
|                 value: Box::new(OpSolid { | ||||
|                     artifact_id: value.artifact_id, | ||||
|                 }), | ||||
|             }, | ||||
|             KclValue::Solids { value } => { | ||||
|                 let value = value | ||||
|                     .iter() | ||||
|                     .map(|solid| OpSolid { | ||||
|                         artifact_id: solid.artifact_id, | ||||
|                     }) | ||||
|                     .collect(); | ||||
|                 Self::Solids { value } | ||||
|             } | ||||
|             KclValue::Helix { value } => Self::Helix { | ||||
|                 value: Box::new(OpHelix { | ||||
|                     artifact_id: value.artifact_id, | ||||
| @ -295,7 +271,6 @@ impl From<&KclValue> for OpKclValue { | ||||
|             KclValue::Module { .. } => Self::Module {}, | ||||
|             KclValue::KclNone { .. } => Self::KclNone {}, | ||||
|             KclValue::Type { .. } => Self::Type {}, | ||||
|             KclValue::Tombstone { .. } => unreachable!("Tombstone OpKclValue"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,11 +8,11 @@ use crate::{ | ||||
|     execution::{ | ||||
|         annotations, | ||||
|         cad_op::{OpArg, OpKclValue, Operation}, | ||||
|         kcl_value::{FunctionSource, NumericType, PrimitiveType, RuntimeType}, | ||||
|         kcl_value::{FunctionSource, NumericType, RuntimeType}, | ||||
|         memory, | ||||
|         state::ModuleState, | ||||
|         BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, Plane, PlaneType, Point3d, | ||||
|         TagEngineInfo, TagIdentifier, | ||||
|         BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo, | ||||
|         TagIdentifier, | ||||
|     }, | ||||
|     modules::{ModuleId, ModulePath, ModuleRepr}, | ||||
|     parsing::ast::types::{ | ||||
| @ -23,7 +23,7 @@ use crate::{ | ||||
|     }, | ||||
|     source_range::SourceRange, | ||||
|     std::{ | ||||
|         args::{Arg, FromKclValue, KwArgs}, | ||||
|         args::{Arg, KwArgs}, | ||||
|         FunctionKind, | ||||
|     }, | ||||
|     CompilationError, | ||||
| @ -55,10 +55,9 @@ impl ExecutorContext { | ||||
|         for annotation in annotations { | ||||
|             if annotation.name() == Some(annotations::SETTINGS) { | ||||
|                 if matches!(body_type, BodyType::Root) { | ||||
|                     let old_units = exec_state.length_unit(); | ||||
|                     exec_state.mod_local.settings.update_from_annotation(annotation)?; | ||||
|                     let new_units = exec_state.length_unit(); | ||||
|                     if !self.engine.execution_kind().await.is_isolated() && old_units != new_units { | ||||
|                     if !self.engine.execution_kind().await.is_isolated() { | ||||
|                         self.engine | ||||
|                             .set_units(new_units.into(), annotation.as_source_range()) | ||||
|                             .await?; | ||||
| @ -94,6 +93,7 @@ impl ExecutorContext { | ||||
|         exec_state: &mut ExecState, | ||||
|         exec_kind: ExecutionKind, | ||||
|         preserve_mem: bool, | ||||
|         module_id: ModuleId, | ||||
|         path: &ModulePath, | ||||
|     ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> { | ||||
|         crate::log::log(format!("enter module {path} {}", exec_state.stack())); | ||||
| @ -101,7 +101,12 @@ impl ExecutorContext { | ||||
|         let old_units = exec_state.length_unit(); | ||||
|         let original_execution = self.engine.replace_execution_kind(exec_kind).await; | ||||
|  | ||||
|         let mut local_state = ModuleState::new(&self.settings, path.std_path(), exec_state.stack().memory.clone()); | ||||
|         let mut local_state = ModuleState::new( | ||||
|             &self.settings, | ||||
|             path.std_path(), | ||||
|             exec_state.stack().memory.clone(), | ||||
|             Some(module_id), | ||||
|         ); | ||||
|         if !preserve_mem { | ||||
|             std::mem::swap(&mut exec_state.mod_local, &mut local_state); | ||||
|         } | ||||
| @ -452,7 +457,7 @@ impl ExecutorContext { | ||||
|             ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)), | ||||
|             ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())), | ||||
|             ModuleRepr::Kcl(program, cache) => self | ||||
|                 .exec_module_from_ast(program, &path, exec_state, exec_kind, source_range) | ||||
|                 .exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range) | ||||
|                 .await | ||||
|                 .map(|(_, er, items)| { | ||||
|                     *cache = Some((er, items.clone())); | ||||
| @ -483,7 +488,7 @@ impl ExecutorContext { | ||||
|         let result = match &repr { | ||||
|             ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)), | ||||
|             ModuleRepr::Kcl(program, _) => self | ||||
|                 .exec_module_from_ast(program, &path, exec_state, exec_kind, source_range) | ||||
|                 .exec_module_from_ast(program, module_id, &path, exec_state, exec_kind, source_range) | ||||
|                 .await | ||||
|                 .map(|(val, _, _)| val), | ||||
|             ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self) | ||||
| @ -499,13 +504,16 @@ impl ExecutorContext { | ||||
|     async fn exec_module_from_ast( | ||||
|         &self, | ||||
|         program: &Node<Program>, | ||||
|         module_id: ModuleId, | ||||
|         path: &ModulePath, | ||||
|         exec_state: &mut ExecState, | ||||
|         exec_kind: ExecutionKind, | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> { | ||||
|         exec_state.global.mod_loader.enter_module(path); | ||||
|         let result = self.exec_module_body(program, exec_state, exec_kind, false, path).await; | ||||
|         let result = self | ||||
|             .exec_module_body(program, exec_state, exec_kind, false, module_id, path) | ||||
|             .await; | ||||
|         exec_state.global.mod_loader.leave_module(path); | ||||
|  | ||||
|         result.map_err(|err| { | ||||
| @ -638,11 +646,11 @@ impl ExecutorContext { | ||||
|                 let result = self | ||||
|                     .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind) | ||||
|                     .await?; | ||||
|                 coerce(result, &expr.ty, exec_state).map_err(|value| { | ||||
|                 coerce(&result, &expr.ty, exec_state).ok_or_else(|| { | ||||
|                     KclError::Semantic(KclErrorDetails { | ||||
|                         message: format!( | ||||
|                             "could not coerce {} value to type {}", | ||||
|                             value.human_friendly_type(), | ||||
|                             result.human_friendly_type(), | ||||
|                             expr.ty | ||||
|                         ), | ||||
|                         source_ranges: vec![expr.into()], | ||||
| @ -654,72 +662,14 @@ impl ExecutorContext { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn coerce(value: KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Result<KclValue, KclValue> { | ||||
|     let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, (&value).into()) | ||||
| fn coerce(value: &KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|     let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into()) | ||||
|         .map_err(|e| { | ||||
|             exec_state.err(e); | ||||
|             value.clone() | ||||
|         })? | ||||
|         .ok_or_else(|| value.clone())?; | ||||
|     if value.has_type(&ty) { | ||||
|         return Ok(value); | ||||
|     } | ||||
|         }) | ||||
|         .ok()??; | ||||
|  | ||||
|     // TODO coerce numeric types | ||||
|  | ||||
|     if let KclValue::Object { value, meta } = value { | ||||
|         return match ty { | ||||
|             RuntimeType::Primitive(PrimitiveType::Plane) => { | ||||
|                 let origin = value | ||||
|                     .get("origin") | ||||
|                     .and_then(Point3d::from_kcl_val) | ||||
|                     .ok_or_else(|| KclValue::Object { | ||||
|                         value: value.clone(), | ||||
|                         meta: meta.clone(), | ||||
|                     })?; | ||||
|                 let x_axis = value | ||||
|                     .get("xAxis") | ||||
|                     .and_then(Point3d::from_kcl_val) | ||||
|                     .ok_or_else(|| KclValue::Object { | ||||
|                         value: value.clone(), | ||||
|                         meta: meta.clone(), | ||||
|                     })?; | ||||
|                 let y_axis = value | ||||
|                     .get("yAxis") | ||||
|                     .and_then(Point3d::from_kcl_val) | ||||
|                     .ok_or_else(|| KclValue::Object { | ||||
|                         value: value.clone(), | ||||
|                         meta: meta.clone(), | ||||
|                     })?; | ||||
|                 let z_axis = value | ||||
|                     .get("zAxis") | ||||
|                     .and_then(Point3d::from_kcl_val) | ||||
|                     .ok_or_else(|| KclValue::Object { | ||||
|                         value: value.clone(), | ||||
|                         meta: meta.clone(), | ||||
|                     })?; | ||||
|  | ||||
|                 let id = exec_state.global.id_generator.next_uuid(); | ||||
|                 let plane = Plane { | ||||
|                     id, | ||||
|                     artifact_id: id.into(), | ||||
|                     origin, | ||||
|                     x_axis, | ||||
|                     y_axis, | ||||
|                     z_axis, | ||||
|                     value: PlaneType::Uninit, | ||||
|                     // TODO use length unit from origin | ||||
|                     units: exec_state.length_unit(), | ||||
|                     meta, | ||||
|                 }; | ||||
|  | ||||
|                 Ok(KclValue::Plane { value: Box::new(plane) }) | ||||
|             } | ||||
|             _ => Err(KclValue::Object { value, meta }), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     Err(value) | ||||
|     value.coerce(&ty, exec_state) | ||||
| } | ||||
|  | ||||
| impl BinaryPart { | ||||
| @ -745,33 +695,7 @@ impl BinaryPart { | ||||
| } | ||||
|  | ||||
| impl Node<MemberExpression> { | ||||
|     pub fn get_result_array(&self, exec_state: &mut ExecState, index: usize) -> Result<KclValue, KclError> { | ||||
|         let array = match &self.object { | ||||
|             MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?, | ||||
|             MemberObject::Identifier(identifier) => { | ||||
|                 let value = exec_state.stack().get(&identifier.name, identifier.into())?; | ||||
|                 value.clone() | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let KclValue::MixedArray { value: array, meta: _ } = array else { | ||||
|             return Err(KclError::Semantic(KclErrorDetails { | ||||
|                 message: format!("MemberExpression array is not an array: {:?}", array), | ||||
|                 source_ranges: vec![self.clone().into()], | ||||
|             })); | ||||
|         }; | ||||
|  | ||||
|         if let Some(value) = array.get(index) { | ||||
|             Ok(value.to_owned()) | ||||
|         } else { | ||||
|             Err(KclError::UndefinedValue(KclErrorDetails { | ||||
|                 message: format!("index {} not found in array", index), | ||||
|                 source_ranges: vec![self.clone().into()], | ||||
|             })) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> { | ||||
|     fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> { | ||||
|         let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?; | ||||
|         let object = match &self.object { | ||||
|             // TODO: Don't use recursion here, use a loop. | ||||
| @ -1372,11 +1296,22 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex | ||||
|     // TODO: This could probably be done in a better way, but as of now this was my only idea | ||||
|     // and it works. | ||||
|     match result { | ||||
|         KclValue::Sketch { value: ref mut sketch } => { | ||||
|             for (_, tag) in sketch.tags.iter() { | ||||
|                 exec_state | ||||
|                     .mut_stack() | ||||
|                     .insert_or_update(tag.value.clone(), KclValue::TagIdentifier(Box::new(tag.clone()))); | ||||
|         KclValue::Sketch { value } => { | ||||
|             for (name, tag) in value.tags.iter() { | ||||
|                 if exec_state.stack().cur_frame_contains(name) { | ||||
|                     exec_state.mut_stack().update(name, |v, _| { | ||||
|                         v.as_mut_tag().unwrap().merge_info(tag); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     exec_state | ||||
|                         .mut_stack() | ||||
|                         .add( | ||||
|                             name.to_owned(), | ||||
|                             KclValue::TagIdentifier(Box::new(tag.clone())), | ||||
|                             SourceRange::default(), | ||||
|                         ) | ||||
|                         .unwrap(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         KclValue::Solid { ref mut value } => { | ||||
| @ -1385,7 +1320,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex | ||||
|                     // Get the past tag and update it. | ||||
|                     let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) { | ||||
|                         let mut t = t.clone(); | ||||
|                         let Some(ref info) = t.info else { | ||||
|                         let Some(info) = t.get_cur_info() else { | ||||
|                             return Err(KclError::Internal(KclErrorDetails { | ||||
|                                 message: format!("Tag {} does not have path info", tag.name), | ||||
|                                 source_ranges: vec![tag.into()], | ||||
| @ -1395,59 +1330,70 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex | ||||
|                         let mut info = info.clone(); | ||||
|                         info.surface = Some(v.clone()); | ||||
|                         info.sketch = value.id; | ||||
|                         t.info = Some(info); | ||||
|                         t.info.push((exec_state.stack().current_epoch(), info)); | ||||
|                         t | ||||
|                     } else { | ||||
|                         // It's probably a fillet or a chamfer. | ||||
|                         // Initialize it. | ||||
|                         TagIdentifier { | ||||
|                             value: tag.name.clone(), | ||||
|                             info: Some(TagEngineInfo { | ||||
|                                 id: v.get_id(), | ||||
|                                 surface: Some(v.clone()), | ||||
|                                 path: None, | ||||
|                                 sketch: value.id, | ||||
|                             }), | ||||
|                             info: vec![( | ||||
|                                 exec_state.stack().current_epoch(), | ||||
|                                 TagEngineInfo { | ||||
|                                     id: v.get_id(), | ||||
|                                     surface: Some(v.clone()), | ||||
|                                     path: None, | ||||
|                                     sketch: value.id, | ||||
|                                 }, | ||||
|                             )], | ||||
|                             meta: vec![Metadata { | ||||
|                                 source_range: tag.clone().into(), | ||||
|                             }], | ||||
|                         } | ||||
|                     }; | ||||
|  | ||||
|                     exec_state | ||||
|                         .mut_stack() | ||||
|                         .insert_or_update(tag.name.clone(), KclValue::TagIdentifier(Box::new(tag_id.clone()))); | ||||
|  | ||||
|                     // update the sketch tags. | ||||
|                     value.sketch.tags.insert(tag.name.clone(), tag_id); | ||||
|                     value.sketch.merge_tags(Some(&tag_id).into_iter()); | ||||
|  | ||||
|                     if exec_state.stack().cur_frame_contains(&tag.name) { | ||||
|                         exec_state.mut_stack().update(&tag.name, |v, _| { | ||||
|                             v.as_mut_tag().unwrap().merge_info(&tag_id); | ||||
|                         }); | ||||
|                     } else { | ||||
|                         exec_state | ||||
|                             .mut_stack() | ||||
|                             .add( | ||||
|                                 tag.name.clone(), | ||||
|                                 KclValue::TagIdentifier(Box::new(tag_id)), | ||||
|                                 SourceRange::default(), | ||||
|                             ) | ||||
|                             .unwrap(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Find the stale sketch in memory and update it. | ||||
|             if !value.sketch.tags.is_empty() { | ||||
|                 let updates: Vec<_> = exec_state | ||||
|                 let sketches_to_update: Vec<_> = exec_state | ||||
|                     .stack() | ||||
|                     .find_all_in_current_env(|v| match v { | ||||
|                     .find_keys_in_current_env(|v| match v { | ||||
|                         KclValue::Sketch { value: sk } => sk.artifact_id == value.sketch.artifact_id, | ||||
|                         _ => false, | ||||
|                     }) | ||||
|                     .map(|(k, v)| { | ||||
|                         let mut sketch = v.as_sketch().unwrap().clone(); | ||||
|                         for (tag_name, tag_id) in value.sketch.tags.iter() { | ||||
|                             sketch.tags.insert(tag_name.clone(), tag_id.clone()); | ||||
|                         } | ||||
|                         ( | ||||
|                             k.clone(), | ||||
|                             KclValue::Sketch { | ||||
|                                 value: Box::new(sketch), | ||||
|                             }, | ||||
|                         ) | ||||
|                     }) | ||||
|                     .cloned() | ||||
|                     .collect(); | ||||
|  | ||||
|                 updates | ||||
|                     .into_iter() | ||||
|                     .for_each(|(k, v)| exec_state.mut_stack().insert_or_update(k, v)) | ||||
|                 for k in sketches_to_update { | ||||
|                     exec_state.mut_stack().update(&k, |v, _| { | ||||
|                         let sketch = v.as_mut_sketch().unwrap(); | ||||
|                         sketch.merge_tags(value.sketch.tags.values()); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { | ||||
|             for v in value { | ||||
|                 update_memory_for_tags_of_geometry(v, exec_state)?; | ||||
|             } | ||||
|         } | ||||
|         _ => {} | ||||
| @ -1459,7 +1405,7 @@ impl Node<TagDeclarator> { | ||||
|     pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> { | ||||
|         let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier { | ||||
|             value: self.name.clone(), | ||||
|             info: None, | ||||
|             info: Vec::new(), | ||||
|             meta: vec![Metadata { | ||||
|                 source_range: self.into(), | ||||
|             }], | ||||
| @ -1966,14 +1912,16 @@ impl FunctionSource { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use std::sync::Arc; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::{ | ||||
|         execution::{memory::Stack, parse_execute}, | ||||
|         execution::{memory::Stack, parse_execute, ContextType}, | ||||
|         parsing::ast::types::{DefaultParamVal, Identifier, Parameter}, | ||||
|     }; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_assign_args_to_params() { | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn test_assign_args_to_params() { | ||||
|         // Set up a little framework for this test. | ||||
|         fn mem(number: usize) -> KclValue { | ||||
|             KclValue::Number { | ||||
| @ -2084,7 +2032,16 @@ mod test { | ||||
|                 digest: None, | ||||
|             }); | ||||
|             let args = args.into_iter().map(Arg::synthetic).collect(); | ||||
|             let mut exec_state = ExecState::new(&Default::default()); | ||||
|             let exec_ctxt = ExecutorContext { | ||||
|                 engine: Arc::new(Box::new( | ||||
|                     crate::engine::conn_mock::EngineConnection::new().await.unwrap(), | ||||
|                 )), | ||||
|                 fs: Arc::new(crate::fs::FileManager::new()), | ||||
|                 stdlib: Arc::new(crate::std::StdLib::new()), | ||||
|                 settings: Default::default(), | ||||
|                 context_type: ContextType::Mock, | ||||
|             }; | ||||
|             let mut exec_state = ExecState::new(&exec_ctxt); | ||||
|             exec_state.mod_local.stack = Stack::new_for_tests(); | ||||
|             let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack); | ||||
|             assert_eq!( | ||||
|  | ||||
| @ -23,8 +23,8 @@ type Point3D = kcmc::shared::Point3d<f64>; | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type")] | ||||
| pub enum Geometry { | ||||
|     Sketch(Box<Sketch>), | ||||
|     Solid(Box<Solid>), | ||||
|     Sketch(Sketch), | ||||
|     Solid(Solid), | ||||
| } | ||||
|  | ||||
| impl Geometry { | ||||
| @ -52,8 +52,8 @@ impl Geometry { | ||||
| #[serde(tag = "type")] | ||||
| #[allow(clippy::vec_box)] | ||||
| pub enum Geometries { | ||||
|     Sketches(Vec<Box<Sketch>>), | ||||
|     Solids(Vec<Box<Solid>>), | ||||
|     Sketches(Vec<Sketch>), | ||||
|     Solids(Vec<Solid>), | ||||
| } | ||||
|  | ||||
| impl From<Geometry> for Geometries { | ||||
| @ -65,150 +65,6 @@ impl From<Geometry> for Geometries { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A sketch or a group of sketches. | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type", rename_all = "camelCase")] | ||||
| #[allow(clippy::vec_box)] | ||||
| pub enum SketchSet { | ||||
|     Sketch(Box<Sketch>), | ||||
|     Sketches(Vec<Box<Sketch>>), | ||||
| } | ||||
|  | ||||
| impl SketchSet { | ||||
|     pub fn meta(&self) -> Vec<Metadata> { | ||||
|         match self { | ||||
|             SketchSet::Sketch(sg) => sg.meta.clone(), | ||||
|             SketchSet::Sketches(sg) => sg.iter().flat_map(|sg| sg.meta.clone()).collect(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SketchSet> for Vec<Sketch> { | ||||
|     fn from(value: SketchSet) -> Self { | ||||
|         match value { | ||||
|             SketchSet::Sketch(sg) => vec![*sg], | ||||
|             SketchSet::Sketches(sgs) => sgs.into_iter().map(|sg| *sg).collect(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Sketch> for SketchSet { | ||||
|     fn from(sg: Sketch) -> Self { | ||||
|         SketchSet::Sketch(Box::new(sg)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Box<Sketch>> for SketchSet { | ||||
|     fn from(sg: Box<Sketch>) -> Self { | ||||
|         SketchSet::Sketch(sg) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Sketch>> for SketchSet { | ||||
|     fn from(sg: Vec<Sketch>) -> Self { | ||||
|         if sg.len() == 1 { | ||||
|             SketchSet::Sketch(Box::new(sg[0].clone())) | ||||
|         } else { | ||||
|             SketchSet::Sketches(sg.into_iter().map(Box::new).collect()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Box<Sketch>>> for SketchSet { | ||||
|     fn from(sg: Vec<Box<Sketch>>) -> Self { | ||||
|         if sg.len() == 1 { | ||||
|             SketchSet::Sketch(sg[0].clone()) | ||||
|         } else { | ||||
|             SketchSet::Sketches(sg) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SketchSet> for Vec<Box<Sketch>> { | ||||
|     fn from(sg: SketchSet) -> Self { | ||||
|         match sg { | ||||
|             SketchSet::Sketch(sg) => vec![sg], | ||||
|             SketchSet::Sketches(sgs) => sgs, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<&Sketch> for Vec<Box<Sketch>> { | ||||
|     fn from(sg: &Sketch) -> Self { | ||||
|         vec![Box::new(sg.clone())] | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Box<Sketch>> for Vec<Box<Sketch>> { | ||||
|     fn from(sg: Box<Sketch>) -> Self { | ||||
|         vec![sg] | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A solid or a group of solids. | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type", rename_all = "camelCase")] | ||||
| #[allow(clippy::vec_box)] | ||||
| pub enum SolidSet { | ||||
|     Solid(Box<Solid>), | ||||
|     Solids(Vec<Box<Solid>>), | ||||
| } | ||||
|  | ||||
| impl From<Solid> for SolidSet { | ||||
|     fn from(eg: Solid) -> Self { | ||||
|         SolidSet::Solid(Box::new(eg)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Box<Solid>> for SolidSet { | ||||
|     fn from(eg: Box<Solid>) -> Self { | ||||
|         SolidSet::Solid(eg) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Solid>> for SolidSet { | ||||
|     fn from(eg: Vec<Solid>) -> Self { | ||||
|         if eg.len() == 1 { | ||||
|             SolidSet::Solid(Box::new(eg[0].clone())) | ||||
|         } else { | ||||
|             SolidSet::Solids(eg.into_iter().map(Box::new).collect()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Box<Solid>>> for SolidSet { | ||||
|     fn from(eg: Vec<Box<Solid>>) -> Self { | ||||
|         if eg.len() == 1 { | ||||
|             SolidSet::Solid(eg[0].clone()) | ||||
|         } else { | ||||
|             SolidSet::Solids(eg) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SolidSet> for Vec<Box<Solid>> { | ||||
|     fn from(eg: SolidSet) -> Self { | ||||
|         match eg { | ||||
|             SolidSet::Solid(eg) => vec![eg], | ||||
|             SolidSet::Solids(egs) => egs, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<&Solid> for Vec<Box<Solid>> { | ||||
|     fn from(eg: &Solid) -> Self { | ||||
|         vec![Box::new(eg.clone())] | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Box<Solid>> for Vec<Box<Solid>> { | ||||
|     fn from(eg: Box<Solid>) -> Self { | ||||
|         vec![eg] | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Data for an imported geometry. | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| @ -228,17 +84,29 @@ pub struct ImportedGeometry { | ||||
| #[serde(tag = "type", rename_all = "camelCase")] | ||||
| #[allow(clippy::vec_box)] | ||||
| pub enum SolidOrImportedGeometry { | ||||
|     Solid(Box<Solid>), | ||||
|     ImportedGeometry(Box<ImportedGeometry>), | ||||
|     SolidSet(Vec<Box<Solid>>), | ||||
|     SolidSet(Vec<Solid>), | ||||
| } | ||||
|  | ||||
| impl From<SolidOrImportedGeometry> for crate::execution::KclValue { | ||||
|     fn from(value: SolidOrImportedGeometry) -> Self { | ||||
|         match value { | ||||
|             SolidOrImportedGeometry::Solid(s) => crate::execution::KclValue::Solid { value: s }, | ||||
|             SolidOrImportedGeometry::ImportedGeometry(s) => crate::execution::KclValue::ImportedGeometry(*s), | ||||
|             SolidOrImportedGeometry::SolidSet(s) => crate::execution::KclValue::Solids { value: s }, | ||||
|             SolidOrImportedGeometry::SolidSet(mut s) => { | ||||
|                 if s.len() == 1 { | ||||
|                     crate::execution::KclValue::Solid { | ||||
|                         value: Box::new(s.pop().unwrap()), | ||||
|                     } | ||||
|                 } else { | ||||
|                     crate::execution::KclValue::HomArray { | ||||
|                         value: s | ||||
|                             .into_iter() | ||||
|                             .map(|s| crate::execution::KclValue::Solid { value: Box::new(s) }) | ||||
|                             .collect(), | ||||
|                         ty: crate::execution::PrimitiveType::Solid, | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -246,7 +114,6 @@ impl From<SolidOrImportedGeometry> for crate::execution::KclValue { | ||||
| impl SolidOrImportedGeometry { | ||||
|     pub(crate) fn ids(&self) -> Vec<uuid::Uuid> { | ||||
|         match self { | ||||
|             SolidOrImportedGeometry::Solid(s) => vec![s.id], | ||||
|             SolidOrImportedGeometry::ImportedGeometry(s) => vec![s.id], | ||||
|             SolidOrImportedGeometry::SolidSet(s) => s.iter().map(|s| s.id).collect(), | ||||
|         } | ||||
| @ -370,7 +237,7 @@ impl Plane { | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self { | ||||
|         let id = exec_state.global.id_generator.next_uuid(); | ||||
|         let id = exec_state.next_uuid(); | ||||
|         match value { | ||||
|             PlaneData::XY => Plane { | ||||
|                 id, | ||||
| @ -443,17 +310,20 @@ impl Plane { | ||||
|                 x_axis, | ||||
|                 y_axis, | ||||
|                 z_axis, | ||||
|             } => Plane { | ||||
|                 id, | ||||
|                 artifact_id: id.into(), | ||||
|                 origin, | ||||
|                 x_axis, | ||||
|                 y_axis, | ||||
|                 z_axis, | ||||
|                 value: PlaneType::Custom, | ||||
|                 units: exec_state.length_unit(), | ||||
|                 meta: vec![], | ||||
|             }, | ||||
|             } => { | ||||
|                 let id = exec_state.next_uuid(); | ||||
|                 Plane { | ||||
|                     id, | ||||
|                     artifact_id: id.into(), | ||||
|                     origin, | ||||
|                     x_axis, | ||||
|                     y_axis, | ||||
|                     z_axis, | ||||
|                     value: PlaneType::Custom, | ||||
|                     units: exec_state.length_unit(), | ||||
|                     meta: vec![], | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -636,19 +506,35 @@ impl GetTangentialInfoFromPathsResult { | ||||
| } | ||||
|  | ||||
| impl Sketch { | ||||
|     pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path) { | ||||
|     pub(crate) fn add_tag(&mut self, tag: NodeRef<'_, TagDeclarator>, current_path: &Path, exec_state: &ExecState) { | ||||
|         let mut tag_identifier: TagIdentifier = tag.into(); | ||||
|         let base = current_path.get_base(); | ||||
|         tag_identifier.info = Some(TagEngineInfo { | ||||
|             id: base.geo_meta.id, | ||||
|             sketch: self.id, | ||||
|             path: Some(current_path.clone()), | ||||
|             surface: None, | ||||
|         }); | ||||
|         tag_identifier.info.push(( | ||||
|             exec_state.stack().current_epoch(), | ||||
|             TagEngineInfo { | ||||
|                 id: base.geo_meta.id, | ||||
|                 sketch: self.id, | ||||
|                 path: Some(current_path.clone()), | ||||
|                 surface: None, | ||||
|             }, | ||||
|         )); | ||||
|  | ||||
|         self.tags.insert(tag.name.to_string(), tag_identifier); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn merge_tags<'a>(&mut self, tags: impl Iterator<Item = &'a TagIdentifier>) { | ||||
|         for t in tags { | ||||
|             match self.tags.get_mut(&t.value) { | ||||
|                 Some(id) => { | ||||
|                     id.merge_info(t); | ||||
|                 } | ||||
|                 None => { | ||||
|                     self.tags.insert(t.value.clone(), t.clone()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Get the path most recently sketched. | ||||
|     pub(crate) fn latest_path(&self) -> Option<&Path> { | ||||
|         self.paths.last() | ||||
|  | ||||
							
								
								
									
										83
									
								
								rust/kcl-lib/src/execution/id_generator.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								rust/kcl-lib/src/execution/id_generator.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| //! A generator for ArtifactIds that can be stable across executions. | ||||
|  | ||||
| use crate::execution::ModuleId; | ||||
|  | ||||
| const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("efcd6508-4ce6-4a09-8317-e6a6994a3cd7"); | ||||
|  | ||||
| /// A generator for ArtifactIds that can be stable across executions. | ||||
| #[derive(Debug, Clone, Default, PartialEq)] | ||||
| pub struct IdGenerator { | ||||
|     module_id: Option<ModuleId>, | ||||
|     next_id: u64, | ||||
| } | ||||
|  | ||||
| impl IdGenerator { | ||||
|     pub fn new(module_id: Option<ModuleId>) -> Self { | ||||
|         Self { module_id, next_id: 0 } | ||||
|     } | ||||
|  | ||||
|     pub fn next_uuid(&mut self) -> uuid::Uuid { | ||||
|         let next_id = self.next_id; | ||||
|  | ||||
|         let next = format!( | ||||
|             "{} {}", | ||||
|             self.module_id.map(|id| id.to_string()).unwrap_or("none".to_string()), | ||||
|             next_id | ||||
|         ); | ||||
|         let next_uuid = uuid::Uuid::new_v5(&NAMESPACE_KCL, next.as_bytes()); | ||||
|  | ||||
|         self.next_id += 1; | ||||
|  | ||||
|         next_uuid | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_id_generator() { | ||||
|         let mut generator = IdGenerator::new(Some(ModuleId::default())); | ||||
|  | ||||
|         let uuid1 = generator.next_uuid(); | ||||
|         let uuid2 = generator.next_uuid(); | ||||
|  | ||||
|         assert_ne!(uuid1, uuid2); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     // Test that the same generator produces the same UUIDs. | ||||
|     fn test_id_generator_stable() { | ||||
|         let mut generator = IdGenerator::new(Some(ModuleId::default())); | ||||
|  | ||||
|         let uuid1 = generator.next_uuid(); | ||||
|         let uuid2 = generator.next_uuid(); | ||||
|  | ||||
|         let mut generator = IdGenerator::new(Some(ModuleId::default())); | ||||
|  | ||||
|         let uuid3 = generator.next_uuid(); | ||||
|         let uuid4 = generator.next_uuid(); | ||||
|  | ||||
|         assert_eq!(uuid1, uuid3); | ||||
|         assert_eq!(uuid2, uuid4); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     // Generate 20 uuids and make sure all are unique. | ||||
|     fn test_id_generator_unique() { | ||||
|         let mut generator = IdGenerator::new(Some(ModuleId::default())); | ||||
|  | ||||
|         let mut uuids = Vec::new(); | ||||
|  | ||||
|         for _ in 0..20 { | ||||
|             uuids.push(generator.next_uuid()); | ||||
|         } | ||||
|  | ||||
|         for i in 0..uuids.len() { | ||||
|             for j in i + 1..uuids.len() { | ||||
|                 assert_ne!(uuids[i], uuids[j]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -6,13 +6,12 @@ use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use super::{ | ||||
|     memory::{self, EnvironmentRef}, | ||||
|     MetaSettings, | ||||
|     MetaSettings, Point3d, | ||||
| }; | ||||
| use crate::{ | ||||
|     errors::KclErrorDetails, | ||||
|     execution::{ | ||||
|         ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, SketchSet, Solid, SolidSet, | ||||
|         TagIdentifier, | ||||
|         ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, Solid, TagIdentifier, | ||||
|     }, | ||||
|     parsing::{ | ||||
|         ast::types::{ | ||||
| @ -21,7 +20,10 @@ use crate::{ | ||||
|         }, | ||||
|         token::NumericSuffix, | ||||
|     }, | ||||
|     std::{args::Arg, StdFnProps}, | ||||
|     std::{ | ||||
|         args::{Arg, FromKclValue}, | ||||
|         StdFnProps, | ||||
|     }, | ||||
|     CompilationError, KclError, ModuleId, SourceRange, | ||||
| }; | ||||
|  | ||||
| @ -58,6 +60,13 @@ pub enum KclValue { | ||||
|         #[serde(skip)] | ||||
|         meta: Vec<Metadata>, | ||||
|     }, | ||||
|     // An array where all values have a shared type (not necessarily the same principal type). | ||||
|     HomArray { | ||||
|         value: Vec<KclValue>, | ||||
|         // The type of values, not the array type. | ||||
|         #[serde(skip)] | ||||
|         ty: PrimitiveType, | ||||
|     }, | ||||
|     Object { | ||||
|         value: KclObjectFields, | ||||
|         #[serde(skip)] | ||||
| @ -74,15 +83,9 @@ pub enum KclValue { | ||||
|     Sketch { | ||||
|         value: Box<Sketch>, | ||||
|     }, | ||||
|     Sketches { | ||||
|         value: Vec<Box<Sketch>>, | ||||
|     }, | ||||
|     Solid { | ||||
|         value: Box<Solid>, | ||||
|     }, | ||||
|     Solids { | ||||
|         value: Vec<Box<Solid>>, | ||||
|     }, | ||||
|     Helix { | ||||
|         value: Box<Helix>, | ||||
|     }, | ||||
| @ -111,12 +114,6 @@ pub enum KclValue { | ||||
|         #[serde(skip)] | ||||
|         meta: Vec<Metadata>, | ||||
|     }, | ||||
|     // Only used for memory management. Should never be visible outside of the memory module. | ||||
|     Tombstone { | ||||
|         value: (), | ||||
|         #[serde(skip)] | ||||
|         meta: Vec<Metadata>, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, PartialEq, Default)] | ||||
| @ -145,48 +142,46 @@ impl JsonSchema for FunctionSource { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SketchSet> for KclValue { | ||||
|     fn from(sg: SketchSet) -> Self { | ||||
|         match sg { | ||||
|             SketchSet::Sketch(value) => KclValue::Sketch { value }, | ||||
|             SketchSet::Sketches(value) => KclValue::Sketches { value }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Box<Sketch>>> for KclValue { | ||||
|     fn from(sg: Vec<Box<Sketch>>) -> Self { | ||||
|         KclValue::Sketches { value: sg } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SolidSet> for KclValue { | ||||
|     fn from(eg: SolidSet) -> Self { | ||||
|         match eg { | ||||
|             SolidSet::Solid(eg) => KclValue::Solid { value: eg }, | ||||
|             SolidSet::Solids(egs) => KclValue::Solids { value: egs }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Box<Solid>>> for KclValue { | ||||
|     fn from(eg: Vec<Box<Solid>>) -> Self { | ||||
| impl From<Vec<Sketch>> for KclValue { | ||||
|     fn from(mut eg: Vec<Sketch>) -> Self { | ||||
|         if eg.len() == 1 { | ||||
|             KclValue::Solid { value: eg[0].clone() } | ||||
|             KclValue::Sketch { | ||||
|                 value: Box::new(eg.pop().unwrap()), | ||||
|             } | ||||
|         } else { | ||||
|             KclValue::Solids { value: eg } | ||||
|             KclValue::HomArray { | ||||
|                 value: eg | ||||
|                     .into_iter() | ||||
|                     .map(|s| KclValue::Sketch { value: Box::new(s) }) | ||||
|                     .collect(), | ||||
|                 ty: crate::execution::PrimitiveType::Sketch, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Vec<Solid>> for KclValue { | ||||
|     fn from(mut eg: Vec<Solid>) -> Self { | ||||
|         if eg.len() == 1 { | ||||
|             KclValue::Solid { | ||||
|                 value: Box::new(eg.pop().unwrap()), | ||||
|             } | ||||
|         } else { | ||||
|             KclValue::HomArray { | ||||
|                 value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(), | ||||
|                 ty: crate::execution::PrimitiveType::Solid, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<KclValue> for Vec<SourceRange> { | ||||
|     fn from(item: KclValue) -> Self { | ||||
|         match item { | ||||
|             KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], | ||||
|             KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), | ||||
|             KclValue::Solid { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), | ||||
|             KclValue::Sketch { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), | ||||
|             KclValue::Helix { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), | ||||
|             KclValue::Function { meta, .. } => to_vec_sr(&meta), | ||||
| @ -196,12 +191,12 @@ impl From<KclValue> for Vec<SourceRange> { | ||||
|             KclValue::Number { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::String { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::MixedArray { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), | ||||
|             KclValue::Object { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::Module { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::Uuid { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::Type { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::KclNone { meta, .. } => to_vec_sr(&meta), | ||||
|             KclValue::Tombstone { .. } => unreachable!("Tombstone SourceRange"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -216,9 +211,7 @@ impl From<&KclValue> for Vec<SourceRange> { | ||||
|             KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)], | ||||
|             KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), | ||||
|             KclValue::Solid { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), | ||||
|             KclValue::Sketch { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), | ||||
|             KclValue::Helix { value } => to_vec_sr(&value.meta), | ||||
|             KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta), | ||||
|             KclValue::Function { meta, .. } => to_vec_sr(meta), | ||||
| @ -229,11 +222,11 @@ impl From<&KclValue> for Vec<SourceRange> { | ||||
|             KclValue::String { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::Uuid { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::MixedArray { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(), | ||||
|             KclValue::Object { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::Module { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::KclNone { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::Type { meta, .. } => to_vec_sr(meta), | ||||
|             KclValue::Tombstone { .. } => unreachable!("Tombstone &SourceRange"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -253,22 +246,20 @@ impl KclValue { | ||||
|             KclValue::Number { meta, .. } => meta.clone(), | ||||
|             KclValue::String { value: _, meta } => meta.clone(), | ||||
|             KclValue::MixedArray { value: _, meta } => meta.clone(), | ||||
|             KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(), | ||||
|             KclValue::Object { value: _, meta } => meta.clone(), | ||||
|             KclValue::TagIdentifier(x) => x.meta.clone(), | ||||
|             KclValue::TagDeclarator(x) => vec![x.metadata()], | ||||
|             KclValue::Plane { value } => value.meta.clone(), | ||||
|             KclValue::Face { value } => value.meta.clone(), | ||||
|             KclValue::Sketch { value } => value.meta.clone(), | ||||
|             KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), | ||||
|             KclValue::Solid { value } => value.meta.clone(), | ||||
|             KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), | ||||
|             KclValue::Helix { value } => value.meta.clone(), | ||||
|             KclValue::ImportedGeometry(x) => x.meta.clone(), | ||||
|             KclValue::Function { meta, .. } => meta.clone(), | ||||
|             KclValue::Module { meta, .. } => meta.clone(), | ||||
|             KclValue::KclNone { meta, .. } => meta.clone(), | ||||
|             KclValue::Type { meta, .. } => meta.clone(), | ||||
|             KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -285,29 +276,6 @@ impl KclValue { | ||||
|         Some(ast.as_source_range()) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_solid_set(&self) -> Result<SolidSet> { | ||||
|         match self { | ||||
|             KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())), | ||||
|             KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())), | ||||
|             KclValue::MixedArray { value, .. } => { | ||||
|                 let solids: Vec<_> = value | ||||
|                     .iter() | ||||
|                     .enumerate() | ||||
|                     .map(|(i, v)| { | ||||
|                         v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| { | ||||
|                             anyhow::anyhow!( | ||||
|                                 "expected this array to only contain solids, but element {i} was actually {}", | ||||
|                                 v.human_friendly_type() | ||||
|                             ) | ||||
|                         }) | ||||
|                     }) | ||||
|                     .collect::<Result<_, _>>()?; | ||||
|                 Ok(SolidSet::Solids(solids)) | ||||
|             } | ||||
|             _ => anyhow::bail!("Not a solid or solids: {:?}", self), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     pub(crate) fn none() -> Self { | ||||
|         Self::KclNone { | ||||
| @ -324,9 +292,7 @@ impl KclValue { | ||||
|             KclValue::TagDeclarator(_) => "TagDeclarator", | ||||
|             KclValue::TagIdentifier(_) => "TagIdentifier", | ||||
|             KclValue::Solid { .. } => "Solid", | ||||
|             KclValue::Solids { .. } => "Solids", | ||||
|             KclValue::Sketch { .. } => "Sketch", | ||||
|             KclValue::Sketches { .. } => "Sketches", | ||||
|             KclValue::Helix { .. } => "Helix", | ||||
|             KclValue::ImportedGeometry(_) => "ImportedGeometry", | ||||
|             KclValue::Function { .. } => "Function", | ||||
| @ -336,11 +302,11 @@ impl KclValue { | ||||
|             KclValue::Number { .. } => "number", | ||||
|             KclValue::String { .. } => "string (text)", | ||||
|             KclValue::MixedArray { .. } => "array (list)", | ||||
|             KclValue::HomArray { .. } => "array (list)", | ||||
|             KclValue::Object { .. } => "object", | ||||
|             KclValue::Module { .. } => "module", | ||||
|             KclValue::Type { .. } => "type", | ||||
|             KclValue::KclNone { .. } => "None", | ||||
|             KclValue::Tombstone { .. } => "TOMBSTONE", | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -367,16 +333,14 @@ impl KclValue { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self { | ||||
|     pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self { | ||||
|         let mut result = self.clone(); | ||||
|         if let KclValue::Function { | ||||
|             value: FunctionSource::User { ref mut memory, .. }, | ||||
|             .. | ||||
|         } = result | ||||
|         { | ||||
|             if let Some(new) = env_map.get(memory) { | ||||
|                 *memory = *new; | ||||
|             } | ||||
|             memory.replace_env(old_env, new_env); | ||||
|         } | ||||
|         result | ||||
|     } | ||||
| @ -501,6 +465,21 @@ impl KclValue { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> { | ||||
|         if let KclValue::Sketch { value } = self { | ||||
|             Some(value) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> { | ||||
|         if let KclValue::TagIdentifier(value) = self { | ||||
|             Some(value) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|     pub fn as_f64(&self) -> Option<f64> { | ||||
|         if let KclValue::Number { value, .. } = &self { | ||||
|             Some(*value) | ||||
| @ -563,17 +542,6 @@ impl KclValue { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Get an optional tag from a memory item. | ||||
|     pub fn get_tag_declarator_opt(&self) -> Result<Option<TagNode>, KclError> { | ||||
|         match self { | ||||
|             KclValue::TagDeclarator(t) => Ok(Some((**t).clone())), | ||||
|             _ => Err(KclError::Semantic(KclErrorDetails { | ||||
|                 message: format!("Not a tag declarator: {:?}", self), | ||||
|                 source_ranges: self.clone().into(), | ||||
|             })), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// If this KCL value is a bool, retrieve it. | ||||
|     pub fn get_bool(&self) -> Result<bool, KclError> { | ||||
|         let Self::Bool { value: b, .. } = self else { | ||||
| @ -594,6 +562,215 @@ impl KclValue { | ||||
|         self_ty.subtype(ty) | ||||
|     } | ||||
|  | ||||
|     /// Coerce `self` to a new value which has `ty` as it's closest supertype. | ||||
|     /// | ||||
|     /// If the result is Some, then: | ||||
|     ///   - result.principal_type().unwrap().subtype(ty) | ||||
|     /// | ||||
|     /// If self.principal_type() == ty then result == self | ||||
|     pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         match ty { | ||||
|             RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state), | ||||
|             RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state), | ||||
|             RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state), | ||||
|             RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state), | ||||
|             RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         let value = match self { | ||||
|             KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0], | ||||
|             _ => self, | ||||
|         }; | ||||
|         match ty { | ||||
|             // TODO numeric type coercions | ||||
|             PrimitiveType::Number(_ty) => match value { | ||||
|                 KclValue::Number { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::String => match value { | ||||
|                 KclValue::String { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::Boolean => match value { | ||||
|                 KclValue::Bool { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::Sketch => match value { | ||||
|                 KclValue::Sketch { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::Solid => match value { | ||||
|                 KclValue::Solid { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::Plane => match value { | ||||
|                 KclValue::Plane { .. } => Some(value.clone()), | ||||
|                 KclValue::Object { value, meta } => { | ||||
|                     let origin = value.get("origin").and_then(Point3d::from_kcl_val)?; | ||||
|                     let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?; | ||||
|                     let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?; | ||||
|                     let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?; | ||||
|  | ||||
|                     let id = exec_state.mod_local.id_generator.next_uuid(); | ||||
|                     let plane = Plane { | ||||
|                         id, | ||||
|                         artifact_id: id.into(), | ||||
|                         origin, | ||||
|                         x_axis, | ||||
|                         y_axis, | ||||
|                         z_axis, | ||||
|                         value: super::PlaneType::Uninit, | ||||
|                         // TODO use length unit from origin | ||||
|                         units: exec_state.length_unit(), | ||||
|                         meta: meta.clone(), | ||||
|                     }; | ||||
|  | ||||
|                     Some(KclValue::Plane { value: Box::new(plane) }) | ||||
|                 } | ||||
|                 _ => None, | ||||
|             }, | ||||
|             PrimitiveType::ImportedGeometry => match value { | ||||
|                 KclValue::ImportedGeometry { .. } => Some(value.clone()), | ||||
|                 _ => None, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         match self { | ||||
|             KclValue::HomArray { value, ty: aty } => { | ||||
|                 // TODO could check types of values individually | ||||
|                 if aty != ty { | ||||
|                     return None; | ||||
|                 } | ||||
|  | ||||
|                 let value = match len { | ||||
|                     ArrayLen::None => value.clone(), | ||||
|                     ArrayLen::NonEmpty => { | ||||
|                         if value.is_empty() { | ||||
|                             return None; | ||||
|                         } | ||||
|  | ||||
|                         value.clone() | ||||
|                     } | ||||
|                     ArrayLen::Known(n) => { | ||||
|                         if n != value.len() { | ||||
|                             return None; | ||||
|                         } | ||||
|  | ||||
|                         value[..n].to_vec() | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 Some(KclValue::HomArray { value, ty: ty.clone() }) | ||||
|             } | ||||
|             KclValue::MixedArray { value, .. } => { | ||||
|                 let value = match len { | ||||
|                     ArrayLen::None => value.clone(), | ||||
|                     ArrayLen::NonEmpty => { | ||||
|                         if value.is_empty() { | ||||
|                             return None; | ||||
|                         } | ||||
|  | ||||
|                         value.clone() | ||||
|                     } | ||||
|                     ArrayLen::Known(n) => { | ||||
|                         if n != value.len() { | ||||
|                             return None; | ||||
|                         } | ||||
|  | ||||
|                         value[..n].to_vec() | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 let rt = RuntimeType::Primitive(ty.clone()); | ||||
|                 let value = value | ||||
|                     .iter() | ||||
|                     .map(|v| v.coerce(&rt, exec_state)) | ||||
|                     .collect::<Option<Vec<_>>>()?; | ||||
|  | ||||
|                 Some(KclValue::HomArray { value, ty: ty.clone() }) | ||||
|             } | ||||
|             KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray { | ||||
|                 value: Vec::new(), | ||||
|                 ty: ty.clone(), | ||||
|             }), | ||||
|             value if len.satisfied(1) => { | ||||
|                 if value.has_type(&RuntimeType::Primitive(ty.clone())) { | ||||
|                     Some(KclValue::HomArray { | ||||
|                         value: vec![value.clone()], | ||||
|                         ty: ty.clone(), | ||||
|                     }) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             } | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         match self { | ||||
|             KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => { | ||||
|                 if value.len() < tys.len() { | ||||
|                     return None; | ||||
|                 } | ||||
|                 let mut result = Vec::new(); | ||||
|                 for (i, t) in tys.iter().enumerate() { | ||||
|                     result.push(value[i].coerce_to_primitive_type(t, exec_state)?); | ||||
|                 } | ||||
|  | ||||
|                 Some(KclValue::MixedArray { | ||||
|                     value: result, | ||||
|                     meta: Vec::new(), | ||||
|                 }) | ||||
|             } | ||||
|             KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray { | ||||
|                 value: Vec::new(), | ||||
|                 meta: meta.clone(), | ||||
|             }), | ||||
|             value if tys.len() == 1 => { | ||||
|                 if value.has_type(&RuntimeType::Primitive(tys[0].clone())) { | ||||
|                     Some(KclValue::MixedArray { | ||||
|                         value: vec![value.clone()], | ||||
|                         meta: Vec::new(), | ||||
|                     }) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             } | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         for t in tys { | ||||
|             if let Some(v) = self.coerce(t, exec_state) { | ||||
|                 return Some(v); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option<KclValue> { | ||||
|         match self { | ||||
|             KclValue::Object { value, .. } => { | ||||
|                 for (s, t) in tys { | ||||
|                     // TODO coerce fields | ||||
|                     if !value.get(s)?.has_type(t) { | ||||
|                         return None; | ||||
|                     } | ||||
|                 } | ||||
|                 // TODO remove non-required fields | ||||
|                 Some(self.clone()) | ||||
|             } | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn principal_type(&self) -> Option<RuntimeType> { | ||||
|         match self { | ||||
|             KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)), | ||||
| @ -608,26 +785,24 @@ impl KclValue { | ||||
|             } | ||||
|             KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)), | ||||
|             KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)), | ||||
|             KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)), | ||||
|             KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)), | ||||
|             KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)), | ||||
|             KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)), | ||||
|             KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple( | ||||
|                 value | ||||
|                     .iter() | ||||
|                     .map(|v| v.principal_type().and_then(RuntimeType::primitive)) | ||||
|                     .collect::<Option<Vec<_>>>()?, | ||||
|             )), | ||||
|             KclValue::HomArray { ty, value, .. } => Some(RuntimeType::Array(ty.clone(), ArrayLen::Known(value.len()))), | ||||
|             KclValue::Face { .. } => None, | ||||
|             KclValue::Helix { .. } | ||||
|             | KclValue::ImportedGeometry(..) | ||||
|             | KclValue::Function { .. } | ||||
|             | KclValue::Module { .. } | ||||
|             | KclValue::TagIdentifier(_) | ||||
|             | KclValue::TagDeclarator(_) | ||||
|             | KclValue::KclNone { .. } | ||||
|             | KclValue::Type { .. } | ||||
|             | KclValue::Uuid { .. } | ||||
|             | KclValue::Tombstone { .. } => None, | ||||
|             | KclValue::Uuid { .. } => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -729,20 +904,18 @@ impl KclValue { | ||||
|             KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)), | ||||
|             // TODO better Array and Object stringification | ||||
|             KclValue::MixedArray { .. } => Some("[...]".to_owned()), | ||||
|             KclValue::HomArray { .. } => Some("[...]".to_owned()), | ||||
|             KclValue::Object { .. } => Some("{ ... }".to_owned()), | ||||
|             KclValue::Module { .. } | ||||
|             | KclValue::Solid { .. } | ||||
|             | KclValue::Solids { .. } | ||||
|             | KclValue::Sketch { .. } | ||||
|             | KclValue::Sketches { .. } | ||||
|             | KclValue::Helix { .. } | ||||
|             | KclValue::ImportedGeometry(_) | ||||
|             | KclValue::Function { .. } | ||||
|             | KclValue::Plane { .. } | ||||
|             | KclValue::Face { .. } | ||||
|             | KclValue::KclNone { .. } | ||||
|             | KclValue::Type { .. } | ||||
|             | KclValue::Tombstone { .. } => None, | ||||
|             | KclValue::Type { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -750,7 +923,8 @@ impl KclValue { | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub enum RuntimeType { | ||||
|     Primitive(PrimitiveType), | ||||
|     Array(PrimitiveType), | ||||
|     Array(PrimitiveType, ArrayLen), | ||||
|     Union(Vec<RuntimeType>), | ||||
|     Tuple(Vec<PrimitiveType>), | ||||
|     Object(Vec<(String, RuntimeType)>), | ||||
| } | ||||
| @ -765,7 +939,9 @@ impl RuntimeType { | ||||
|             Type::Primitive(pt) => { | ||||
|                 PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive) | ||||
|             } | ||||
|             Type::Array(pt) => PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Array), | ||||
|             Type::Array(pt) => { | ||||
|                 PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(|t| RuntimeType::Array(t, ArrayLen::None)) | ||||
|             } | ||||
|             Type::Object { properties } => properties | ||||
|                 .into_iter() | ||||
|                 .map(|p| { | ||||
| @ -781,15 +957,37 @@ impl RuntimeType { | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub fn human_friendly_type(&self) -> String { | ||||
|         match self { | ||||
|             RuntimeType::Primitive(ty) => ty.to_string(), | ||||
|             RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()), | ||||
|             RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()), | ||||
|             RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()), | ||||
|             RuntimeType::Union(tys) => tys | ||||
|                 .iter() | ||||
|                 .map(Self::human_friendly_type) | ||||
|                 .collect::<Vec<_>>() | ||||
|                 .join(" or "), | ||||
|             RuntimeType::Tuple(tys) => format!( | ||||
|                 "an array with values of types ({})", | ||||
|                 tys.iter().map(PrimitiveType::to_string).collect::<Vec<_>>().join(", ") | ||||
|             ), | ||||
|             RuntimeType::Object(_) => format!("an object with fields {}", self), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Subtype with no coercion, including refining numeric types. | ||||
|     fn subtype(&self, sup: &RuntimeType) -> bool { | ||||
|         use RuntimeType::*; | ||||
|  | ||||
|         match (self, sup) { | ||||
|             (Primitive(t1), Primitive(t2)) => t1 == t2, | ||||
|             // TODO arrays could be covariant | ||||
|             (Primitive(t1), Primitive(t2)) | (Array(t1), Array(t2)) => t1 == t2, | ||||
|             (Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2), | ||||
|             (Tuple(t1), Tuple(t2)) => t1 == t2, | ||||
|             (Tuple(t1), Array(t2)) => t1.iter().all(|t| t == t2), | ||||
|             (Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == t2), | ||||
|             (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)), | ||||
|             (t1, Union(ts2)) => ts2.contains(t1), | ||||
|             // TODO record subtyping - subtype can be larger, fields can be covariant. | ||||
|             (Object(t1), Object(t2)) => t1 == t2, | ||||
|             _ => false, | ||||
| @ -808,12 +1006,21 @@ impl fmt::Display for RuntimeType { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match self { | ||||
|             RuntimeType::Primitive(t) => t.fmt(f), | ||||
|             RuntimeType::Array(t) => write!(f, "[{t}]"), | ||||
|             RuntimeType::Array(t, l) => match l { | ||||
|                 ArrayLen::None => write!(f, "[{t}]"), | ||||
|                 ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"), | ||||
|                 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"), | ||||
|             }, | ||||
|             RuntimeType::Tuple(ts) => write!( | ||||
|                 f, | ||||
|                 "[{}]", | ||||
|                 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ") | ||||
|             ), | ||||
|             RuntimeType::Union(ts) => write!( | ||||
|                 f, | ||||
|                 "{}", | ||||
|                 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ") | ||||
|             ), | ||||
|             RuntimeType::Object(items) => write!( | ||||
|                 f, | ||||
|                 "{{ {} }}", | ||||
| @ -827,6 +1034,34 @@ impl fmt::Display for RuntimeType { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| pub enum ArrayLen { | ||||
|     None, | ||||
|     NonEmpty, | ||||
|     Known(usize), | ||||
| } | ||||
|  | ||||
| impl ArrayLen { | ||||
|     pub fn subtype(self, other: ArrayLen) -> bool { | ||||
|         match (self, other) { | ||||
|             (_, ArrayLen::None) => true, | ||||
|             (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true, | ||||
|             (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true, | ||||
|             (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true, | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// True if the length constraint is satisfied by the supplied length. | ||||
|     fn satisfied(self, len: usize) -> bool { | ||||
|         match self { | ||||
|             ArrayLen::None => true, | ||||
|             ArrayLen::NonEmpty => len > 0, | ||||
|             ArrayLen::Known(s) => len == s, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub enum PrimitiveType { | ||||
|     Number(NumericType), | ||||
| @ -835,6 +1070,7 @@ pub enum PrimitiveType { | ||||
|     Sketch, | ||||
|     Solid, | ||||
|     Plane, | ||||
|     ImportedGeometry, | ||||
| } | ||||
|  | ||||
| impl PrimitiveType { | ||||
| @ -866,6 +1102,19 @@ impl PrimitiveType { | ||||
|             _ => None, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn display_multiple(&self) -> String { | ||||
|         match self { | ||||
|             PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"), | ||||
|             PrimitiveType::Number(_) => "numbers".to_owned(), | ||||
|             PrimitiveType::String => "strings".to_owned(), | ||||
|             PrimitiveType::Boolean => "bools".to_owned(), | ||||
|             PrimitiveType::Sketch => "Sketches".to_owned(), | ||||
|             PrimitiveType::Solid => "Solids".to_owned(), | ||||
|             PrimitiveType::Plane => "Planes".to_owned(), | ||||
|             PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Display for PrimitiveType { | ||||
| @ -878,6 +1127,7 @@ impl fmt::Display for PrimitiveType { | ||||
|             PrimitiveType::Sketch => write!(f, "Sketch"), | ||||
|             PrimitiveType::Solid => write!(f, "Solid"), | ||||
|             PrimitiveType::Plane => write!(f, "Plane"), | ||||
|             PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,18 +6,18 @@ | ||||
| //! one per execution. It has no explicit support for caching between executions. | ||||
| //! | ||||
| //! Memory is mostly immutable (since KCL does not support mutation or reassignment). However, tags | ||||
| //! may change as code is executed and that mutates memory. Therefore, | ||||
| //! may change as code is executed and that mutates memory. Therefore to some extent, | ||||
| //! ProgramMemory supports mutability and does not rely on KCL's (mostly) immutable nature. | ||||
| //! | ||||
| //! ProgramMemory is observably monotonic, i.e., it only grows and even when we pop a stack frame, | ||||
| //! the frame is retained unless we can prove it is unreferenced. We remove some values which we | ||||
| //! know cannot be referenced, but we should in the future do better garbage collection (of values | ||||
| //!  and envs). | ||||
| //! and envs). | ||||
| //! | ||||
| //! ## Concepts | ||||
| //! | ||||
| //! There are three main moving parts for ProgramMemory: environments, snapshots, and stacks. I'll | ||||
| //! cover environments (and the call stack) first as if snapshots didn't exist, then describe snapshots. | ||||
| //! There are three main moving parts for ProgramMemory: environments, epochs, and stacks. I'll | ||||
| //! cover environments (and the call stack) first as if epochs didn't exist, then describe epochs. | ||||
| //! | ||||
| //! An environment is a set of bindings (i.e., a map from names to values). Environments handle | ||||
| //! both scoping and context switching. A new lexical scope means a new environment. Nesting of scopes | ||||
| @ -81,12 +81,25 @@ | ||||
| //! temporally) the definition of `c`. (Note that although KCL does not permit mutation, objects | ||||
| //! can change due to the way tags are implemented). | ||||
| //! | ||||
| //! To make this work, when we save a reference to an enclosing scope we take a snapshot of memory at | ||||
| //! that point and save a reference to that snapshot. When we call a function, the parent of the new | ||||
| //! callee env is that snapshot, not the current version of the enclosing scope. | ||||
| //! To make this work, we have the concept of an epoch. An epoch is a simple, global, monotonic counter | ||||
| //! which is incremented at any significant moment in execution (we use the term snapshot). When a | ||||
| //! value is saved in memory we also save the epoch at which it was stored. | ||||
| //! | ||||
| //! Entering an inline scope (e.g., the body of an `if` statement) means pushing an env whose parent | ||||
| //! is the current env. We don't need to snapshot in this case. | ||||
| //! When we save a reference to an enclosing scope we take a snapshot and save that epoch as part of | ||||
| //! the reference. When we call a function, we use the epoch when it was defined to look up variables, | ||||
| //! ignoring any variables which have a creation time later than the saved epoch. | ||||
| //! | ||||
| //! Because the callee could create new variables (with a creation time of the current epoch) which | ||||
| //! the callee should be able to read, we can't simply check the epoch with the callees (and we'd need | ||||
| //! to maintain a stack of callee epochs for further calls, etc.). Instead a stack frame consists of | ||||
| //! a reference to an environment and an epoch at which reads should take place. When we call a function | ||||
| //! this creates a new env using the current epoch, and it's parent env (which is the enclosing scope | ||||
| //! of the function declaration) includes the epoch at which the function was declared. | ||||
| //! | ||||
| //! So far, this handles variables created after a function is declared, but does not handle mutation. | ||||
| //! Mutation must be handled internally in values, see for example `TagIdentifier`. It is suggested | ||||
| //! that objects rely on epochs for this. Since epochs are linked to the stack frame, only objects in | ||||
| //! the current stack frame should be mutated. | ||||
| //! | ||||
| //! ### Std | ||||
| //! | ||||
| @ -107,53 +120,17 @@ | ||||
| //! Pushing and popping stack frames is straightforward. Most get/set/update operations don't touch | ||||
| //! the call stack other than the current env (updating tags on function return is the exception). | ||||
| //! | ||||
| //! Snapshots are maintained within an environment and are always specific to an environment. Snapshots | ||||
| //! must also have a parent reference (since they are logically a snapshot of all memory). This parent | ||||
| //! refers to a snapshot within the parent env. When a snapshot is created, we must create a snapshot | ||||
| //! object for each parent env. When using a snapshot we must check the parent snapshot whenever | ||||
| //! we check the parent env (and not the current version of the parent env). | ||||
| //! | ||||
| //! An environment will have many snapshots, they are kept in time order, and do not reference each | ||||
| //! other. (The parent of a snapshot is always in another env). | ||||
| //! | ||||
| //! A snapshot is created empty (we don't copy memory) and we use a copy-on-write design: when a | ||||
| //! value in an environment is modified, we copy the old version into the most recent snapshot (note | ||||
| //! that we never overwrite a value in the snapshot, if a value is modified multiple times, we want | ||||
| //! to keep the original version, not an intermediate one). Likewise, if we insert a new variable, | ||||
| //! we put a tombstone value in the snapshot. | ||||
| //! | ||||
| //! When we read from the current version of an environment, we simply read from the bindings in the | ||||
| //! env and ignore the snapshots. When we read from a snapshot, we first check the specific snapshot | ||||
| //! for the key, then check any newer snapshots, then finally check the env bindings. | ||||
| //! | ||||
| //! A minor optimisation is that when creating a snapshot, if the previous one is empty, then | ||||
| //! we can reuse that rather than creating a new one. Since we only create a snapshot when a function | ||||
| //! is declared and the function decl is immediately saved into the new snapshot, the empty snapshot | ||||
| //! optimisation only happens with parent snapshots (though if the env tree is deep this means we | ||||
| //! can save a lot of snapshots). | ||||
| //! | ||||
| //! ## Invariants | ||||
| //! | ||||
| //! There's obviously a bunch of invariants in this design, some are kinda obvious, some are limited | ||||
| //! in scope and are documented inline, here are some others: | ||||
| //! | ||||
| //! - The current env and all envs in the call stack are 'just envs', never a snapshot (we could | ||||
| //!   use just a ref to an env, rather than to a snapshot but this is pretty inconvenient, so just | ||||
| //!   know that the snapshot ref is always to the current version). Only the parent envs or saved refs | ||||
| //!   can be refs to snapshots. | ||||
| //! - We only ever write into the current env, never into any parent envs (though we can read from | ||||
| //!   both). | ||||
| //! - Therefore, there is no concept of writing into a snapshot, only reading from one. | ||||
| //! - The env ref saved with a function decl is always to a snapshot, never to the current version. | ||||
| //! - If there are no snapshots in an environment and it is no longer in the call stack, then there | ||||
| //!   are no references from function decls to the env (if it is the parent of an env with extant refs | ||||
| //!   then there would be snapshots in the child env and that implies there must be a snapshot in the | ||||
| //!   parent to be the parent of that snapshot). | ||||
| //! - We only ever write (or mutate) at the most recent epoch, never at an older one. | ||||
| //! - The env ref saved with a function decl is always to an historic epoch, never to the current one. | ||||
| //! - Since KCL does not have submodules and decls are not visible outside of a nested scope, all | ||||
| //!   references to variables in other modules must be in the root scope of a module. | ||||
| //! - Therefore, an active env must either be on the call stack, have snapshots, or be a root env. This | ||||
| //!   is however a conservative approximation since snapshots may exist even if there are no live | ||||
| //!   references to an env. | ||||
| //! | ||||
| //! ## Concurrency and thread-safety | ||||
| //! | ||||
| @ -227,7 +204,6 @@ | ||||
|  | ||||
| use std::{ | ||||
|     cell::UnsafeCell, | ||||
|     collections::HashMap, | ||||
|     fmt, | ||||
|     pin::Pin, | ||||
|     sync::{ | ||||
| @ -267,6 +243,7 @@ pub(crate) struct ProgramMemory { | ||||
|     /// Statistics about the memory, should not be used for anything other than meta-info. | ||||
|     pub(crate) stats: MemoryStats, | ||||
|     next_stack_id: AtomicUsize, | ||||
|     epoch: AtomicUsize, | ||||
|     write_lock: AtomicBool, | ||||
| } | ||||
|  | ||||
| @ -307,7 +284,7 @@ impl fmt::Display for Stack { | ||||
|             .call_stack | ||||
|             .iter() | ||||
|             .chain(Some(&self.current_env)) | ||||
|             .map(|e| format!("EnvRef({}, {})", e.0, e.1 .0)) | ||||
|             .map(|e| format!("EnvRef({}, {})", e.0, e.1)) | ||||
|             .collect(); | ||||
|         write!(f, "Stack {}\nstack frames:\n{}", self.id, stack.join("\n")) | ||||
|     } | ||||
| @ -322,6 +299,7 @@ impl ProgramMemory { | ||||
|             std: None, | ||||
|             stats: MemoryStats::default(), | ||||
|             next_stack_id: AtomicUsize::new(1), | ||||
|             epoch: AtomicUsize::new(1), | ||||
|             write_lock: AtomicBool::new(false), | ||||
|         }) | ||||
|     } | ||||
| @ -340,10 +318,12 @@ impl ProgramMemory { | ||||
|             std: self.std, | ||||
|             stats: MemoryStats::default(), | ||||
|             next_stack_id: AtomicUsize::new(self.next_stack_id.load(Ordering::Relaxed)), | ||||
|             epoch: AtomicUsize::new(self.epoch.load(Ordering::Relaxed)), | ||||
|             write_lock: AtomicBool::new(false), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Create a new stack object referencing this `ProgramMemory`. | ||||
|     pub fn new_stack(self: Arc<Self>) -> Stack { | ||||
|         let id = self.next_stack_id.fetch_add(1, Ordering::Relaxed); | ||||
|         assert!(id > 0); | ||||
| @ -367,7 +347,7 @@ impl ProgramMemory { | ||||
|         self.std.is_none() | ||||
|     } | ||||
|  | ||||
|     /// Get a value from a specific snapshot of the memory. | ||||
|     /// Get a value from a specific environment of the memory at a specific point in time. | ||||
|     pub fn get_from( | ||||
|         &self, | ||||
|         var: &str, | ||||
| @ -438,7 +418,7 @@ impl ProgramMemory { | ||||
|  | ||||
|         let new_env = Environment::new(parent, is_root_env, owner); | ||||
|         self.with_envs(|envs| { | ||||
|             let result = EnvironmentRef(envs.len(), SnapshotRef::none()); | ||||
|             let result = EnvironmentRef(envs.len(), usize::MAX); | ||||
|             // Note this might reallocate, which would hold the `with_envs` spin lock for way too long | ||||
|             // so somehow we should make sure we don't do that (though honestly the chance of that | ||||
|             // happening while another thread is waiting for the lock is pretty small). | ||||
| @ -490,23 +470,12 @@ impl ProgramMemory { | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     #[cfg(test)] | ||||
|     fn update_with_env(&self, key: &str, value: KclValue, env: usize, owner: usize) { | ||||
|         self.stats.mutation_count.fetch_add(1, Ordering::Relaxed); | ||||
|         self.get_env(env).insert_or_update(key.to_owned(), value, owner); | ||||
|     } | ||||
|  | ||||
|     /// Get a value from memory without checking for ownership of the env. | ||||
|     /// | ||||
|     /// This is not safe to use in general and should only be used if you have unique access to | ||||
|     /// the `self` which is generally only true during testing. | ||||
|     #[cfg(test)] | ||||
|     pub fn get_from_unchecked( | ||||
|         &self, | ||||
|         var: &str, | ||||
|         mut env_ref: EnvironmentRef, | ||||
|         source_range: SourceRange, | ||||
|     ) -> Result<&KclValue, KclError> { | ||||
|     pub fn get_from_unchecked(&self, var: &str, mut env_ref: EnvironmentRef) -> Result<&KclValue, KclError> { | ||||
|         loop { | ||||
|             let env = self.get_env(env_ref.index()); | ||||
|             env_ref = match env.get_unchecked(var, env_ref.1) { | ||||
| @ -518,7 +487,7 @@ impl ProgramMemory { | ||||
|  | ||||
|         Err(KclError::UndefinedValue(KclErrorDetails { | ||||
|             message: format!("memory item key `{}` is not defined", var), | ||||
|             source_ranges: vec![source_range], | ||||
|             source_ranges: vec![], | ||||
|         })) | ||||
|     } | ||||
| } | ||||
| @ -544,6 +513,11 @@ impl Stack { | ||||
|         stack | ||||
|     } | ||||
|  | ||||
|     /// Get the current (globally most recent) epoch. | ||||
|     pub fn current_epoch(&self) -> usize { | ||||
|         self.memory.epoch.load(Ordering::Relaxed) | ||||
|     } | ||||
|  | ||||
|     /// Push a new (standard KCL) stack frame on to the call stack. | ||||
|     /// | ||||
|     /// `parent` is the environment where the function being called is declared (not the caller's | ||||
| @ -577,7 +551,7 @@ impl Stack { | ||||
|         // Rust functions shouldn't try to set or access anything in their environment, so don't | ||||
|         // waste time and space on a new env. Using usize::MAX means we'll get an overflow if we | ||||
|         // try to access anything rather than a silent error. | ||||
|         self.current_env = EnvironmentRef(usize::MAX, SnapshotRef::none()); | ||||
|         self.current_env = EnvironmentRef(usize::MAX, 0); | ||||
|     } | ||||
|  | ||||
|     /// Push a new stack frame on to the call stack with no connection to a parent environment. | ||||
| @ -596,7 +570,6 @@ impl Stack { | ||||
|     /// SAFETY: the env must not be being used by another `Stack` since we'll move the env from | ||||
|     /// read-only to owned. | ||||
|     pub fn restore_env(&mut self, env: EnvironmentRef) { | ||||
|         assert!(env.1.is_none()); | ||||
|         self.call_stack.push(self.current_env); | ||||
|         self.memory.get_env(env.index()).restore_owner(self.id); | ||||
|         self.current_env = env; | ||||
| @ -642,25 +615,28 @@ impl Stack { | ||||
|         } | ||||
|  | ||||
|         let mut old_env = self.memory.take_env(old); | ||||
|         if old_env.is_empty() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Map of any old env refs to the current env. | ||||
|         let snapshot_map: HashMap<_, _> = old_env | ||||
|             .snapshot_parents() | ||||
|             .map(|(s, p)| (EnvironmentRef(old.0, s), (EnvironmentRef(self.current_env.0, p)))) | ||||
|             .collect(); | ||||
|  | ||||
|         // Make a new scope so we override variables properly. | ||||
|         self.push_new_env_for_scope(); | ||||
|         // Move the variables in the popped env into the current env. | ||||
|         let env = self.memory.get_env(self.current_env.index()); | ||||
|         for (k, v) in old_env.as_mut().take_bindings() { | ||||
|             env.insert_or_update(k.clone(), v.map_env_ref(&snapshot_map), self.id); | ||||
|         for (k, (e, v)) in old_env.as_mut().take_bindings() { | ||||
|             env.insert(k, e, v.map_env_ref(old.0, self.current_env.0), self.id); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Snapshot the current state of the memory. | ||||
|     pub fn snapshot(&mut self) -> EnvironmentRef { | ||||
|         self.memory.stats.snapshot_count.fetch_add(1, Ordering::Relaxed); | ||||
|         let snapshot = env::snapshot(&self.memory, self.current_env, self.id); | ||||
|         EnvironmentRef(self.current_env.0, snapshot) | ||||
|         self.memory.stats.epoch_count.fetch_add(1, Ordering::Relaxed); | ||||
|  | ||||
|         let env = self.memory.get_env(self.current_env.index()); | ||||
|         env.mark_as_refed(); | ||||
|  | ||||
|         let prev_epoch = self.memory.epoch.fetch_add(1, Ordering::Relaxed); | ||||
|         EnvironmentRef(self.current_env.0, prev_epoch) | ||||
|     } | ||||
|  | ||||
|     /// Add a value to the program memory (in the current scope). The value must not already exist. | ||||
| @ -675,16 +651,21 @@ impl Stack { | ||||
|  | ||||
|         self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed); | ||||
|  | ||||
|         env.insert(key, value, self.id); | ||||
|         env.insert(key, self.memory.epoch.load(Ordering::Relaxed), value, self.id); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn insert_or_update(&mut self, key: String, value: KclValue) { | ||||
|     /// Update a variable in memory. `key` must exist in memory. If it doesn't, this function will panic | ||||
|     /// in debug builds and do nothing in release builds. | ||||
|     pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) { | ||||
|         self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed); | ||||
|         self.memory | ||||
|             .get_env(self.current_env.index()) | ||||
|             .insert_or_update(key, value, self.id); | ||||
|         self.memory.get_env(self.current_env.index()).update( | ||||
|             key, | ||||
|             f, | ||||
|             self.memory.epoch.load(Ordering::Relaxed), | ||||
|             self.id, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /// Get a value from the program memory. | ||||
| @ -693,38 +674,41 @@ impl Stack { | ||||
|         self.memory.get_from(var, self.current_env, source_range, self.id) | ||||
|     } | ||||
|  | ||||
|     /// Whether the current frame of the stack contains a variable with the given name. | ||||
|     pub fn cur_frame_contains(&self, var: &str) -> bool { | ||||
|         let env = self.memory.get_env(self.current_env.index()); | ||||
|         env.contains_key(var) | ||||
|     } | ||||
|  | ||||
|     /// Get a key from the first KCL (i.e., non-Rust) stack frame on the call stack. | ||||
|     pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<&KclValue, KclError> { | ||||
|     pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> { | ||||
|         if !self.current_env.skip_env() { | ||||
|             return self.get(key, source_range); | ||||
|             return Ok((self.current_env.1, self.get(key, source_range)?)); | ||||
|         } | ||||
|  | ||||
|         for env in self.call_stack.iter().rev() { | ||||
|             if !env.skip_env() { | ||||
|                 return self.memory.get_from(key, *env, source_range, self.id); | ||||
|                 return Ok((env.1, self.memory.get_from(key, *env, source_range, self.id)?)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         unreachable!("It can't be Rust frames all the way down"); | ||||
|     } | ||||
|  | ||||
|     /// Iterate over all key/value pairs in the current environment which satisfy the provided | ||||
|     /// predicate. | ||||
|     pub fn find_all_in_current_env<'a>( | ||||
|     /// Iterate over all keys in the current environment which satisfy the provided predicate. | ||||
|     pub fn find_keys_in_current_env<'a>( | ||||
|         &'a self, | ||||
|         pred: impl Fn(&KclValue) -> bool + 'a, | ||||
|     ) -> impl Iterator<Item = (&'a String, &'a KclValue)> { | ||||
|         self.memory.find_all_in_env(self.current_env, pred, self.id) | ||||
|     ) -> impl Iterator<Item = &'a String> { | ||||
|         self.memory | ||||
|             .find_all_in_env(self.current_env, pred, self.id) | ||||
|             .map(|(k, _)| k) | ||||
|     } | ||||
|  | ||||
|     /// Iterate over all key/value pairs in the specified environment which satisfy the provided | ||||
|     /// predicate. `env` must either be read-only or owned by `self`. | ||||
|     pub fn find_all_in_env<'a>( | ||||
|         &'a self, | ||||
|         env: EnvironmentRef, | ||||
|         pred: impl Fn(&KclValue) -> bool + 'a, | ||||
|     ) -> impl Iterator<Item = (&'a String, &'a KclValue)> { | ||||
|         self.memory.find_all_in_env(env, pred, self.id) | ||||
|     pub fn find_all_in_env(&self, env: EnvironmentRef) -> impl Iterator<Item = (&String, &KclValue)> { | ||||
|         self.memory.find_all_in_env(env, |_| true, self.id) | ||||
|     } | ||||
|  | ||||
|     /// Walk all values accessible from any environment in the call stack. | ||||
| @ -781,7 +765,7 @@ impl<'a> Iterator for CallStackIterator<'a> { | ||||
|                     return next; | ||||
|                 } | ||||
|  | ||||
|                 if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent(self.cur_env.1) { | ||||
|                 if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent() { | ||||
|                     self.cur_env = env_ref; | ||||
|                     self.init_iter(); | ||||
|                 } else { | ||||
| @ -816,23 +800,32 @@ impl<'a> Iterator for CallStackIterator<'a> { | ||||
| #[cfg(test)] | ||||
| impl PartialEq for Stack { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         let vars: Vec<_> = self.find_all_in_current_env(|_| true).collect(); | ||||
|         let vars_other: Vec<_> = other.find_all_in_current_env(|_| true).collect(); | ||||
|         vars == vars_other | ||||
|         let vars: Vec<_> = self.find_keys_in_current_env(|_| true).collect(); | ||||
|         let vars_other: Vec<_> = other.find_keys_in_current_env(|_| true).collect(); | ||||
|         if vars != vars_other { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         vars.iter() | ||||
|             .all(|k| self.get(k, SourceRange::default()).unwrap() == other.get(k, SourceRange::default()).unwrap()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs). | ||||
| /// An index pointing to an environment at a point in time. | ||||
| /// | ||||
| /// The first field indexes an environment, the second field is an epoch. An epoch of 0 is indicates | ||||
| /// a dummy, error, or placeholder env ref, an epoch of `usize::MAX` represents the current most | ||||
| /// recent epoch. | ||||
| #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS, JsonSchema)] | ||||
| pub struct EnvironmentRef(usize, SnapshotRef); | ||||
| pub struct EnvironmentRef(usize, usize); | ||||
|  | ||||
| impl EnvironmentRef { | ||||
|     fn dummy() -> Self { | ||||
|         Self(usize::MAX, SnapshotRef(usize::MAX)) | ||||
|         Self(usize::MAX, 0) | ||||
|     } | ||||
|  | ||||
|     fn is_regular(&self) -> bool { | ||||
|         self.0 < usize::MAX && self.1 .0 < usize::MAX | ||||
|         self.0 < usize::MAX && self.1 > 0 | ||||
|     } | ||||
|  | ||||
|     fn index(&self) -> usize { | ||||
| @ -842,33 +835,11 @@ impl EnvironmentRef { | ||||
|     fn skip_env(&self) -> bool { | ||||
|         self.0 == usize::MAX | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// An index pointing to a snapshot within a specific (unspecified) environment. | ||||
| #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS, JsonSchema)] | ||||
| struct SnapshotRef(usize); | ||||
|  | ||||
| impl SnapshotRef { | ||||
|     /// Represents no snapshot, use the current version of the environment. | ||||
|     fn none() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
|  | ||||
|     /// `self` represents a snapshot. | ||||
|     fn is_some(self) -> bool { | ||||
|         self.0 > 0 | ||||
|     } | ||||
|  | ||||
|     /// `self` represents the current version. | ||||
|     fn is_none(self) -> bool { | ||||
|         self.0 == 0 | ||||
|     } | ||||
|  | ||||
|     // Precondition: self.is_some() | ||||
|     fn index(&self) -> usize { | ||||
|         // Note that `0` is a distinguished value meaning 'no snapshot', so the reference value | ||||
|         // is one greater than the index into the list of snapshots. | ||||
|         self.0 - 1 | ||||
|     pub fn replace_env(&mut self, old: usize, new: usize) { | ||||
|         if self.0 == old { | ||||
|             self.0 = new; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -877,8 +848,8 @@ impl SnapshotRef { | ||||
| pub(crate) struct MemoryStats { | ||||
|     // Total number of environments created. | ||||
|     env_count: AtomicUsize, | ||||
|     // Total number of snapshots created. | ||||
|     snapshot_count: AtomicUsize, | ||||
|     // Total number of epochs. | ||||
|     epoch_count: AtomicUsize, | ||||
|     // Total number of values inserted or updated. | ||||
|     mutation_count: AtomicUsize, | ||||
|     // The number of envs we delete when popped from the call stack. | ||||
| @ -900,12 +871,10 @@ mod env { | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     pub(super) struct Environment { | ||||
|         bindings: UnsafeCell<IndexMap<String, KclValue>>, | ||||
|         // invariant: self.parent.is_none() => forall s in self.snapshots: s.parent_snapshot.is_none() | ||||
|         snapshots: UnsafeCell<Vec<Snapshot>>, | ||||
|         bindings: UnsafeCell<IndexMap<String, (usize, KclValue)>>, | ||||
|         // An outer scope, if one exists. | ||||
|         parent: Option<EnvironmentRef>, | ||||
|         is_root_env: bool, | ||||
|         might_be_refed: AtomicBool, | ||||
|         // The id of the `Stack` if this `Environment` is on a call stack. If this is >0 then it may | ||||
|         // only be read or written by that `Stack`; if 0 then the env is read-only. | ||||
|         owner: AtomicUsize, | ||||
| @ -918,9 +887,8 @@ mod env { | ||||
|             assert!(self.owner.load(Ordering::Acquire) == 0); | ||||
|             Self { | ||||
|                 bindings: UnsafeCell::new(self.get_bindings().clone()), | ||||
|                 snapshots: UnsafeCell::new(self.iter_snapshots().cloned().collect()), | ||||
|                 parent: self.parent, | ||||
|                 is_root_env: self.is_root_env, | ||||
|                 might_be_refed: AtomicBool::new(self.might_be_refed.load(Ordering::Acquire)), | ||||
|                 owner: AtomicUsize::new(0), | ||||
|                 _unpin: PhantomPinned, | ||||
|             } | ||||
| @ -931,45 +899,19 @@ mod env { | ||||
|         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|             let parent = self | ||||
|                 .parent | ||||
|                 .map(|e| format!("EnvRef({}, {})", e.0, e.1 .0)) | ||||
|                 .map(|e| format!("EnvRef({}, {})", e.0, e.1)) | ||||
|                 .unwrap_or("_".to_owned()); | ||||
|             let data: Vec<String> = self | ||||
|                 .get_bindings() | ||||
|                 .iter() | ||||
|                 .map(|(k, v)| format!("{k}: {}", v.human_friendly_type())) | ||||
|                 .map(|(k, v)| format!("{k}: {}@{}", v.1.human_friendly_type(), v.0)) | ||||
|                 .collect(); | ||||
|             let snapshots: Vec<String> = self.iter_snapshots().map(|s| s.to_string()).collect(); | ||||
|             write!( | ||||
|                 f, | ||||
|                 "Env {{\n  parent: {parent},\n  owner: {},\n  is root: {},\n  bindings:\n    {},\n  snapshots:\n    {}\n}}", | ||||
|                 "Env {{\n  parent: {parent},\n  owner: {},\n  ref'ed?: {},\n  bindings:\n    {}\n}}", | ||||
|                 self.owner.load(Ordering::Relaxed), | ||||
|                 self.is_root_env, | ||||
|                 self.might_be_refed.load(Ordering::Relaxed), | ||||
|                 data.join("\n    "), | ||||
|                 snapshots.join("\n    ") | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, PartialEq)] | ||||
|     struct Snapshot { | ||||
|         /// The version of the owning environment's parent environment corresponding to this snapshot. | ||||
|         parent_snapshot: Option<SnapshotRef>, | ||||
|         /// CoW'ed data from the environment. | ||||
|         data: IndexMap<String, KclValue>, | ||||
|     } | ||||
|  | ||||
|     impl fmt::Display for Snapshot { | ||||
|         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|             let parent = self.parent_snapshot.map(|s| s.0.to_string()).unwrap_or("_".to_owned()); | ||||
|             let data: Vec<String> = self | ||||
|                 .data | ||||
|                 .iter() | ||||
|                 .map(|(k, v)| format!("{k}: {}", v.human_friendly_type())) | ||||
|                 .collect(); | ||||
|             write!( | ||||
|                 f, | ||||
|                 "Snapshot {{\n      parent: {parent},\n      data: {},\n    }}", | ||||
|                 data.join("\n        ") | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| @ -977,80 +919,47 @@ mod env { | ||||
|     impl Environment { | ||||
|         /// Create a new environment, parent points to it's surrounding lexical scope or the std | ||||
|         /// env if it's a root scope. | ||||
|         pub(super) fn new(parent: Option<EnvironmentRef>, is_root_env: bool, owner: usize) -> Self { | ||||
|         pub(super) fn new(parent: Option<EnvironmentRef>, might_be_refed: bool, owner: usize) -> Self { | ||||
|             assert!(parent.map(|p| p.is_regular()).unwrap_or(true)); | ||||
|             Self { | ||||
|                 bindings: UnsafeCell::new(IndexMap::new()), | ||||
|                 snapshots: UnsafeCell::new(Vec::new()), | ||||
|                 parent, | ||||
|                 is_root_env, | ||||
|                 might_be_refed: AtomicBool::new(might_be_refed), | ||||
|                 owner: AtomicUsize::new(owner), | ||||
|                 _unpin: PhantomPinned, | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Mark this env as read-only (see module docs). | ||||
|         /// Mark this env as read-only (see module docs). | ||||
|         pub(super) fn read_only(&self) { | ||||
|             self.owner.store(0, Ordering::Release); | ||||
|         } | ||||
|  | ||||
|         // Mark this env as owned (see module docs). | ||||
|         /// Mark this env as owned (see module docs). | ||||
|         pub(super) fn restore_owner(&self, owner: usize) { | ||||
|             self.owner.store(owner, Ordering::Release); | ||||
|         } | ||||
|  | ||||
|         // SAFETY: either the owner of the env is on the Rust stack or the env is read-only. | ||||
|         fn snapshots_len(&self) -> usize { | ||||
|             unsafe { self.snapshots.get().as_ref().unwrap().len() } | ||||
|         /// Mark this environment as possibly having external references. | ||||
|         pub(super) fn mark_as_refed(&self) { | ||||
|             self.might_be_refed.store(true, Ordering::Release); | ||||
|         } | ||||
|  | ||||
|         // SAFETY: either the owner of the env is on the Rust stack or the env is read-only. | ||||
|         fn get_shapshot(&self, index: usize) -> &Snapshot { | ||||
|             unsafe { &self.snapshots.get().as_ref().unwrap()[index] } | ||||
|         } | ||||
|  | ||||
|         // SAFETY: either the owner of the env is on the Rust stack or the env is read-only. | ||||
|         fn iter_snapshots(&self) -> impl Iterator<Item = &Snapshot> { | ||||
|             unsafe { self.snapshots.get().as_ref().unwrap().iter() } | ||||
|         } | ||||
|  | ||||
|         fn cur_snapshot(&self, owner: usize) -> Option<&mut Snapshot> { | ||||
|             assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner); | ||||
|             unsafe { self.snapshots.get().as_mut().unwrap().last_mut() } | ||||
|         } | ||||
|  | ||||
|         // SAFETY: either the owner of the env is on the Rust stack or the env is read-only. | ||||
|         fn get_bindings(&self) -> &IndexMap<String, KclValue> { | ||||
|         fn get_bindings(&self) -> &IndexMap<String, (usize, KclValue)> { | ||||
|             unsafe { self.bindings.get().as_ref().unwrap() } | ||||
|         } | ||||
|  | ||||
|         // SAFETY do not call this function while a previous mutable reference is live | ||||
|         #[allow(clippy::mut_from_ref)] | ||||
|         fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, KclValue> { | ||||
|         fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, (usize, KclValue)> { | ||||
|             assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner); | ||||
|             unsafe { self.bindings.get().as_mut().unwrap() } | ||||
|         } | ||||
|  | ||||
|         // True if the env is empty and not a root env. | ||||
|         // True if the env is empty and has no external references. | ||||
|         pub(super) fn is_empty(&self) -> bool { | ||||
|             self.snapshots_len() == 0 && self.get_bindings().is_empty() && !self.is_root_env | ||||
|         } | ||||
|  | ||||
|         fn push_snapshot(&self, parent: Option<SnapshotRef>, owner: usize) -> SnapshotRef { | ||||
|             let env_owner = self.owner.load(Ordering::Acquire); | ||||
|             // The env is read-only, no need to snapshot. | ||||
|             if env_owner == 0 { | ||||
|                 return SnapshotRef::none(); | ||||
|             } | ||||
|             assert!( | ||||
|                 owner > 0 && env_owner == owner, | ||||
|                 "mutating owner: {owner}, env: {self}({env_owner})" | ||||
|             ); | ||||
|             unsafe { | ||||
|                 let snapshots = self.snapshots.get().as_mut().unwrap(); | ||||
|                 snapshots.push(Snapshot::new(parent)); | ||||
|                 SnapshotRef(snapshots.len()) | ||||
|             } | ||||
|             self.get_bindings().is_empty() && !self.might_be_refed.load(Ordering::Acquire) | ||||
|         } | ||||
|  | ||||
|         /// Possibly compress this environment by deleting the memory. | ||||
| @ -1062,116 +971,61 @@ mod env { | ||||
|         /// See module docs for more details. | ||||
|         pub(super) fn compact(&self, owner: usize) { | ||||
|             // Don't compress if there might be a closure or import referencing us. | ||||
|             if self.snapshots_len() != 0 || self.is_root_env { | ||||
|             if self.might_be_refed.load(Ordering::Acquire) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             *self.get_mut_bindings(owner) = IndexMap::new(); | ||||
|         } | ||||
|  | ||||
|         pub(super) fn get( | ||||
|             &self, | ||||
|             key: &str, | ||||
|             snapshot: SnapshotRef, | ||||
|             owner: usize, | ||||
|         ) -> Result<&KclValue, Option<EnvironmentRef>> { | ||||
|         pub(super) fn get(&self, key: &str, epoch: usize, owner: usize) -> Result<&KclValue, Option<EnvironmentRef>> { | ||||
|             let env_owner = self.owner.load(Ordering::Acquire); | ||||
|             assert!(env_owner == 0 || env_owner == owner); | ||||
|  | ||||
|             self.get_unchecked(key, snapshot) | ||||
|             self.get_unchecked(key, epoch) | ||||
|         } | ||||
|  | ||||
|         /// Get a value from memory without checking the env's ownership invariant. Prefer to use `get`. | ||||
|         pub(super) fn get_unchecked( | ||||
|             &self, | ||||
|             key: &str, | ||||
|             snapshot: SnapshotRef, | ||||
|         ) -> Result<&KclValue, Option<EnvironmentRef>> { | ||||
|             if snapshot.is_some() { | ||||
|                 for i in snapshot.index()..self.snapshots_len() { | ||||
|                     match self.get_shapshot(i).data.get(key) { | ||||
|                         Some(KclValue::Tombstone { .. }) => return Err(self.parent(snapshot)), | ||||
|                         Some(v) => return Ok(v), | ||||
|                         None => {} | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         pub(super) fn get_unchecked(&self, key: &str, epoch: usize) -> Result<&KclValue, Option<EnvironmentRef>> { | ||||
|             self.get_bindings() | ||||
|                 .get(key) | ||||
|                 .and_then(|v| match v { | ||||
|                     KclValue::Tombstone { .. } => None, | ||||
|                     _ => Some(v), | ||||
|                 }) | ||||
|                 .ok_or(self.parent(snapshot)) | ||||
|                 .and_then(|(e, v)| if *e <= epoch { Some(v) } else { None }) | ||||
|                 .ok_or(self.parent) | ||||
|         } | ||||
|  | ||||
|         /// Find the `EnvironmentRef` of the parent of this environment corresponding to the specified snapshot. | ||||
|         pub(super) fn parent(&self, snapshot: SnapshotRef) -> Option<EnvironmentRef> { | ||||
|             if snapshot.is_none() { | ||||
|                 return self.parent; | ||||
|             } | ||||
|         pub(super) fn update(&self, key: &str, f: impl Fn(&mut KclValue, usize), epoch: usize, owner: usize) { | ||||
|             let Some((_, value)) = self.get_mut_bindings(owner).get_mut(key) else { | ||||
|                 debug_assert!(false, "Missing memory entry for {key}"); | ||||
|                 return; | ||||
|             }; | ||||
|  | ||||
|             match self.get_shapshot(snapshot.index()).parent_snapshot { | ||||
|                 Some(sr) => Some(EnvironmentRef(self.parent.unwrap().0, sr)), | ||||
|                 None => self.parent, | ||||
|             } | ||||
|             f(value, epoch); | ||||
|         } | ||||
|  | ||||
|         /// Iterate over all values in the environment at the specified snapshot. | ||||
|         pub(super) fn values<'a>(&'a self, snapshot: SnapshotRef) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> { | ||||
|             if snapshot.is_none() { | ||||
|                 return Box::new(self.get_bindings().values()); | ||||
|             } | ||||
|         pub(super) fn parent(&self) -> Option<EnvironmentRef> { | ||||
|             self.parent | ||||
|         } | ||||
|  | ||||
|         /// Iterate over all values in the environment at the specified epoch. | ||||
|         pub(super) fn values<'a>(&'a self, epoch: usize) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> { | ||||
|             Box::new( | ||||
|                 self.get_bindings() | ||||
|                     .iter() | ||||
|                     .filter_map(move |(k, v)| { | ||||
|                         (!self.snapshot_contains_key(k, snapshot) && !matches!(v, KclValue::Tombstone { .. })) | ||||
|                             .then_some(v) | ||||
|                     }) | ||||
|                     .chain( | ||||
|                         self.iter_snapshots() | ||||
|                             .flat_map(|s| s.data.values().filter(|v| !matches!(v, KclValue::Tombstone { .. }))), | ||||
|                     ), | ||||
|                     .values() | ||||
|                     .filter_map(move |(e, v)| (*e <= epoch).then_some(v)), | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         /// Pure insert, panics if `key` is already in this environment. | ||||
|         /// | ||||
|         /// Precondition: !self.contains_key(key) | ||||
|         pub(super) fn insert(&self, key: String, value: KclValue, owner: usize) { | ||||
|         pub(super) fn insert(&self, key: String, epoch: usize, value: KclValue, owner: usize) { | ||||
|             debug_assert!(!self.get_bindings().contains_key(&key)); | ||||
|             if let Some(s) = self.cur_snapshot(owner) { | ||||
|                 s.data.insert(key.clone(), tombstone()); | ||||
|             } | ||||
|             self.get_mut_bindings(owner).insert(key, value); | ||||
|         } | ||||
|  | ||||
|         pub(super) fn insert_or_update(&self, key: String, value: KclValue, owner: usize) { | ||||
|             if let Some(s) = self.cur_snapshot(owner) { | ||||
|                 if !s.data.contains_key(&key) { | ||||
|                     let old_value = self.get_bindings().get(&key).cloned().unwrap_or_else(tombstone); | ||||
|                     s.data.insert(key.clone(), old_value); | ||||
|                 } | ||||
|             } | ||||
|             self.get_mut_bindings(owner).insert(key, value); | ||||
|         } | ||||
|  | ||||
|         /// Was the key contained in this environment at the specified point in time. | ||||
|         fn snapshot_contains_key(&self, key: &str, snapshot: SnapshotRef) -> bool { | ||||
|             for i in snapshot.index()..self.snapshots_len() { | ||||
|                 if self.get_shapshot(i).data.contains_key(key) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             false | ||||
|             self.get_mut_bindings(owner).insert(key, (epoch, value)); | ||||
|         } | ||||
|  | ||||
|         /// Is the key currently contained in this environment. | ||||
|         pub(super) fn contains_key(&self, key: &str) -> bool { | ||||
|             !matches!(self.get_bindings().get(key), Some(KclValue::Tombstone { .. }) | None) | ||||
|             self.get_bindings().contains_key(key) | ||||
|         } | ||||
|  | ||||
|         /// Iterate over all key/value pairs currently in this environment where the value satisfies | ||||
| @ -1186,61 +1040,14 @@ mod env { | ||||
|  | ||||
|             self.get_bindings() | ||||
|                 .iter() | ||||
|                 .filter(move |(_, v)| f(v) && !matches!(v, KclValue::Tombstone { .. })) | ||||
|                 .filter_map(move |(k, (_, v))| f(v).then_some((k, v))) | ||||
|         } | ||||
|  | ||||
|         /// Take all bindings from the environment. | ||||
|         pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, KclValue)> { | ||||
|         pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, (usize, KclValue))> { | ||||
|             // SAFETY: caller must have unique access since self is mut. We're not moving or invalidating `self`. | ||||
|             let bindings = std::mem::take(unsafe { self.bindings.get().as_mut().unwrap() }); | ||||
|             bindings | ||||
|                 .into_iter() | ||||
|                 .filter(move |(_, v)| !matches!(v, KclValue::Tombstone { .. })) | ||||
|         } | ||||
|  | ||||
|         /// Returns an iterator over any snapshots in this environment, returning the ref to the | ||||
|         /// snapshot and its parent. | ||||
|         pub(super) fn snapshot_parents(&self) -> impl Iterator<Item = (SnapshotRef, SnapshotRef)> + '_ { | ||||
|             self.iter_snapshots() | ||||
|                 .enumerate() | ||||
|                 .map(|(i, s)| (SnapshotRef(i + 1), s.parent_snapshot.unwrap())) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Snapshot { | ||||
|         fn new(parent_snapshot: Option<SnapshotRef>) -> Self { | ||||
|             Snapshot { | ||||
|                 parent_snapshot, | ||||
|                 data: IndexMap::new(), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Build a new snapshot of the specified environment at the current moment. | ||||
|     /// | ||||
|     /// This is non-trival since we have to build the tree of parent snapshots. | ||||
|     pub(super) fn snapshot(mem: &ProgramMemory, env_ref: EnvironmentRef, owner: usize) -> SnapshotRef { | ||||
|         let env = mem.get_env(env_ref.index()); | ||||
|         let parent_snapshot = env.parent.map(|p| snapshot(mem, p, owner)); | ||||
|  | ||||
|         let env = mem.get_env(env_ref.index()); | ||||
|         if env.snapshots_len() == 0 { | ||||
|             return env.push_snapshot(parent_snapshot, owner); | ||||
|         } | ||||
|  | ||||
|         let prev_snapshot = env.cur_snapshot(owner).unwrap(); | ||||
|         if prev_snapshot.data.is_empty() && prev_snapshot.parent_snapshot == parent_snapshot { | ||||
|             // If the prev snapshot is empty, reuse it. | ||||
|             return SnapshotRef(env.snapshots_len()); | ||||
|         } | ||||
|  | ||||
|         env.push_snapshot(parent_snapshot, owner) | ||||
|     } | ||||
|  | ||||
|     fn tombstone() -> KclValue { | ||||
|         KclValue::Tombstone { | ||||
|             value: (), | ||||
|             meta: Vec::new(), | ||||
|             bindings.into_iter() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1270,16 +1077,9 @@ mod test { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn expect_small_number(value: &KclValue) -> Option<i64> { | ||||
|         match value { | ||||
|             KclValue::Number { value, .. } if value > &0.0 && value < &10.0 => Some(*value as i64), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[track_caller] | ||||
|     fn assert_get_from(mem: &Stack, key: &str, n: i64, snapshot: EnvironmentRef) { | ||||
|         match mem.memory.get_from_unchecked(key, snapshot, sr()).unwrap() { | ||||
|         match mem.memory.get_from_unchecked(key, snapshot).unwrap() { | ||||
|             KclValue::Number { value, .. } => assert_eq!(*value as i64, n), | ||||
|             _ => unreachable!(), | ||||
|         } | ||||
| @ -1318,7 +1118,7 @@ mod test { | ||||
|         assert_get(mem, "a", 1); | ||||
|         mem.add("b".to_owned(), val(3), sr()).unwrap(); | ||||
|         assert_get(mem, "b", 3); | ||||
|         mem.memory.get_from_unchecked("b", sn, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("b", sn).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @ -1337,11 +1137,11 @@ mod test { | ||||
|         assert_get(mem, "b", 3); | ||||
|         assert_get(mem, "c", 6); | ||||
|         assert_get_from(mem, "a", 1, sn1); | ||||
|         mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("c", sn1, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("b", sn1).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("c", sn1).unwrap_err(); | ||||
|         assert_get_from(mem, "a", 1, sn2); | ||||
|         assert_get_from(mem, "b", 3, sn2); | ||||
|         mem.memory.get_from_unchecked("c", sn2, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("c", sn2).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @ -1481,7 +1281,7 @@ mod test { | ||||
|  | ||||
|         mem.pop_env(); | ||||
|         // old snapshot still untouched | ||||
|         mem.memory.get_from_unchecked("b", sn, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("b", sn).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @ -1503,62 +1303,22 @@ mod test { | ||||
|  | ||||
|         mem.pop_env(); | ||||
|         // old snapshots still untouched | ||||
|         mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("b", sn1).unwrap_err(); | ||||
|         assert_get_from(mem, "b", 3, sn2); | ||||
|         mem.memory.get_from_unchecked("c", sn2, sr()).unwrap_err(); | ||||
|         mem.memory.get_from_unchecked("c", sn2).unwrap_err(); | ||||
|         assert_get_from(mem, "b", 4, sn3); | ||||
|         mem.memory.get_from_unchecked("c", sn3, sr()).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn snap_env_two_updates() { | ||||
|         let mem = &mut Stack::new_for_tests(); | ||||
|         mem.add("a".to_owned(), val(1), sr()).unwrap(); | ||||
|  | ||||
|         let sn1 = mem.snapshot(); | ||||
|         mem.add("b".to_owned(), val(3), sr()).unwrap(); | ||||
|         let sn2 = mem.snapshot(); | ||||
|  | ||||
|         let callee_env = mem.current_env.0; | ||||
|         mem.push_new_env_for_call(sn2); | ||||
|         let sn3 = mem.snapshot(); | ||||
|         mem.add("b".to_owned(), val(4), sr()).unwrap(); | ||||
|         let sn4 = mem.snapshot(); | ||||
|         mem.insert_or_update("b".to_owned(), val(6)); | ||||
|         mem.memory.update_with_env("b", val(7), callee_env, mem.id); | ||||
|  | ||||
|         assert_get(mem, "b", 6); | ||||
|         assert_get_from(mem, "b", 3, sn3); | ||||
|         assert_get_from(mem, "b", 4, sn4); | ||||
|  | ||||
|         let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect(); | ||||
|         let expected = [6, 1, 3, 1, 7]; | ||||
|         assert_eq!(vals, expected); | ||||
|  | ||||
|         let popped = mem.pop_env(); | ||||
|         assert_get(mem, "b", 7); | ||||
|         mem.memory.get_from_unchecked("b", sn1, sr()).unwrap_err(); | ||||
|         assert_get_from(mem, "b", 3, sn2); | ||||
|  | ||||
|         let vals: Vec<_> = mem.walk_call_stack().filter_map(expect_small_number).collect(); | ||||
|         let expected = [1, 7]; | ||||
|         assert_eq!(vals, expected); | ||||
|  | ||||
|         let popped_env = mem.memory.get_env(popped.index()); | ||||
|         let sp: Vec<_> = popped_env.snapshot_parents().collect(); | ||||
|         assert_eq!( | ||||
|             sp, | ||||
|             vec![(SnapshotRef(1), SnapshotRef(2)), (SnapshotRef(2), SnapshotRef(2))] | ||||
|         ); | ||||
|         mem.memory.get_from_unchecked("c", sn3).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn squash_env() { | ||||
|         let mem = &mut Stack::new_for_tests(); | ||||
|         mem.add("a".to_owned(), val(1), sr()).unwrap(); | ||||
|         mem.add("b".to_owned(), val(3), sr()).unwrap(); | ||||
|         let sn1 = mem.snapshot(); | ||||
|         mem.push_new_env_for_call(sn1); | ||||
|         mem.add("b".to_owned(), val(2), sr()).unwrap(); | ||||
|  | ||||
|         let sn2 = mem.snapshot(); | ||||
|         mem.add( | ||||
|             "f".to_owned(), | ||||
| @ -1581,11 +1341,10 @@ mod test { | ||||
|             KclValue::Function { | ||||
|                 value: FunctionSource::User { memory, .. }, | ||||
|                 .. | ||||
|             } if memory == &sn1 => {} | ||||
|             v => panic!("{v:#?}"), | ||||
|             } if memory.0 == mem.current_env.0 => {} | ||||
|             v => panic!("{v:#?}, expected {sn1:?}"), | ||||
|         } | ||||
|         assert_eq!(mem.memory.envs().len(), 1); | ||||
|         assert_eq!(mem.current_env, EnvironmentRef(0, SnapshotRef(0))); | ||||
|         assert_eq!(mem.memory.envs().len(), 2); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|  | ||||
| @ -10,6 +10,7 @@ use cache::OldAstState; | ||||
| pub use cache::{bust_cache, clear_mem_cache}; | ||||
| pub use cad_op::Operation; | ||||
| pub use geometry::*; | ||||
| pub use id_generator::IdGenerator; | ||||
| pub(crate) use import::{ | ||||
|     import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM, | ||||
| }; | ||||
| @ -25,7 +26,7 @@ use kittycad_modeling_cmds as kcmc; | ||||
| pub use memory::EnvironmentRef; | ||||
| use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| pub use state::{ExecState, IdGenerator, MetaSettings}; | ||||
| pub use state::{ExecState, MetaSettings}; | ||||
|  | ||||
| use crate::{ | ||||
|     engine::EngineManager, | ||||
| @ -49,6 +50,7 @@ pub(crate) mod cache; | ||||
| mod cad_op; | ||||
| mod exec_ast; | ||||
| mod geometry; | ||||
| mod id_generator; | ||||
| mod import; | ||||
| pub(crate) mod kcl_value; | ||||
| mod memory; | ||||
| @ -72,6 +74,8 @@ pub struct ExecOutcome { | ||||
|     pub errors: Vec<CompilationError>, | ||||
|     /// File Names in module Id array index order | ||||
|     pub filenames: IndexMap<ModuleId, ModulePath>, | ||||
|     /// The default planes. | ||||
|     pub default_planes: Option<DefaultPlanes>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| @ -91,11 +95,46 @@ pub struct DefaultPlanes { | ||||
| #[serde(tag = "type", rename_all = "camelCase")] | ||||
| pub struct TagIdentifier { | ||||
|     pub value: String, | ||||
|     pub info: Option<TagEngineInfo>, | ||||
|     // Multi-version representation of info about the tag. Kept ordered. The usize is the epoch at which the info | ||||
|     // was written. Note that there might be multiple versions of tag info from the same epoch, the version with | ||||
|     // the higher index will be the most recent. | ||||
|     #[serde(skip)] | ||||
|     pub info: Vec<(usize, TagEngineInfo)>, | ||||
|     #[serde(skip)] | ||||
|     pub meta: Vec<Metadata>, | ||||
| } | ||||
|  | ||||
| impl TagIdentifier { | ||||
|     /// Get the tag info for this tag at a specified epoch. | ||||
|     pub fn get_info(&self, at_epoch: usize) -> Option<&TagEngineInfo> { | ||||
|         for (e, info) in self.info.iter().rev() { | ||||
|             if *e <= at_epoch { | ||||
|                 return Some(info); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     /// Get the most recent tag info for this tag. | ||||
|     pub fn get_cur_info(&self) -> Option<&TagEngineInfo> { | ||||
|         self.info.last().map(|i| &i.1) | ||||
|     } | ||||
|  | ||||
|     /// Add info from a different instance of this tag. | ||||
|     pub fn merge_info(&mut self, other: &TagIdentifier) { | ||||
|         assert_eq!(&self.value, &other.value); | ||||
|         'new_info: for (oe, ot) in &other.info { | ||||
|             for (e, _) in &self.info { | ||||
|                 if e > oe { | ||||
|                     continue 'new_info; | ||||
|                 } | ||||
|             } | ||||
|             self.info.push((*oe, ot.clone())); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Eq for TagIdentifier {} | ||||
|  | ||||
| impl std::fmt::Display for TagIdentifier { | ||||
| @ -110,7 +149,7 @@ impl std::str::FromStr for TagIdentifier { | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(Self { | ||||
|             value: s.to_string(), | ||||
|             info: None, | ||||
|             info: Vec::new(), | ||||
|             meta: Default::default(), | ||||
|         }) | ||||
|     } | ||||
| @ -367,22 +406,14 @@ impl ExecutorContext { | ||||
|     } | ||||
|  | ||||
|     #[cfg(target_arch = "wasm32")] | ||||
|     pub async fn new( | ||||
|         engine_manager: crate::engine::conn_wasm::EngineCommandManager, | ||||
|         fs_manager: crate::fs::wasm::FileSystemManager, | ||||
|         settings: ExecutorSettings, | ||||
|     ) -> Result<Self, String> { | ||||
|         Ok(ExecutorContext { | ||||
|             engine: Arc::new(Box::new( | ||||
|                 crate::engine::conn_wasm::EngineConnection::new(engine_manager) | ||||
|                     .await | ||||
|                     .map_err(|e| format!("{:?}", e))?, | ||||
|             )), | ||||
|             fs: Arc::new(FileManager::new(fs_manager)), | ||||
|     pub fn new(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self { | ||||
|         ExecutorContext { | ||||
|             engine, | ||||
|             fs, | ||||
|             stdlib: Arc::new(StdLib::new()), | ||||
|             settings, | ||||
|             context_type: ContextType::Live, | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
| @ -499,7 +530,7 @@ impl ExecutorContext { | ||||
|         source_range: crate::execution::SourceRange, | ||||
|     ) -> Result<(), KclError> { | ||||
|         self.engine | ||||
|             .clear_scene(&mut exec_state.global.id_generator, source_range) | ||||
|             .clear_scene(&mut exec_state.mod_local.id_generator, source_range) | ||||
|             .await | ||||
|     } | ||||
|  | ||||
| @ -518,7 +549,7 @@ impl ExecutorContext { | ||||
|     ) -> Result<ExecOutcome, KclErrorWithOutputs> { | ||||
|         assert!(self.is_mock()); | ||||
|  | ||||
|         let mut exec_state = ExecState::new(&self.settings); | ||||
|         let mut exec_state = ExecState::new(self); | ||||
|         if use_prev_memory { | ||||
|             match cache::read_old_memory().await { | ||||
|                 Some(mem) => *exec_state.mut_stack() = mem, | ||||
| @ -539,7 +570,7 @@ impl ExecutorContext { | ||||
|         // memory, not to the exec_state which is not cached for mock execution. | ||||
|  | ||||
|         let mut mem = exec_state.stack().clone(); | ||||
|         let outcome = exec_state.to_mock_wasm_outcome(result.0); | ||||
|         let outcome = exec_state.to_mock_wasm_outcome(result.0).await; | ||||
|  | ||||
|         mem.squash_env(result.0); | ||||
|         cache::write_old_memory(mem).await; | ||||
| @ -607,13 +638,13 @@ impl ExecutorContext { | ||||
|                         }) | ||||
|                         .await; | ||||
|  | ||||
|                         let outcome = old_state.to_wasm_outcome(result_env); | ||||
|                         let outcome = old_state.to_wasm_outcome(result_env).await; | ||||
|                         return Ok(outcome); | ||||
|                     } | ||||
|                     (true, program) | ||||
|                 } | ||||
|                 CacheResult::NoAction(false) => { | ||||
|                     let outcome = old_state.to_wasm_outcome(result_env); | ||||
|                     let outcome = old_state.to_wasm_outcome(result_env).await; | ||||
|                     return Ok(outcome); | ||||
|                 } | ||||
|             }; | ||||
| @ -621,7 +652,7 @@ impl ExecutorContext { | ||||
|             let (exec_state, preserve_mem) = if clear_scene { | ||||
|                 // Pop the execution state, since we are starting fresh. | ||||
|                 let mut exec_state = old_state; | ||||
|                 exec_state.reset(&self.settings); | ||||
|                 exec_state.reset(self); | ||||
|  | ||||
|                 // We don't do this in mock mode since there is no engine connection | ||||
|                 // anyways and from the TS side we override memory and don't want to clear it. | ||||
| @ -638,7 +669,7 @@ impl ExecutorContext { | ||||
|  | ||||
|             (program, exec_state, preserve_mem) | ||||
|         } else { | ||||
|             let mut exec_state = ExecState::new(&self.settings); | ||||
|             let mut exec_state = ExecState::new(self); | ||||
|             self.send_clear_scene(&mut exec_state, Default::default()) | ||||
|                 .await | ||||
|                 .map_err(KclErrorWithOutputs::no_outputs)?; | ||||
| @ -663,7 +694,7 @@ impl ExecutorContext { | ||||
|         }) | ||||
|         .await; | ||||
|  | ||||
|         let outcome = exec_state.to_wasm_outcome(result.0); | ||||
|         let outcome = exec_state.to_wasm_outcome(result.0).await; | ||||
|         Ok(outcome) | ||||
|     } | ||||
|  | ||||
| @ -699,6 +730,7 @@ impl ExecutorContext { | ||||
|             .await | ||||
|             .map_err(KclErrorWithOutputs::no_outputs)?; | ||||
|  | ||||
|         let default_planes = self.engine.get_default_planes().read().await.clone(); | ||||
|         let env_ref = self | ||||
|             .execute_and_build_graph(&program.ast, exec_state, preserve_mem) | ||||
|             .await | ||||
| @ -717,6 +749,7 @@ impl ExecutorContext { | ||||
|                     exec_state.global.artifact_graph.clone(), | ||||
|                     module_id_to_module_path, | ||||
|                     exec_state.global.id_to_source.clone(), | ||||
|                     default_planes, | ||||
|                 ) | ||||
|             })?; | ||||
|  | ||||
| @ -754,6 +787,7 @@ impl ExecutorContext { | ||||
|                 exec_state, | ||||
|                 ExecutionKind::Normal, | ||||
|                 preserve_mem, | ||||
|                 ModuleId::default(), | ||||
|                 &ModulePath::Main, | ||||
|             ) | ||||
|             .await; | ||||
| @ -933,7 +967,7 @@ pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclErro | ||||
|         settings: Default::default(), | ||||
|         context_type: ContextType::Mock, | ||||
|     }; | ||||
|     let mut exec_state = ExecState::new(&exec_ctxt.settings); | ||||
|     let mut exec_state = ExecState::new(&exec_ctxt); | ||||
|     let result = exec_ctxt.run(&program, &mut exec_state).await?; | ||||
|  | ||||
|     Ok(ExecTestResults { | ||||
| @ -963,11 +997,7 @@ mod tests { | ||||
|     /// Convenience function to get a JSON value from memory and unwrap. | ||||
|     #[track_caller] | ||||
|     fn mem_get_json(memory: &Stack, env: EnvironmentRef, name: &str) -> KclValue { | ||||
|         memory | ||||
|             .memory | ||||
|             .get_from_unchecked(name, env, SourceRange::default()) | ||||
|             .unwrap() | ||||
|             .to_owned() | ||||
|         memory.memory.get_from_unchecked(name, env).unwrap().to_owned() | ||||
|     } | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
| @ -1849,15 +1879,6 @@ let w = f() + f() | ||||
|         parse_execute(ast).await.unwrap(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_serialize_memory_item() { | ||||
|         let mem = KclValue::Solids { | ||||
|             value: Default::default(), | ||||
|         }; | ||||
|         let json = serde_json::to_string(&mem).unwrap(); | ||||
|         assert_eq!(json, r#"{"type":"Solids","value":[]}"#); | ||||
|     } | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn kcl_test_ids_stable_between_executions() { | ||||
|         let code = r#"sketch001 = startSketchOn(XZ) | ||||
| @ -1880,10 +1901,14 @@ let w = f() + f() | ||||
|         let old_program = crate::Program::parse_no_errs(code).unwrap(); | ||||
|  | ||||
|         // Execute the program. | ||||
|         ctx.run_with_caching(old_program).await.unwrap(); | ||||
|         if let Err(err) = ctx.run_with_caching(old_program).await { | ||||
|             let report = err.into_miette_report_with_outputs(code).unwrap(); | ||||
|             let report = miette::Report::new(report); | ||||
|             panic!("Error executing program: {:?}", report); | ||||
|         } | ||||
|  | ||||
|         // Get the id_generator from the first execution. | ||||
|         let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator; | ||||
|         let id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator; | ||||
|  | ||||
|         let code = r#"sketch001 = startSketchOn(XZ) | ||||
| |> startProfileAt([62.74, 206.13], %) | ||||
| @ -1904,7 +1929,7 @@ let w = f() + f() | ||||
|         // Execute the program. | ||||
|         ctx.run_with_caching(program).await.unwrap(); | ||||
|  | ||||
|         let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator; | ||||
|         let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator; | ||||
|  | ||||
|         assert_eq!(id_generator, new_id_generator); | ||||
|     } | ||||
| @ -1933,7 +1958,6 @@ let w = f() + f() | ||||
|         // Execute the program. | ||||
|         ctx.run_with_caching(old_program.clone()).await.unwrap(); | ||||
|  | ||||
|         // Get the id_generator from the first execution. | ||||
|         let settings_state = cache::read_old_ast().await.unwrap().settings; | ||||
|  | ||||
|         // Ensure the settings are as expected. | ||||
| @ -1945,7 +1969,6 @@ let w = f() + f() | ||||
|         // Execute the program. | ||||
|         ctx.run_with_caching(old_program.clone()).await.unwrap(); | ||||
|  | ||||
|         // Get the id_generator from the first execution. | ||||
|         let settings_state = cache::read_old_ast().await.unwrap().settings; | ||||
|  | ||||
|         // Ensure the settings are as expected. | ||||
| @ -1957,7 +1980,6 @@ let w = f() + f() | ||||
|         // Execute the program. | ||||
|         ctx.run_with_caching(old_program).await.unwrap(); | ||||
|  | ||||
|         // Get the id_generator from the first execution. | ||||
|         let settings_state = cache::read_old_ast().await.unwrap().settings; | ||||
|  | ||||
|         // Ensure the settings are as expected. | ||||
| @ -1976,4 +1998,41 @@ let w = f() + f() | ||||
|         let result = ctx2.run_mock(program2, true).await.unwrap(); | ||||
|         assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0); | ||||
|     } | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn read_tag_version() { | ||||
|         let ast = r#"fn bar(t) { | ||||
|   return startSketchOn(XY) | ||||
|     |> startProfileAt([0,0], %) | ||||
|     |> angledLine({ | ||||
|         angle = -60, | ||||
|         length = segLen(t), | ||||
|     }, %) | ||||
|     |> line(end = [0, 0]) | ||||
|     |> close() | ||||
| } | ||||
|    | ||||
| sketch = startSketchOn(XY) | ||||
|   |> startProfileAt([0,0], %) | ||||
|   |> line(end = [0, 10]) | ||||
|   |> line(end = [10, 0], tag = $tag0) | ||||
|   |> line(end = [0, 0]) | ||||
|  | ||||
| fn foo() { | ||||
|   // tag0 tags an edge | ||||
|   return bar(tag0) | ||||
| } | ||||
|  | ||||
| solid = sketch |> extrude(length = 10) | ||||
| // tag0 tags a face | ||||
| sketch2 = startSketchOn(solid, tag0) | ||||
|   |> startProfileAt([0,0], %) | ||||
|   |> line(end = [0, 1]) | ||||
|   |> line(end = [1, 0]) | ||||
|   |> line(end = [0, 0]) | ||||
|  | ||||
| foo() |> extrude(length = 1) | ||||
| "#; | ||||
|         parse_execute(ast).await.unwrap(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -10,7 +10,9 @@ use uuid::Uuid; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails, Severity}, | ||||
|     execution::{ | ||||
|         annotations, kcl_value, | ||||
|         annotations, | ||||
|         id_generator::IdGenerator, | ||||
|         kcl_value, | ||||
|         memory::{ProgramMemory, Stack}, | ||||
|         Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, | ||||
|         Operation, UnitAngle, UnitLen, | ||||
| @ -26,12 +28,11 @@ use crate::{ | ||||
| pub struct ExecState { | ||||
|     pub(super) global: GlobalState, | ||||
|     pub(super) mod_local: ModuleState, | ||||
|     pub(super) exec_context: Option<super::ExecutorContext>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub(super) struct GlobalState { | ||||
|     /// The stable artifact ID generator. | ||||
|     pub id_generator: IdGenerator, | ||||
|     /// Map from source file absolute path to module ID. | ||||
|     pub path_to_source_id: IndexMap<ModulePath, ModuleId>, | ||||
|     /// Map from module ID to source file. | ||||
| @ -62,6 +63,8 @@ pub(super) struct GlobalState { | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub(super) struct ModuleState { | ||||
|     /// The id generator for this module. | ||||
|     pub id_generator: IdGenerator, | ||||
|     pub stack: Stack, | ||||
|     /// The current value of the pipe operator returned from the previous | ||||
|     /// expression.  If we're not currently in a pipeline, this will be None. | ||||
| @ -73,25 +76,21 @@ pub(super) struct ModuleState { | ||||
| } | ||||
|  | ||||
| impl ExecState { | ||||
|     pub fn new(exec_settings: &ExecutorSettings) -> Self { | ||||
|     pub fn new(exec_context: &super::ExecutorContext) -> Self { | ||||
|         ExecState { | ||||
|             global: GlobalState::new(exec_settings), | ||||
|             mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()), | ||||
|             global: GlobalState::new(&exec_context.settings), | ||||
|             mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()), | ||||
|             exec_context: Some(exec_context.clone()), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) fn reset(&mut self, exec_settings: &ExecutorSettings) { | ||||
|         let mut id_generator = self.global.id_generator.clone(); | ||||
|         // We do not pop the ids, since we want to keep the same id generator. | ||||
|         // This is for the front end to keep track of the ids. | ||||
|         id_generator.next_id = 0; | ||||
|  | ||||
|         let mut global = GlobalState::new(exec_settings); | ||||
|         global.id_generator = id_generator; | ||||
|     pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) { | ||||
|         let global = GlobalState::new(&exec_context.settings); | ||||
|  | ||||
|         *self = ExecState { | ||||
|             global, | ||||
|             mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()), | ||||
|             mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()), | ||||
|             exec_context: Some(exec_context.clone()), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @ -113,13 +112,13 @@ impl ExecState { | ||||
|     /// Convert to execution outcome when running in WebAssembly.  We want to | ||||
|     /// reduce the amount of data that crosses the WASM boundary as much as | ||||
|     /// possible. | ||||
|     pub fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome { | ||||
|     pub async fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome { | ||||
|         // Fields are opt-in so that we don't accidentally leak private internal | ||||
|         // state when we add more to ExecState. | ||||
|         ExecOutcome { | ||||
|             variables: self | ||||
|                 .stack() | ||||
|                 .find_all_in_env(main_ref, |_| true) | ||||
|                 .find_all_in_env(main_ref) | ||||
|                 .map(|(k, v)| (k.clone(), v.clone())) | ||||
|                 .collect(), | ||||
|             operations: self.global.operations, | ||||
| @ -132,16 +131,21 @@ impl ExecState { | ||||
|                 .iter() | ||||
|                 .map(|(k, v)| ((*v), k.clone())) | ||||
|                 .collect(), | ||||
|             default_planes: if let Some(ctx) = &self.exec_context { | ||||
|                 ctx.engine.get_default_planes().read().await.clone() | ||||
|             } else { | ||||
|                 None | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome { | ||||
|     pub async fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome { | ||||
|         // Fields are opt-in so that we don't accidentally leak private internal | ||||
|         // state when we add more to ExecState. | ||||
|         ExecOutcome { | ||||
|             variables: self | ||||
|                 .stack() | ||||
|                 .find_all_in_env(main_ref, |_| true) | ||||
|                 .find_all_in_env(main_ref) | ||||
|                 .map(|(k, v)| (k.clone(), v.clone())) | ||||
|                 .collect(), | ||||
|             operations: Default::default(), | ||||
| @ -149,6 +153,11 @@ impl ExecState { | ||||
|             artifact_graph: Default::default(), | ||||
|             errors: self.global.errors, | ||||
|             filenames: Default::default(), | ||||
|             default_planes: if let Some(ctx) = &self.exec_context { | ||||
|                 ctx.engine.get_default_planes().read().await.clone() | ||||
|             } else { | ||||
|                 None | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -160,8 +169,12 @@ impl ExecState { | ||||
|         &mut self.mod_local.stack | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn next_uuid(&mut self) -> Uuid { | ||||
|         self.global.id_generator.next_uuid() | ||||
|     pub fn next_uuid(&mut self) -> Uuid { | ||||
|         self.mod_local.id_generator.next_uuid() | ||||
|     } | ||||
|  | ||||
|     pub fn id_generator(&mut self) -> &mut IdGenerator { | ||||
|         &mut self.mod_local.id_generator | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn add_artifact(&mut self, artifact: Artifact) { | ||||
| @ -241,7 +254,6 @@ impl ExecState { | ||||
| impl GlobalState { | ||||
|     fn new(settings: &ExecutorSettings) -> Self { | ||||
|         let mut global = GlobalState { | ||||
|             id_generator: Default::default(), | ||||
|             path_to_source_id: Default::default(), | ||||
|             module_infos: Default::default(), | ||||
|             artifacts: Default::default(), | ||||
| @ -274,8 +286,14 @@ impl GlobalState { | ||||
| } | ||||
|  | ||||
| impl ModuleState { | ||||
|     pub(super) fn new(exec_settings: &ExecutorSettings, std_path: Option<String>, memory: Arc<ProgramMemory>) -> Self { | ||||
|     pub(super) fn new( | ||||
|         exec_settings: &ExecutorSettings, | ||||
|         std_path: Option<String>, | ||||
|         memory: Arc<ProgramMemory>, | ||||
|         module_id: Option<ModuleId>, | ||||
|     ) -> Self { | ||||
|         ModuleState { | ||||
|             id_generator: IdGenerator::new(module_id), | ||||
|             stack: memory.new_stack(), | ||||
|             pipe_value: Default::default(), | ||||
|             module_exports: Default::default(), | ||||
| @ -332,29 +350,3 @@ impl MetaSettings { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A generator for ArtifactIds that can be stable across executions. | ||||
| #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct IdGenerator { | ||||
|     pub(super) next_id: usize, | ||||
|     ids: Vec<uuid::Uuid>, | ||||
| } | ||||
|  | ||||
| impl IdGenerator { | ||||
|     pub fn new() -> Self { | ||||
|         Self::default() | ||||
|     } | ||||
|  | ||||
|     pub fn next_uuid(&mut self) -> uuid::Uuid { | ||||
|         if let Some(id) = self.ids.get(self.next_id) { | ||||
|             self.next_id += 1; | ||||
|             *id | ||||
|         } else { | ||||
|             let id = uuid::Uuid::new_v4(); | ||||
|             self.ids.push(id); | ||||
|             self.next_id += 1; | ||||
|             id | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,11 +8,11 @@ | ||||
| #[allow(unused_macros)] | ||||
| macro_rules! println { | ||||
|     ($($rest:tt)*) => { | ||||
|         #[cfg(feature = "disable-println")] | ||||
|         #[cfg(all(feature = "disable-println", not(test)))] | ||||
|         { | ||||
|             let _ = format!($($rest)*); | ||||
|         } | ||||
|         #[cfg(not(feature = "disable-println"))] | ||||
|         #[cfg(any(not(feature = "disable-println"), test))] | ||||
|         std::println!($($rest)*) | ||||
|     } | ||||
| } | ||||
| @ -20,11 +20,11 @@ macro_rules! println { | ||||
| #[allow(unused_macros)] | ||||
| macro_rules! eprintln { | ||||
|     ($($rest:tt)*) => { | ||||
|         #[cfg(feature = "disable-println")] | ||||
|         #[cfg(all(feature = "disable-println", not(test)))] | ||||
|         { | ||||
|             let _ = format!($($rest)*); | ||||
|         } | ||||
|         #[cfg(not(feature = "disable-println"))] | ||||
|         #[cfg(any(not(feature = "disable-println"), test))] | ||||
|         std::eprintln!($($rest)*) | ||||
|     } | ||||
| } | ||||
| @ -32,11 +32,11 @@ macro_rules! eprintln { | ||||
| #[allow(unused_macros)] | ||||
| macro_rules! print { | ||||
|     ($($rest:tt)*) => { | ||||
|         #[cfg(feature = "disable-println")] | ||||
|         #[cfg(all(feature = "disable-println", not(test)))] | ||||
|         { | ||||
|             let _ = format!($($rest)*); | ||||
|         } | ||||
|         #[cfg(not(feature = "disable-println"))] | ||||
|         #[cfg(any(not(feature = "disable-println"), test))] | ||||
|         std::print!($($rest)*) | ||||
|     } | ||||
| } | ||||
| @ -44,11 +44,11 @@ macro_rules! print { | ||||
| #[allow(unused_macros)] | ||||
| macro_rules! eprint { | ||||
|     ($($rest:tt)*) => { | ||||
|         #[cfg(feature = "disable-println")] | ||||
|         #[cfg(all(feature = "disable-println", not(test)))] | ||||
|         { | ||||
|             let _ = format!($($rest)*); | ||||
|         } | ||||
|         #[cfg(not(feature = "disable-println"))] | ||||
|         #[cfg(any(not(feature = "disable-println"), test))] | ||||
|         std::eprint!($($rest)*) | ||||
|     } | ||||
| } | ||||
| @ -108,7 +108,7 @@ pub mod wasm_engine { | ||||
|     pub use crate::{ | ||||
|         coredump::wasm::{CoreDumpManager, CoreDumper}, | ||||
|         engine::conn_wasm::{EngineCommandManager, EngineConnection}, | ||||
|         fs::wasm::FileSystemManager, | ||||
|         fs::wasm::{FileManager, FileSystemManager}, | ||||
|     }; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -36,7 +36,7 @@ macro_rules! logln { | ||||
| } | ||||
| pub(crate) use logln; | ||||
|  | ||||
| #[cfg(all(not(feature = "disable-println"), not(target_arch = "wasm32")))] | ||||
| #[cfg(any(test, all(not(feature = "disable-println"), not(target_arch = "wasm32"))))] | ||||
| #[inline] | ||||
| fn log_inner(msg: String) { | ||||
|     eprintln!("{msg}"); | ||||
| @ -48,7 +48,7 @@ fn log_inner(msg: String) { | ||||
|     web_sys::console::log_1(&msg.into()); | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "disable-println")] | ||||
| #[cfg(all(feature = "disable-println", not(test)))] | ||||
| #[inline] | ||||
| fn log_inner(_msg: String) {} | ||||
|  | ||||
|  | ||||
| @ -1170,7 +1170,7 @@ impl LanguageServer for Backend { | ||||
|             Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| { | ||||
|                 let mut text: String = format!("```\n{}", name); | ||||
|                 if let Some(ty) = value.principal_type() { | ||||
|                     text.push_str(&format!(": {}", ty)); | ||||
|                     text.push_str(&format!(": {}", ty.human_friendly_type())); | ||||
|                 } | ||||
|                 if let Some(v) = value.value_str() { | ||||
|                     text.push_str(&format!(" = {}", v)); | ||||
|  | ||||
| @ -36,6 +36,6 @@ async fn main() { | ||||
|     ) | ||||
|     .await | ||||
|     .unwrap(); | ||||
|     let mut exec_state = ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = ExecState::new(&ctx); | ||||
|     ctx.run(&program, &mut exec_state).await.unwrap(); | ||||
| } | ||||
|  | ||||
| @ -33,6 +33,12 @@ impl ModuleId { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl std::fmt::Display for ModuleId { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         write!(f, "{}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Default)] | ||||
| pub(crate) struct ModuleLoader { | ||||
|     /// The stack of import statements for detecting circular module imports. | ||||
|  | ||||
| @ -2149,7 +2149,7 @@ impl From<&Node<TagDeclarator>> for TagIdentifier { | ||||
|     fn from(tag: &Node<TagDeclarator>) -> Self { | ||||
|         TagIdentifier { | ||||
|             value: tag.name.clone(), | ||||
|             info: None, | ||||
|             info: Vec::new(), | ||||
|             meta: vec![Metadata { | ||||
|                 source_range: tag.into(), | ||||
|             }], | ||||
| @ -2937,7 +2937,7 @@ impl fmt::Display for Type { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match self { | ||||
|             Type::Primitive(primitive_type) => primitive_type.fmt(f), | ||||
|             Type::Array(primitive_type) => write!(f, "{primitive_type}[]"), | ||||
|             Type::Array(primitive_type) => write!(f, "[{primitive_type}]"), | ||||
|             Type::Object { properties } => { | ||||
|                 write!(f, "{{")?; | ||||
|                 let mut first = true; | ||||
| @ -3509,7 +3509,7 @@ const cylinder = startSketchOn('-XZ') | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn test_parse_type_args_array_on_functions() { | ||||
|         let some_program_string = r#"fn thing = (arg0: number[], arg1: string[], tag?: string) => { | ||||
|         let some_program_string = r#"fn thing = (arg0: [number], arg1: [string], tag?: string) => { | ||||
|     return arg0 | ||||
| }"#; | ||||
|         let program = crate::parsing::top_level_parse(some_program_string).unwrap(); | ||||
| @ -3540,7 +3540,7 @@ const cylinder = startSketchOn('-XZ') | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn test_parse_type_args_object_on_functions() { | ||||
|         let some_program_string = r#"fn thing = (arg0: number[], arg1: {thing: number, things: string[], more?: string}, tag?: string) => { | ||||
|         let some_program_string = r#"fn thing = (arg0: [number], arg1: {thing: number, things: [string], more?: string}, tag?: string) => { | ||||
|     return arg0 | ||||
| }"#; | ||||
|         let module_id = ModuleId::default(); | ||||
| @ -3594,7 +3594,7 @@ const cylinder = startSketchOn('-XZ') | ||||
|                             56, | ||||
|                             module_id, | ||||
|                         ), | ||||
|                         type_: Some(Node::new(Type::Array(PrimitiveType::String), 58, 64, module_id)), | ||||
|                         type_: Some(Node::new(Type::Array(PrimitiveType::String), 59, 65, module_id)), | ||||
|                         default_value: None, | ||||
|                         labeled: true, | ||||
|                         digest: None | ||||
| @ -3625,7 +3625,7 @@ const cylinder = startSketchOn('-XZ') | ||||
|  | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn test_parse_return_type_on_functions() { | ||||
|         let some_program_string = r#"fn thing(): {thing: number, things: string[], more?: string} { | ||||
|         let some_program_string = r#"fn thing(): {thing: number, things: [string], more?: string} { | ||||
|     return 1 | ||||
| }"#; | ||||
|         let module_id = ModuleId::default(); | ||||
| @ -3675,7 +3675,7 @@ const cylinder = startSketchOn('-XZ') | ||||
|                             34, | ||||
|                             module_id, | ||||
|                         ), | ||||
|                         type_: Some(Node::new(Type::Array(PrimitiveType::String), 36, 42, module_id)), | ||||
|                         type_: Some(Node::new(Type::Array(PrimitiveType::String), 37, 43, module_id)), | ||||
|                         default_value: None, | ||||
|                         labeled: true, | ||||
|                         digest: None | ||||
|  | ||||
| @ -4,17 +4,17 @@ | ||||
| use std::{cell::RefCell, collections::BTreeMap}; | ||||
|  | ||||
| use winnow::{ | ||||
|     combinator::{alt, delimited, opt, peek, preceded, repeat, separated, separated_pair, terminated}, | ||||
|     combinator::{alt, delimited, opt, peek, preceded, repeat, repeat_till, separated, separated_pair, terminated}, | ||||
|     dispatch, | ||||
|     error::{ErrMode, StrContext, StrContextValue}, | ||||
|     prelude::*, | ||||
|     stream::Stream, | ||||
|     token::{any, one_of, take_till}, | ||||
|     token::{any, none_of, one_of, take_till}, | ||||
| }; | ||||
|  | ||||
| use super::{ | ||||
|     ast::types::{Ascription, ImportPath, LabelledExpression}, | ||||
|     token::NumericSuffix, | ||||
|     token::{NumericSuffix, RESERVED_WORDS}, | ||||
| }; | ||||
| use crate::{ | ||||
|     docs::StdLibFn, | ||||
| @ -746,21 +746,58 @@ pub(crate) fn array_elem_by_elem(i: &mut TokenSlice) -> PResult<Node<ArrayExpres | ||||
|     ) | ||||
|     .context(expected("array contents, a list of elements (like [1, 2, 3])")) | ||||
|     .parse_next(i)?; | ||||
|     ignore_trailing_comma(i); | ||||
|     ignore_whitespace(i); | ||||
|     let end = close_bracket(i) | ||||
|         .map_err(|e| { | ||||
|             if let Some(mut err) = e.clone().into_inner() { | ||||
|                 err.cause = Some(CompilationError::fatal( | ||||
|                     open.as_source_range(), | ||||
|                     "Array is missing a closing bracket(`]`)", | ||||
|                 )); | ||||
|                 ErrMode::Cut(err) | ||||
|             } else { | ||||
|                 // ErrMode::Incomplete, not sure if it's actually possible to end up with this here | ||||
|                 e | ||||
|             } | ||||
|         })? | ||||
|         .end; | ||||
|  | ||||
|     let maybe_end = close_bracket(i).map_err(|e| { | ||||
|         if let Some(mut err) = e.clone().into_inner() { | ||||
|             let start_range = open.as_source_range(); | ||||
|             let end_range = i.as_source_range(); | ||||
|             err.cause = Some(CompilationError::fatal( | ||||
|                 SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]), | ||||
|                 "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array", | ||||
|             )); | ||||
|             ErrMode::Cut(err) | ||||
|         } else { | ||||
|             // ErrMode::Incomplete, not sure if it's actually possible to end up with this here | ||||
|             e | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     if maybe_end.is_err() { | ||||
|         // if there is a closing bracket at some point, but it wasn't the next token, it's likely that they forgot a comma between some | ||||
|         // of the elements | ||||
|         let maybe_closing_bracket: PResult<((), Token)> = peek(repeat_till( | ||||
|             0.., | ||||
|             none_of(|token: Token| { | ||||
|                 // bail out early if we encounter something that is for sure not allowed in an | ||||
|                 // array, otherwise we could seek to find a closing bracket until the end of the | ||||
|                 // file | ||||
|                 RESERVED_WORDS | ||||
|                     .keys() | ||||
|                     .chain([",,", "{", "}", "["].iter()) | ||||
|                     .any(|word| *word == token.value) | ||||
|             }) | ||||
|             .void(), | ||||
|             one_of(|term: Token| term.value == "]"), | ||||
|         )) | ||||
|         .parse_next(i); | ||||
|         let has_closing_bracket = maybe_closing_bracket.is_ok(); | ||||
|         if has_closing_bracket { | ||||
|             let start_range = i.as_source_range(); | ||||
|             // safe to unwrap here because we checked it was Ok above | ||||
|             let end_range = maybe_closing_bracket.unwrap().1.as_source_range(); | ||||
|             let e = ContextError { | ||||
|                 context: vec![], | ||||
|                 cause: Some(CompilationError::fatal( | ||||
|                     SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]), | ||||
|                     "Unexpected character encountered. You might be missing a comma in between elements.", | ||||
|                 )), | ||||
|             }; | ||||
|             return Err(ErrMode::Cut(e)); | ||||
|         } | ||||
|     } | ||||
|     let end = maybe_end?.end; | ||||
|  | ||||
|     // Sort the array's elements (i.e. expression nodes) from the noncode nodes. | ||||
|     let (elements, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = elements.into_iter().enumerate().fold( | ||||
| @ -819,7 +856,7 @@ fn array_end_start(i: &mut TokenSlice) -> PResult<Node<ArrayRangeExpression>> { | ||||
| } | ||||
|  | ||||
| fn object_property_same_key_and_val(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> { | ||||
|     let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?; | ||||
|     let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height = 4', 'height' is the property key")).parse_next(i)?; | ||||
|     ignore_whitespace(i); | ||||
|     Ok(Node { | ||||
|         start: key.start, | ||||
| @ -846,7 +883,7 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> { | ||||
|     ignore_whitespace(i); | ||||
|     let expr = match expression | ||||
|         .context(expected( | ||||
|             "the value which you're setting the property to, e.g. in 'height: 4', the value is 4", | ||||
|             "the value which you're setting the property to, e.g. in 'height = 4', the value is 4", | ||||
|         )) | ||||
|         .parse_next(i) | ||||
|     { | ||||
| @ -892,7 +929,7 @@ fn property_separator(i: &mut TokenSlice) -> PResult<()> { | ||||
|     alt(( | ||||
|         // Normally you need a comma. | ||||
|         comma_sep, | ||||
|         // But, if the array is ending, no need for a comma. | ||||
|         // But, if the object is ending, no need for a comma. | ||||
|         peek(preceded(opt(whitespace), close_brace)).void(), | ||||
|     )) | ||||
|     .parse_next(i) | ||||
| @ -926,10 +963,62 @@ pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> { | ||||
|         )), | ||||
|     ) | ||||
|     .context(expected( | ||||
|         "a comma-separated list of key-value pairs, e.g. 'height: 4, width: 3'", | ||||
|         "a comma-separated list of key-value pairs, e.g. 'height = 4, width = 3'", | ||||
|     )) | ||||
|     .parse_next(i)?; | ||||
|     ignore_trailing_comma(i); | ||||
|     ignore_whitespace(i); | ||||
|  | ||||
|     let maybe_end = close_brace(i).map_err(|e| { | ||||
|         if let Some(mut err) = e.clone().into_inner() { | ||||
|             let start_range = open.as_source_range(); | ||||
|             let end_range = i.as_source_range(); | ||||
|             err.cause = Some(CompilationError::fatal( | ||||
|                 SourceRange::from([start_range.start(), end_range.start(), end_range.module_id().as_usize()]), | ||||
|                 "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object", | ||||
|             )); | ||||
|             ErrMode::Cut(err) | ||||
|         } else { | ||||
|             // ErrMode::Incomplete, not sure if it's actually possible to end up with this here | ||||
|             e | ||||
|         } | ||||
|     }); | ||||
|     if maybe_end.is_err() { | ||||
|         // if there is a closing brace at some point, but it wasn't the next token, it's likely that they forgot a comma between some | ||||
|         // of the properties | ||||
|         let maybe_closing_brace: PResult<((), Token)> = peek(repeat_till( | ||||
|             0.., | ||||
|             none_of(|token: Token| { | ||||
|                 // bail out early if we encounter something that is for sure not allowed in an | ||||
|                 // object, otherwise we could seek to find a closing brace until the end of the | ||||
|                 // file | ||||
|                 RESERVED_WORDS | ||||
|                     .keys() | ||||
|                     .chain([",,", "[", "]", "{"].iter()) | ||||
|                     .any(|word| *word == token.value) | ||||
|             }) | ||||
|             .void(), | ||||
|             one_of(|c: Token| c.value == "}"), | ||||
|         )) | ||||
|         .parse_next(i); | ||||
|         let has_closing_brace = maybe_closing_brace.is_ok(); | ||||
|         if has_closing_brace { | ||||
|             let start_range = i.as_source_range(); | ||||
|             // okay to unwrap here because we checked it was Ok above | ||||
|             let end_range = maybe_closing_brace.unwrap().1.as_source_range(); | ||||
|  | ||||
|             let e = ContextError { | ||||
|                 context: vec![], | ||||
|                 cause: Some(CompilationError::fatal( | ||||
|                     SourceRange::from([start_range.start(), end_range.end(), end_range.module_id().as_usize()]), | ||||
|                     "Unexpected character encountered. You might be missing a comma in between properties.", | ||||
|                 )), | ||||
|             }; | ||||
|             return Err(ErrMode::Cut(e)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let end = maybe_end?.end; | ||||
|     // Sort the object's properties from the noncode nodes. | ||||
|     let (properties, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = properties.into_iter().enumerate().fold( | ||||
|         (Vec::new(), BTreeMap::new()), | ||||
| @ -945,9 +1034,7 @@ pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> { | ||||
|             (properties, non_code_nodes) | ||||
|         }, | ||||
|     ); | ||||
|     ignore_trailing_comma(i); | ||||
|     ignore_whitespace(i); | ||||
|     let end = close_brace(i)?.end; | ||||
|  | ||||
|     let non_code_meta = NonCodeMeta { | ||||
|         non_code_nodes, | ||||
|         ..Default::default() | ||||
| @ -2564,7 +2651,7 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
|             )) | ||||
|         }), | ||||
|         // Array types | ||||
|         (primitive_type, open_bracket, close_bracket).map(|(t, _, _)| Ok(t.map(Type::Array))), | ||||
|         (open_bracket, primitive_type, close_bracket).map(|(_, t, _)| Ok(t.map(Type::Array))), | ||||
|         // Primitive types | ||||
|         primitive_type.map(|t| Ok(t.map(Type::Primitive))), | ||||
|     )) | ||||
| @ -3869,7 +3956,7 @@ mySk1 = startSketchOn(XY) | ||||
|         assert_eq!( | ||||
|             src_expected, | ||||
|             src_actual, | ||||
|             "expected error would highlight {} but it actually highlighted {}", | ||||
|             "expected error would highlight `{}` but it actually highlighted `{}`", | ||||
|             &p[src_expected[0]..src_expected[1]], | ||||
|             &p[src_actual[0]..src_actual[1]], | ||||
|         ); | ||||
| @ -4060,7 +4147,11 @@ z(-[["#, | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_weird_lots_of_fancy_brackets() { | ||||
|         assert_err(r#"zz({{{{{{{{)iegAng{{{{{{{##"#, "Unexpected token: (", [2, 3]); | ||||
|         assert_err( | ||||
|             r#"zz({{{{{{{{)iegAng{{{{{{{##"#, | ||||
|             "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object", | ||||
|             [3, 4], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @ -4601,10 +4692,123 @@ let myBox = box([0,0], -3, -16, -10) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_missing_closing_bracket() { | ||||
|     fn test_parse_array_missing_closing_bracket() { | ||||
|         let some_program_string = r#" | ||||
| sketch001 = startSketchOn('XZ') |> startProfileAt([90.45, 119.09, %)"#; | ||||
|         assert_err(some_program_string, "Array is missing a closing bracket(`]`)", [51, 52]); | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array", | ||||
|             [51, 67], | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_array_missing_comma() { | ||||
|         let some_program_string = r#" | ||||
| sketch001 = startSketchOn('XZ') |> startProfileAt([90.45 119.09], %)"#; | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Unexpected character encountered. You might be missing a comma in between elements.", | ||||
|             [52, 65], | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_array_reserved_word_early_exit() { | ||||
|         // since there is an early exit if encountering a reserved word, the error should be about | ||||
|         // that and not the missing comma | ||||
|         let some_program_string = r#" | ||||
| sketch001 = startSketchOn('XZ') |> startProfileAt([90.45 $struct], %)"#; | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array", | ||||
|             [51, 52], | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_array_random_brace() { | ||||
|         let some_program_string = r#" | ||||
| sketch001 = startSketchOn('XZ') |> startProfileAt([}], %)"#; | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing bracket(`]`) for the array", | ||||
|             [51, 52], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_object_missing_closing_brace() { | ||||
|         let some_program_string = r#"{ | ||||
|             foo = bar,"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object", | ||||
|             [0, 23], | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_object_reserved_word_early_exit() { | ||||
|         // since there is an early exit if encountering a reserved word, the error should be about | ||||
|         // that and not the missing comma | ||||
|         let some_program_string = r#"{bar = foo struct = man}"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object", | ||||
|             [0, 1], | ||||
|         ); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_object_missing_comma() { | ||||
|         let some_program_string = r#"{ | ||||
|             foo = bar, | ||||
|             bar = foo | ||||
|             bat = man | ||||
|         }"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Unexpected character encountered. You might be missing a comma in between properties.", | ||||
|             [37, 78], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_object_missing_comma_one_line() { | ||||
|         let some_program_string = r#"{bar = foo bat = man}"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Unexpected character encountered. You might be missing a comma in between properties.", | ||||
|             [1, 21], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_object_random_bracket() { | ||||
|         let some_program_string = r#"{]}"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Encountered an unexpected character(s) before finding a closing brace(`}`) for the object", | ||||
|             [0, 1], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_object_shorthand_missing_comma() { | ||||
|         let some_program_string = r#" | ||||
| bar = 1 | ||||
|         { | ||||
|             foo = bar, | ||||
|             bar | ||||
|             bat = man | ||||
|         }"#; | ||||
|  | ||||
|         assert_err( | ||||
|             some_program_string, | ||||
|             "Unexpected character encountered. You might be missing a comma in between properties.", | ||||
|             [54, 89], | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|  | ||||
| @ -24,7 +24,6 @@ use crate::{ | ||||
|  | ||||
| mod tokeniser; | ||||
|  | ||||
| #[cfg(test)] | ||||
| pub(crate) use tokeniser::RESERVED_WORDS; | ||||
|  | ||||
| // Note the ordering, it's important that `m` comes after `mm` and `cm`. | ||||
| @ -162,7 +161,9 @@ impl IntoIterator for TokenStream { | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct TokenSlice<'a> { | ||||
|     stream: &'a TokenStream, | ||||
|     /// Current position of the leading Token in the stream | ||||
|     start: usize, | ||||
|     /// The number of total Tokens in the stream | ||||
|     end: usize, | ||||
| } | ||||
|  | ||||
| @ -190,6 +191,21 @@ impl<'a> TokenSlice<'a> { | ||||
|             stream: self.stream, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn as_source_range(&self) -> SourceRange { | ||||
|         let stream_len = self.stream.tokens.len(); | ||||
|         let first_token = if stream_len == self.start { | ||||
|             &self.stream.tokens[self.start - 1] | ||||
|         } else { | ||||
|             self.token(0) | ||||
|         }; | ||||
|         let last_token = if stream_len == self.end { | ||||
|             &self.stream.tokens[stream_len - 1] | ||||
|         } else { | ||||
|             self.token(self.end - self.start) | ||||
|         }; | ||||
|         SourceRange::new(first_token.start, last_token.end, last_token.module_id) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> IntoIterator for TokenSlice<'a> { | ||||
| @ -294,6 +310,14 @@ impl<'a> winnow::stream::StreamIsPartial for TokenSlice<'a> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> winnow::stream::FindSlice<&str> for TokenSlice<'a> { | ||||
|     fn find_slice(&self, substr: &str) -> Option<std::ops::Range<usize>> { | ||||
|         self.iter() | ||||
|             .enumerate() | ||||
|             .find_map(|(i, b)| if b.value == substr { Some(i..self.end) } else { None }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Checkpoint(usize, usize); | ||||
|  | ||||
|  | ||||
| @ -149,7 +149,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) { | ||||
|                 // due to SSI and GPU. | ||||
|                 std::fs::write(test.output_dir.join(EXPORTED_STEP_NAME), step).unwrap(); | ||||
|             } | ||||
|             let outcome = exec_state.to_wasm_outcome(env_ref); | ||||
|             let outcome = exec_state.to_wasm_outcome(env_ref).await; | ||||
|             assert_common_snapshots( | ||||
|                 test, | ||||
|                 outcome.operations, | ||||
| @ -2224,3 +2224,24 @@ mod crazy_multi_profile { | ||||
|         super::execute(TEST_NAME, true).await | ||||
|     } | ||||
| } | ||||
| mod assembly_mixed_units_cubes { | ||||
|     const TEST_NAME: &str = "assembly_mixed_units_cubes"; | ||||
|  | ||||
|     /// Test parsing KCL. | ||||
|     #[test] | ||||
|     fn parse() { | ||||
|         super::parse(TEST_NAME) | ||||
|     } | ||||
|  | ||||
|     /// Test that parsing and unparsing KCL produces the original KCL input. | ||||
|     #[test] | ||||
|     fn unparse() { | ||||
|         super::unparse(TEST_NAME) | ||||
|     } | ||||
|  | ||||
|     /// Test that KCL is executed correctly. | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn kcl_test_execute() { | ||||
|         super::execute(TEST_NAME, true).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -12,7 +12,10 @@ use validator::Validate; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, Solid, SolidSet}, | ||||
|     execution::{ | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ExecState, KclValue, PrimitiveType, Solid, | ||||
|     }, | ||||
|     std::Args, | ||||
| }; | ||||
|  | ||||
| @ -38,8 +41,12 @@ struct AppearanceData { | ||||
| } | ||||
|  | ||||
| /// Set the appearance of a solid. This only works on solids, not sketches or individual paths. | ||||
| pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?; | ||||
| pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|  | ||||
|     let color: String = args.get_kw_arg("color")?; | ||||
|     let metalness: Option<f64> = args.get_kw_arg_opt("metalness")?; | ||||
| @ -66,7 +73,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     let result = inner_appearance(solid_set, data.color, data.metalness, data.roughness, args).await?; | ||||
|     let result = inner_appearance(solids, data.color, data.metalness, data.roughness, args).await?; | ||||
|     Ok(result.into()) | ||||
| } | ||||
|  | ||||
| @ -276,21 +283,19 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = { docs = "The solid(s) whose appearance is being set" }, | ||||
|         solids = { docs = "The solid(s) whose appearance is being set" }, | ||||
|         color = { docs = "Color of the new material, a hex string like '#ff0000'"}, | ||||
|         metalness = { docs = "Metalness of the new material, a percentage like 95.7." }, | ||||
|         roughness = { docs = "Roughness of the new material, a percentage like 95.7." }, | ||||
|     } | ||||
| }] | ||||
| async fn inner_appearance( | ||||
|     solid_set: SolidSet, | ||||
|     solids: Vec<Solid>, | ||||
|     color: String, | ||||
|     metalness: Option<f64>, | ||||
|     roughness: Option<f64>, | ||||
|     args: Args, | ||||
| ) -> Result<SolidSet, KclError> { | ||||
|     let solids: Vec<Box<Solid>> = solid_set.into(); | ||||
|  | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     for solid in &solids { | ||||
|         // Set the material properties. | ||||
|         let rgb = rgba_simple::RGB::<f32>::from_hex(&color).map_err(|err| { | ||||
| @ -323,5 +328,5 @@ async fn inner_appearance( | ||||
|         // I can't think of a use case for it. | ||||
|     } | ||||
|  | ||||
|     Ok(SolidSet::from(solids)) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| @ -12,9 +12,9 @@ use serde::{Deserialize, Serialize}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
|         kcl_value::{FunctionSource, NumericType}, | ||||
|         ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSet, | ||||
|         SketchSurface, Solid, SolidSet, TagIdentifier, | ||||
|         kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, | ||||
|         ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PrimitiveType, Sketch, | ||||
|         SketchSurface, Solid, TagIdentifier, | ||||
|     }, | ||||
|     parsing::ast::types::TagNode, | ||||
|     source_range::SourceRange, | ||||
| @ -233,9 +233,41 @@ impl Args { | ||||
|         T::from_kcl_val(&arg.value).ok_or_else(|| { | ||||
|             let expected_type_name = tynm::type_name::<T>(); | ||||
|             let actual_type_name = arg.value.human_friendly_type(); | ||||
|             let msg_base = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}"); | ||||
|             let suggestion = match (expected_type_name.as_str(), actual_type_name) { | ||||
|                 ("SolidSet", "Sketch") => Some( | ||||
|             let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}"); | ||||
|             KclError::Semantic(KclErrorDetails { | ||||
|                 source_ranges: arg.source_ranges(), | ||||
|                 message, | ||||
|             }) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Get the unlabeled keyword argument. If not set, returns Err. If it | ||||
|     /// can't be converted to the given type, returns Err. | ||||
|     pub(crate) fn get_unlabeled_kw_arg_typed<T>( | ||||
|         &self, | ||||
|         label: &str, | ||||
|         ty: &RuntimeType, | ||||
|         exec_state: &mut ExecState, | ||||
|     ) -> Result<T, KclError> | ||||
|     where | ||||
|         T: for<'a> FromKclValue<'a>, | ||||
|     { | ||||
|         let arg = self | ||||
|             .unlabeled_kw_arg_unconverted() | ||||
|             .ok_or(KclError::Semantic(KclErrorDetails { | ||||
|                 source_ranges: vec![self.source_range], | ||||
|                 message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"), | ||||
|             }))?; | ||||
|  | ||||
|         let arg = arg.value.coerce(ty, exec_state).ok_or_else(|| { | ||||
|             let actual_type_name = arg.value.human_friendly_type(); | ||||
|             let msg_base = format!( | ||||
|                 "This function expected the input argument to be {} but it's actually of type {actual_type_name}", | ||||
|                 ty.human_friendly_type(), | ||||
|             ); | ||||
|             let suggestion = match (ty, actual_type_name) { | ||||
|                 (RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") | ||||
|                 | (RuntimeType::Array(PrimitiveType::Solid, _), "Sketch") => Some( | ||||
|                     "You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`", | ||||
|                 ), | ||||
|                 _ => None, | ||||
| @ -248,7 +280,10 @@ impl Args { | ||||
|                 source_ranges: arg.source_ranges(), | ||||
|                 message, | ||||
|             }) | ||||
|         }) | ||||
|         })?; | ||||
|  | ||||
|         // TODO unnecessary cloning | ||||
|         Ok(T::from_kcl_val(&arg).unwrap()) | ||||
|     } | ||||
|  | ||||
|     // Add a modeling command to the batch but don't fire it right away. | ||||
| @ -286,13 +321,16 @@ impl Args { | ||||
|         exec_state: &'e mut ExecState, | ||||
|         tag: &'a TagIdentifier, | ||||
|     ) -> Result<&'e crate::execution::TagEngineInfo, KclError> { | ||||
|         if let KclValue::TagIdentifier(t) = exec_state.stack().get_from_call_stack(&tag.value, self.source_range)? { | ||||
|             Ok(t.info.as_ref().ok_or_else(|| { | ||||
|         if let (epoch, KclValue::TagIdentifier(t)) = | ||||
|             exec_state.stack().get_from_call_stack(&tag.value, self.source_range)? | ||||
|         { | ||||
|             let info = t.get_info(epoch).ok_or_else(|| { | ||||
|                 KclError::Type(KclErrorDetails { | ||||
|                     message: format!("Tag `{}` does not have engine info", tag.value), | ||||
|                     source_ranges: vec![self.source_range], | ||||
|                 }) | ||||
|             })?) | ||||
|             })?; | ||||
|             Ok(info) | ||||
|         } else { | ||||
|             Err(KclError::Type(KclErrorDetails { | ||||
|                 message: format!("Tag `{}` does not exist", tag.value), | ||||
| @ -309,7 +347,7 @@ impl Args { | ||||
|     where | ||||
|         'e: 'a, | ||||
|     { | ||||
|         if let Some(info) = &tag.info { | ||||
|         if let Some(info) = tag.get_cur_info() { | ||||
|             return Ok(info); | ||||
|         } | ||||
|  | ||||
| @ -324,7 +362,7 @@ impl Args { | ||||
|     where | ||||
|         'e: 'a, | ||||
|     { | ||||
|         if let Some(info) = &tag.info { | ||||
|         if let Some(info) = tag.get_cur_info() { | ||||
|             if info.surface.is_some() { | ||||
|                 return Ok(info); | ||||
|             } | ||||
| @ -335,10 +373,10 @@ impl Args { | ||||
|  | ||||
|     /// Flush just the fillets and chamfers for this specific SolidSet. | ||||
|     #[allow(clippy::vec_box)] | ||||
|     pub(crate) async fn flush_batch_for_solid_set( | ||||
|     pub(crate) async fn flush_batch_for_solids( | ||||
|         &self, | ||||
|         exec_state: &mut ExecState, | ||||
|         solids: Vec<Box<Solid>>, | ||||
|         solids: Vec<Solid>, | ||||
|     ) -> Result<(), KclError> { | ||||
|         // Make sure we don't traverse sketches more than once. | ||||
|         let mut traversed_sketches = Vec::new(); | ||||
| @ -507,12 +545,48 @@ impl Args { | ||||
|         Ok((a.n, b.n, ty)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_sketches(&self) -> Result<(SketchSet, Sketch), KclError> { | ||||
|         FromArgs::from_args(self, 0) | ||||
|     pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> { | ||||
|         let sarg = self.args[0] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!( | ||||
|                     "Expected an array of sketches, found {}", | ||||
|                     self.args[0].value.human_friendly_type() | ||||
|                 ), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         let sketches = match sarg { | ||||
|             KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(), | ||||
|             _ => unreachable!(), | ||||
|         }; | ||||
|         let sarg = self.args[1] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!("Expected a sketch, found {}", self.args[1].value.human_friendly_type()), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         let sketch = match sarg { | ||||
|             KclValue::Sketch { value } => *value, | ||||
|             _ => unreachable!(), | ||||
|         }; | ||||
|  | ||||
|         Ok((sketches, sketch)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_sketch(&self) -> Result<Sketch, KclError> { | ||||
|         FromArgs::from_args(self, 0) | ||||
|     pub(crate) fn get_sketch(&self, exec_state: &mut ExecState) -> Result<Sketch, KclError> { | ||||
|         let sarg = self.args[0] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!("Expected a sketch, found {}", self.args[0].value.human_friendly_type()), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         match sarg { | ||||
|             KclValue::Sketch { value } => Ok(*value), | ||||
|             _ => unreachable!(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError> | ||||
| @ -533,18 +607,55 @@ impl Args { | ||||
|         FromArgs::from_args(self, 0) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_data_and_sketch_set<'a, T>(&'a self) -> Result<(T, SketchSet), KclError> | ||||
|     pub(crate) fn get_data_and_sketches<'a, T>( | ||||
|         &'a self, | ||||
|         exec_state: &mut ExecState, | ||||
|     ) -> Result<(T, Vec<Sketch>), KclError> | ||||
|     where | ||||
|         T: serde::de::DeserializeOwned + FromArgs<'a>, | ||||
|     { | ||||
|         FromArgs::from_args(self, 0) | ||||
|         let data: T = FromArgs::from_args(self, 0)?; | ||||
|         let sarg = self.args[1] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::None), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!( | ||||
|                     "Expected an array of sketches for second argument, found {}", | ||||
|                     self.args[1].value.human_friendly_type() | ||||
|                 ), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         let sketches = match sarg { | ||||
|             KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(), | ||||
|             _ => unreachable!(), | ||||
|         }; | ||||
|         Ok((data, sketches)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_data_and_sketch_and_tag<'a, T>(&'a self) -> Result<(T, Sketch, Option<TagNode>), KclError> | ||||
|     pub(crate) fn get_data_and_sketch_and_tag<'a, T>( | ||||
|         &'a self, | ||||
|         exec_state: &mut ExecState, | ||||
|     ) -> Result<(T, Sketch, Option<TagNode>), KclError> | ||||
|     where | ||||
|         T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized, | ||||
|     { | ||||
|         FromArgs::from_args(self, 0) | ||||
|         let data: T = FromArgs::from_args(self, 0)?; | ||||
|         let sarg = self.args[1] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Primitive(PrimitiveType::Sketch), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!( | ||||
|                     "Expected a sketch for second argument, found {}", | ||||
|                     self.args[1].value.human_friendly_type() | ||||
|                 ), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         let sketch = match sarg { | ||||
|             KclValue::Sketch { value } => *value, | ||||
|             _ => unreachable!(), | ||||
|         }; | ||||
|         let tag: Option<TagNode> = FromArgs::from_args(self, 2)?; | ||||
|         Ok((data, sketch, tag)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_data_and_sketch_surface<'a, T>(&'a self) -> Result<(T, SketchSurface, Option<TagNode>), KclError> | ||||
| @ -554,11 +665,26 @@ impl Args { | ||||
|         FromArgs::from_args(self, 0) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_data_and_solid<'a, T>(&'a self) -> Result<(T, Box<Solid>), KclError> | ||||
|     pub(crate) fn get_data_and_solid<'a, T>(&'a self, exec_state: &mut ExecState) -> Result<(T, Box<Solid>), KclError> | ||||
|     where | ||||
|         T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized, | ||||
|     { | ||||
|         FromArgs::from_args(self, 0) | ||||
|         let data: T = FromArgs::from_args(self, 0)?; | ||||
|         let sarg = self.args[1] | ||||
|             .value | ||||
|             .coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state) | ||||
|             .ok_or(KclError::Type(KclErrorDetails { | ||||
|                 message: format!( | ||||
|                     "Expected a solid for second argument, found {}", | ||||
|                     self.args[1].value.human_friendly_type() | ||||
|                 ), | ||||
|                 source_ranges: vec![self.source_range], | ||||
|             }))?; | ||||
|         let solid = match sarg { | ||||
|             KclValue::Solid { value } => value, | ||||
|             _ => unreachable!(), | ||||
|         }; | ||||
|         Ok((data, solid)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn get_tag_to_number_sketch(&self) -> Result<(TagIdentifier, f64, Sketch), KclError> { | ||||
| @ -1301,7 +1427,6 @@ impl_from_kcl_for_vec!(crate::execution::EdgeCut); | ||||
| impl_from_kcl_for_vec!(crate::execution::Metadata); | ||||
| impl_from_kcl_for_vec!(super::fillet::EdgeReference); | ||||
| impl_from_kcl_for_vec!(ExtrudeSurface); | ||||
| impl_from_kcl_for_vec!(Sketch); | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for SourceRange { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
| @ -1337,8 +1462,10 @@ impl<'a> FromKclValue<'a> for crate::execution::Solid { | ||||
| impl<'a> FromKclValue<'a> for crate::execution::SolidOrImportedGeometry { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         match arg { | ||||
|             KclValue::Solid { value } => Some(Self::Solid(value.clone())), | ||||
|             KclValue::Solids { value } => Some(Self::SolidSet(value.clone())), | ||||
|             KclValue::Solid { value } => Some(Self::SolidSet(vec![(**value).clone()])), | ||||
|             KclValue::HomArray { value, .. } => Some(Self::SolidSet( | ||||
|                 value.iter().map(|v| v.as_solid().unwrap().clone()).collect(), | ||||
|             )), | ||||
|             KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))), | ||||
|             _ => None, | ||||
|         } | ||||
| @ -1351,11 +1478,13 @@ impl<'a> FromKclValue<'a> for super::sketch::SketchData { | ||||
|         let case1 = crate::execution::Plane::from_kcl_val; | ||||
|         let case2 = super::sketch::PlaneData::from_kcl_val; | ||||
|         let case3 = crate::execution::Solid::from_kcl_val; | ||||
|         let case4 = <Vec<Solid>>::from_kcl_val; | ||||
|         case1(arg) | ||||
|             .map(Box::new) | ||||
|             .map(Self::Plane) | ||||
|             .or_else(|| case2(arg).map(Self::PlaneOrientation)) | ||||
|             .or_else(|| case3(arg).map(Box::new).map(Self::Solid)) | ||||
|             .or_else(|| case4(arg).map(|v| Box::new(v[0].clone())).map(Self::Solid)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1528,6 +1657,7 @@ impl<'a> FromKclValue<'a> for TyF64 { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for Sketch { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         let KclValue::Sketch { value } = arg else { | ||||
| @ -1545,13 +1675,16 @@ impl<'a> FromKclValue<'a> for Helix { | ||||
|         Some(value.as_ref().to_owned()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for SweepPath { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         let case1 = Sketch::from_kcl_val; | ||||
|         let case2 = Helix::from_kcl_val; | ||||
|         let case2 = <Vec<Sketch>>::from_kcl_val; | ||||
|         let case3 = Helix::from_kcl_val; | ||||
|         case1(arg) | ||||
|             .map(Self::Sketch) | ||||
|             .or_else(|| case2(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0)))) | ||||
|             .or_else(|| case2(arg).map(|arg0: Vec<Sketch>| Self::Sketch(arg0[0].clone()))) | ||||
|             .or_else(|| case3(arg).map(|arg0: Helix| Self::Helix(Box::new(arg0)))) | ||||
|     } | ||||
| } | ||||
| impl<'a> FromKclValue<'a> for String { | ||||
| @ -1579,20 +1712,6 @@ impl<'a> FromKclValue<'a> for bool { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for SketchSet { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         match arg { | ||||
|             KclValue::Sketch { value: sketch } => Some(SketchSet::from(sketch.to_owned())), | ||||
|             KclValue::Sketches { value } => Some(SketchSet::from(value.to_owned())), | ||||
|             KclValue::MixedArray { .. } => { | ||||
|                 let v: Option<Vec<Sketch>> = FromKclValue::from_kcl_val(arg); | ||||
|                 Some(SketchSet::Sketches(v?.iter().cloned().map(Box::new).collect())) | ||||
|             } | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for Box<Solid> { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         let KclValue::Solid { value } = arg else { | ||||
| @ -1602,15 +1721,27 @@ impl<'a> FromKclValue<'a> for Box<Solid> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for &'a FunctionSource { | ||||
| impl<'a> FromKclValue<'a> for Vec<Solid> { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         arg.get_function() | ||||
|         let KclValue::HomArray { value, .. } = arg else { | ||||
|             return None; | ||||
|         }; | ||||
|         value.iter().map(Solid::from_kcl_val).collect() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for SolidSet { | ||||
| impl<'a> FromKclValue<'a> for Vec<Sketch> { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         arg.get_solid_set().ok() | ||||
|         let KclValue::HomArray { value, .. } = arg else { | ||||
|             return None; | ||||
|         }; | ||||
|         value.iter().map(Sketch::from_kcl_val).collect() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromKclValue<'a> for &'a FunctionSource { | ||||
|     fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { | ||||
|         arg.get_function() | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -7,7 +7,10 @@ use kittycad_modeling_cmds as kcmc; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, Solid}, | ||||
|     execution::{ | ||||
|         kcl_value::RuntimeType, ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, PrimitiveType, | ||||
|         Solid, | ||||
|     }, | ||||
|     parsing::ast::types::TagNode, | ||||
|     std::{fillet::EdgeReference, Args}, | ||||
| }; | ||||
| @ -16,7 +19,7 @@ pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001; | ||||
|  | ||||
| /// Create chamfers on tagged paths. | ||||
| pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid = args.get_unlabeled_kw_arg("solid")?; | ||||
|     let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?; | ||||
|     let length = args.get_kw_arg("length")?; | ||||
|     let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?; | ||||
|     let tag = args.get_kw_arg_opt("tag")?; | ||||
|  | ||||
| @ -19,18 +19,22 @@ use uuid::Uuid; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
|         ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSet, SketchSurface, Solid, | ||||
|         SolidSet, | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, PrimitiveType, Sketch, SketchSurface, Solid, | ||||
|     }, | ||||
|     std::Args, | ||||
| }; | ||||
|  | ||||
| /// Extrudes by a given amount. | ||||
| pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch_set = args.get_unlabeled_kw_arg("sketch_set")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let length = args.get_kw_arg("length")?; | ||||
|  | ||||
|     let result = inner_extrude(sketch_set, length, exec_state, args).await?; | ||||
|     let result = inner_extrude(sketches, length, exec_state, args).await?; | ||||
|  | ||||
|     Ok(result.into()) | ||||
| } | ||||
| @ -90,18 +94,17 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         sketch_set = { docs = "Which sketch or set of sketches should be extruded"}, | ||||
|         sketches = { docs = "Which sketch or sketches should be extruded"}, | ||||
|         length = { docs = "How far to extrude the given sketches"}, | ||||
|     } | ||||
| }] | ||||
| async fn inner_extrude( | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     length: f64, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidSet, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     // Extrude the element(s). | ||||
|     let sketches: Vec<Sketch> = sketch_set.into(); | ||||
|     let mut solids = Vec::new(); | ||||
|     for sketch in &sketches { | ||||
|         let id = exec_state.next_uuid(); | ||||
| @ -121,7 +124,7 @@ async fn inner_extrude( | ||||
|         solids.push(do_post_extrude(sketch.clone(), id.into(), length, exec_state, args.clone()).await?); | ||||
|     } | ||||
|  | ||||
|     Ok(solids.into()) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| pub(crate) async fn do_post_extrude( | ||||
| @ -130,7 +133,7 @@ pub(crate) async fn do_post_extrude( | ||||
|     length: f64, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Box<Solid>, KclError> { | ||||
| ) -> Result<Solid, KclError> { | ||||
|     // Bring the object to the front of the scene. | ||||
|     // See: https://github.com/KittyCAD/modeling-app/issues/806 | ||||
|     args.batch_modeling_cmd( | ||||
| @ -282,7 +285,7 @@ pub(crate) async fn do_post_extrude( | ||||
|         }) | ||||
|         .collect(); | ||||
|  | ||||
|     Ok(Box::new(Solid { | ||||
|     Ok(Solid { | ||||
|         // Ok so you would think that the id would be the id of the solid, | ||||
|         // that we passed in to the function, but it's actually the id of the | ||||
|         // sketch. | ||||
| @ -296,7 +299,7 @@ pub(crate) async fn do_post_extrude( | ||||
|         start_cap_id, | ||||
|         end_cap_id, | ||||
|         edge_cuts: vec![], | ||||
|     })) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| #[derive(Default)] | ||||
|  | ||||
| @ -14,7 +14,10 @@ use uuid::Uuid; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier}, | ||||
|     execution::{ | ||||
|         kcl_value::RuntimeType, EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, PrimitiveType, | ||||
|         Solid, TagIdentifier, | ||||
|     }, | ||||
|     parsing::ast::types::TagNode, | ||||
|     settings::types::UnitLength, | ||||
|     std::Args, | ||||
| @ -64,8 +67,7 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)] | ||||
|  | ||||
| /// Create fillets on tagged paths. | ||||
| pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     // Get all args: | ||||
|     let solid = args.get_unlabeled_kw_arg("solid")?; | ||||
|     let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?; | ||||
|     let radius = args.get_kw_arg("radius")?; | ||||
|     let tolerance = args.get_kw_arg_opt("tolerance")?; | ||||
|     let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?; | ||||
|  | ||||
| @ -196,7 +196,7 @@ pub struct HelixRevolutionsData { | ||||
|  | ||||
| /// Create a helix on a cylinder. | ||||
| pub async fn helix_revolutions(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, solid): (HelixRevolutionsData, Box<Solid>) = args.get_data_and_solid()?; | ||||
|     let (data, solid): (HelixRevolutionsData, Box<Solid>) = args.get_data_and_solid(exec_state)?; | ||||
|  | ||||
|     let value = inner_helix_revolutions(data, solid, exec_state, args).await?; | ||||
|     Ok(KclValue::Solid { value }) | ||||
|  | ||||
| @ -9,7 +9,10 @@ use kittycad_modeling_cmds as kcmc; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, Sketch, Solid}, | ||||
|     execution::{ | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ExecState, KclValue, PrimitiveType, Sketch, Solid, | ||||
|     }, | ||||
|     std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, | ||||
| }; | ||||
|  | ||||
| @ -17,7 +20,11 @@ const DEFAULT_V_DEGREE: u32 = 2; | ||||
|  | ||||
| /// Create a 3D surface or solid by interpolating between two or more sketches. | ||||
| pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketches = args.get_unlabeled_kw_arg("sketches")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let v_degree: NonZeroU32 = args | ||||
|         .get_kw_arg_opt("vDegree")? | ||||
|         .unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()); | ||||
| @ -159,5 +166,7 @@ async fn inner_loft( | ||||
|     let mut sketch = sketches[0].clone(); | ||||
|     // Override its id with the loft id so we can get its faces later | ||||
|     sketch.id = id; | ||||
|     do_post_extrude(sketch, id.into(), 0.0, exec_state, args).await | ||||
|     Ok(Box::new( | ||||
|         do_post_extrude(sketch, id.into(), 0.0, exec_state, args).await?, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| @ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::KclError, | ||||
|     execution::{ExecState, KclValue, Sketch, SketchSet}, | ||||
|     execution::{ExecState, KclValue, Sketch}, | ||||
|     std::{axis_or_reference::Axis2dOrEdgeReference, Args}, | ||||
| }; | ||||
|  | ||||
| @ -26,7 +26,7 @@ pub struct Mirror2dData { | ||||
| /// | ||||
| /// Only works on unclosed sketches for now. | ||||
| pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch_set): (Mirror2dData, SketchSet) = args.get_data_and_sketch_set()?; | ||||
|     let (data, sketch_set): (Mirror2dData, Vec<Sketch>) = args.get_data_and_sketches(exec_state)?; | ||||
|  | ||||
|     let sketches = inner_mirror_2d(data, sketch_set, exec_state, args).await?; | ||||
|     Ok(sketches.into()) | ||||
| @ -103,14 +103,11 @@ pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValu | ||||
| }] | ||||
| async fn inner_mirror_2d( | ||||
|     data: Mirror2dData, | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Vec<Box<Sketch>>, KclError> { | ||||
|     let starting_sketches = match sketch_set { | ||||
|         SketchSet::Sketch(sketch) => vec![sketch], | ||||
|         SketchSet::Sketches(sketches) => sketches, | ||||
|     }; | ||||
| ) -> Result<Vec<Sketch>, KclError> { | ||||
|     let starting_sketches = sketches; | ||||
|  | ||||
|     if args.ctx.no_engine_commands().await { | ||||
|         return Ok(starting_sketches); | ||||
|  | ||||
| @ -20,9 +20,8 @@ use super::args::Arg; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
|         kcl_value::{FunctionSource, NumericType}, | ||||
|         ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, | ||||
|         SolidSet, | ||||
|         kcl_value::{ArrayLen, FunctionSource, NumericType, RuntimeType}, | ||||
|         ExecState, Geometries, Geometry, KclObjectFields, KclValue, Point2d, Point3d, PrimitiveType, Sketch, Solid, | ||||
|     }, | ||||
|     std::Args, | ||||
|     ExecutorContext, SourceRange, | ||||
| @ -48,25 +47,32 @@ pub struct LinearPattern3dData { | ||||
|  | ||||
| /// Repeat some 3D solid, changing each repetition slightly. | ||||
| pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set = args.get_unlabeled_kw_arg("solidSet")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let instances: u32 = args.get_kw_arg("instances")?; | ||||
|     let transform: &FunctionSource = args.get_kw_arg("transform")?; | ||||
|     let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; | ||||
|  | ||||
|     let solids = inner_pattern_transform(solid_set, instances, transform, use_original, exec_state, &args).await?; | ||||
|     Ok(KclValue::Solids { value: solids }) | ||||
|     let solids = inner_pattern_transform(solids, instances, transform, use_original, exec_state, &args).await?; | ||||
|     Ok(solids.into()) | ||||
| } | ||||
|  | ||||
| /// Repeat some 2D sketch, changing each repetition slightly. | ||||
| pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch_set = args.get_unlabeled_kw_arg("sketchSet")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let instances: u32 = args.get_kw_arg("instances")?; | ||||
|     let transform: &FunctionSource = args.get_kw_arg("transform")?; | ||||
|     let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; | ||||
|  | ||||
|     let sketches = | ||||
|         inner_pattern_transform_2d(sketch_set, instances, transform, use_original, exec_state, &args).await?; | ||||
|     Ok(KclValue::Sketches { value: sketches }) | ||||
|     let sketches = inner_pattern_transform_2d(sketches, instances, transform, use_original, exec_state, &args).await?; | ||||
|     Ok(sketches.into()) | ||||
| } | ||||
|  | ||||
| /// Repeat a 3-dimensional solid, changing it each time. | ||||
| @ -258,20 +264,20 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = { docs = "The solid(s) to duplicate" }, | ||||
|         solids = { docs = "The solid(s) to duplicate" }, | ||||
|         instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." }, | ||||
|         transform = { docs = "How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples." }, | ||||
|         use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." }, | ||||
|     } | ||||
| }] | ||||
| async fn inner_pattern_transform<'a>( | ||||
|     solid_set: SolidSet, | ||||
|     solids: Vec<Solid>, | ||||
|     instances: u32, | ||||
|     transform: &'a FunctionSource, | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: &'a Args, | ||||
| ) -> Result<Vec<Box<Solid>>, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     // Build the vec of transforms, one for each repetition. | ||||
|     let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap()); | ||||
|     if instances < 1 { | ||||
| @ -281,12 +287,12 @@ async fn inner_pattern_transform<'a>( | ||||
|         })); | ||||
|     } | ||||
|     for i in 1..instances { | ||||
|         let t = make_transform::<Box<Solid>>(i, transform, args.source_range, exec_state, &args.ctx).await?; | ||||
|         let t = make_transform::<Solid>(i, transform, args.source_range, exec_state, &args.ctx).await?; | ||||
|         transform_vec.push(t); | ||||
|     } | ||||
|     execute_pattern_transform( | ||||
|         transform_vec, | ||||
|         solid_set, | ||||
|         solids, | ||||
|         use_original.unwrap_or_default(), | ||||
|         exec_state, | ||||
|         args, | ||||
| @ -311,20 +317,20 @@ async fn inner_pattern_transform<'a>( | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         sketch_set = { docs = "The sketch(es) to duplicate" }, | ||||
|         sketches = { docs = "The sketch(es) to duplicate" }, | ||||
|         instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." }, | ||||
|         transform = { docs = "How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples." }, | ||||
|         use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." }, | ||||
|     } | ||||
| }] | ||||
| async fn inner_pattern_transform_2d<'a>( | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     instances: u32, | ||||
|     transform: &'a FunctionSource, | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: &'a Args, | ||||
| ) -> Result<Vec<Box<Sketch>>, KclError> { | ||||
| ) -> Result<Vec<Sketch>, KclError> { | ||||
|     // Build the vec of transforms, one for each repetition. | ||||
|     let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap()); | ||||
|     if instances < 1 { | ||||
| @ -334,12 +340,12 @@ async fn inner_pattern_transform_2d<'a>( | ||||
|         })); | ||||
|     } | ||||
|     for i in 1..instances { | ||||
|         let t = make_transform::<Box<Sketch>>(i, transform, args.source_range, exec_state, &args.ctx).await?; | ||||
|         let t = make_transform::<Sketch>(i, transform, args.source_range, exec_state, &args.ctx).await?; | ||||
|         transform_vec.push(t); | ||||
|     } | ||||
|     execute_pattern_transform( | ||||
|         transform_vec, | ||||
|         sketch_set, | ||||
|         sketches, | ||||
|         use_original.unwrap_or_default(), | ||||
|         exec_state, | ||||
|         args, | ||||
| @ -611,8 +617,8 @@ trait GeometryTrait: Clone { | ||||
|     async fn flush_batch(args: &Args, exec_state: &mut ExecState, set: Self::Set) -> Result<(), KclError>; | ||||
| } | ||||
|  | ||||
| impl GeometryTrait for Box<Sketch> { | ||||
|     type Set = SketchSet; | ||||
| impl GeometryTrait for Sketch { | ||||
|     type Set = Vec<Sketch>; | ||||
|     fn set_id(&mut self, id: Uuid) { | ||||
|         self.id = id; | ||||
|     } | ||||
| @ -632,8 +638,8 @@ impl GeometryTrait for Box<Sketch> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl GeometryTrait for Box<Solid> { | ||||
|     type Set = SolidSet; | ||||
| impl GeometryTrait for Solid { | ||||
|     type Set = Vec<Solid>; | ||||
|     fn set_id(&mut self, id: Uuid) { | ||||
|         self.id = id; | ||||
|     } | ||||
| @ -651,7 +657,7 @@ impl GeometryTrait for Box<Solid> { | ||||
|     } | ||||
|  | ||||
|     async fn flush_batch(args: &Args, exec_state: &mut ExecState, solid_set: Self::Set) -> Result<(), KclError> { | ||||
|         args.flush_batch_for_solid_set(exec_state, solid_set.into()).await | ||||
|         args.flush_batch_for_solids(exec_state, solid_set).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -690,7 +696,11 @@ mod tests { | ||||
|  | ||||
| /// A linear pattern on a 2D sketch. | ||||
| pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let instances: u32 = args.get_kw_arg("instances")?; | ||||
|     let distance: f64 = args.get_kw_arg("distance")?; | ||||
|     let axis: [f64; 2] = args.get_kw_arg("axis")?; | ||||
| @ -705,8 +715,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     let sketches = | ||||
|         inner_pattern_linear_2d(sketch_set, instances, distance, axis, use_original, exec_state, args).await?; | ||||
|     let sketches = inner_pattern_linear_2d(sketches, instances, distance, axis, use_original, exec_state, args).await?; | ||||
|     Ok(sketches.into()) | ||||
| } | ||||
|  | ||||
| @ -729,7 +738,7 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         sketch_set = { docs = "The sketch(es) to duplicate" }, | ||||
|         sketches = { docs = "The sketch(es) to duplicate" }, | ||||
|         instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." }, | ||||
|         distance = { docs = "Distance between each repetition. Also known as 'spacing'."}, | ||||
|         axis = { docs = "The axis of the pattern. A 2D vector." }, | ||||
| @ -737,14 +746,14 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|     } | ||||
| }] | ||||
| async fn inner_pattern_linear_2d( | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     instances: u32, | ||||
|     distance: f64, | ||||
|     axis: [f64; 2], | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Vec<Box<Sketch>>, KclError> { | ||||
| ) -> Result<Vec<Sketch>, KclError> { | ||||
|     let [x, y] = axis; | ||||
|     let axis_len = f64::sqrt(x * x + y * y); | ||||
|     let normalized_axis = kcmc::shared::Point2d::from([x / axis_len, y / axis_len]); | ||||
| @ -760,7 +769,7 @@ async fn inner_pattern_linear_2d( | ||||
|         .collect(); | ||||
|     execute_pattern_transform( | ||||
|         transforms, | ||||
|         sketch_set, | ||||
|         sketches, | ||||
|         use_original.unwrap_or_default(), | ||||
|         exec_state, | ||||
|         &args, | ||||
| @ -770,7 +779,11 @@ async fn inner_pattern_linear_2d( | ||||
|  | ||||
| /// A linear pattern on a 3D model. | ||||
| pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let instances: u32 = args.get_kw_arg("instances")?; | ||||
|     let distance: f64 = args.get_kw_arg("distance")?; | ||||
|     let axis: [f64; 3] = args.get_kw_arg("axis")?; | ||||
| @ -785,7 +798,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     let solids = inner_pattern_linear_3d(solid_set, instances, distance, axis, use_original, exec_state, args).await?; | ||||
|     let solids = inner_pattern_linear_3d(solids, instances, distance, axis, use_original, exec_state, args).await?; | ||||
|     Ok(solids.into()) | ||||
| } | ||||
|  | ||||
| @ -866,7 +879,7 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = { docs = "The solid(s) to duplicate" }, | ||||
|         solids = { docs = "The solid(s) to duplicate" }, | ||||
|         instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." }, | ||||
|         distance = { docs = "Distance between each repetition. Also known as 'spacing'."}, | ||||
|         axis = { docs = "The axis of the pattern. A 2D vector." }, | ||||
| @ -874,14 +887,14 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result | ||||
|     } | ||||
| }] | ||||
| async fn inner_pattern_linear_3d( | ||||
|     solid_set: SolidSet, | ||||
|     solids: Vec<Solid>, | ||||
|     instances: u32, | ||||
|     distance: f64, | ||||
|     axis: [f64; 3], | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Vec<Box<Solid>>, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     let [x, y, z] = axis; | ||||
|     let axis_len = f64::sqrt(x * x + y * y + z * z); | ||||
|     let normalized_axis = kcmc::shared::Point3d::from([x / axis_len, y / axis_len, z / axis_len]); | ||||
| @ -895,14 +908,7 @@ async fn inner_pattern_linear_3d( | ||||
|             }] | ||||
|         }) | ||||
|         .collect(); | ||||
|     execute_pattern_transform( | ||||
|         transforms, | ||||
|         solid_set, | ||||
|         use_original.unwrap_or_default(), | ||||
|         exec_state, | ||||
|         &args, | ||||
|     ) | ||||
|     .await | ||||
|     execute_pattern_transform(transforms, solids, use_original.unwrap_or_default(), exec_state, &args).await | ||||
| } | ||||
|  | ||||
| /// Data for a circular pattern on a 2D sketch. | ||||
| @ -1022,7 +1028,11 @@ impl CircularPattern { | ||||
|  | ||||
| /// A circular pattern on a 2D sketch. | ||||
| pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch_set: SketchSet = args.get_unlabeled_kw_arg("sketchSet")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let instances: u32 = args.get_kw_arg("instances")?; | ||||
|     let center: [f64; 2] = args.get_kw_arg("center")?; | ||||
|     let arc_degrees: f64 = args.get_kw_arg("arcDegrees")?; | ||||
| @ -1030,7 +1040,7 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu | ||||
|     let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; | ||||
|  | ||||
|     let sketches = inner_pattern_circular_2d( | ||||
|         sketch_set, | ||||
|         sketches, | ||||
|         instances, | ||||
|         center, | ||||
|         arc_degrees, | ||||
| @ -1079,7 +1089,7 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu | ||||
| }] | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| async fn inner_pattern_circular_2d( | ||||
|     sketch_set: SketchSet, | ||||
|     sketch_set: Vec<Sketch>, | ||||
|     instances: u32, | ||||
|     center: [f64; 2], | ||||
|     arc_degrees: f64, | ||||
| @ -1087,8 +1097,8 @@ async fn inner_pattern_circular_2d( | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Vec<Box<Sketch>>, KclError> { | ||||
|     let starting_sketches: Vec<Box<Sketch>> = sketch_set.into(); | ||||
| ) -> Result<Vec<Sketch>, KclError> { | ||||
|     let starting_sketches = sketch_set; | ||||
|  | ||||
|     if args.ctx.context_type == crate::execution::ContextType::Mock { | ||||
|         return Ok(starting_sketches); | ||||
| @ -1126,7 +1136,11 @@ async fn inner_pattern_circular_2d( | ||||
|  | ||||
| /// A circular pattern on a 3D model. | ||||
| pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     // The number of total instances. Must be greater than or equal to 1. | ||||
|     // This includes the original entity. For example, if instances is 2, | ||||
|     // there will be two copies -- the original, and one new copy. | ||||
| @ -1145,7 +1159,7 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu | ||||
|     let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?; | ||||
|  | ||||
|     let solids = inner_pattern_circular_3d( | ||||
|         solid_set, | ||||
|         solids, | ||||
|         instances, | ||||
|         axis, | ||||
|         center, | ||||
| @ -1183,7 +1197,7 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = { docs = "Which solid(s) to pattern" }, | ||||
|         solids = { docs = "Which solid(s) to pattern" }, | ||||
|         instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."}, | ||||
|         axis = { docs = "The axis around which to make the pattern. This is a 3D vector"}, | ||||
|         center = { docs = "The center about which to make the pattern. This is a 3D vector."}, | ||||
| @ -1194,7 +1208,7 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu | ||||
| }] | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| async fn inner_pattern_circular_3d( | ||||
|     solid_set: SolidSet, | ||||
|     solids: Vec<Solid>, | ||||
|     instances: u32, | ||||
|     axis: [f64; 3], | ||||
|     center: [f64; 3], | ||||
| @ -1203,14 +1217,13 @@ async fn inner_pattern_circular_3d( | ||||
|     use_original: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Vec<Box<Solid>>, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     // Flush the batch for our fillets/chamfers if there are any. | ||||
|     // If we do not flush these, then you won't be able to pattern something with fillets. | ||||
|     // Flush just the fillets/chamfers that apply to these solids. | ||||
|     args.flush_batch_for_solid_set(exec_state, solid_set.clone().into()) | ||||
|         .await?; | ||||
|     args.flush_batch_for_solids(exec_state, solids.clone()).await?; | ||||
|  | ||||
|     let starting_solids: Vec<Box<Solid>> = solid_set.into(); | ||||
|     let starting_solids = solids; | ||||
|  | ||||
|     if args.ctx.context_type == crate::execution::ContextType::Mock { | ||||
|         return Ok(starting_solids); | ||||
|  | ||||
| @ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, Sketch, SketchSet, SolidSet}, | ||||
|     execution::{ExecState, KclValue, Sketch, Solid}, | ||||
|     std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, fillet::default_tolerance, Args}, | ||||
| }; | ||||
|  | ||||
| @ -30,9 +30,9 @@ pub struct RevolveData { | ||||
|  | ||||
| /// Revolve a sketch or set of sketches around an axis. | ||||
| pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch_set): (RevolveData, SketchSet) = args.get_data_and_sketch_set()?; | ||||
|     let (data, sketches): (RevolveData, _) = args.get_data_and_sketches(exec_state)?; | ||||
|  | ||||
|     let value = inner_revolve(data, sketch_set, exec_state, args).await?; | ||||
|     let value = inner_revolve(data, sketches, exec_state, args).await?; | ||||
|     Ok(value.into()) | ||||
| } | ||||
|  | ||||
| @ -206,10 +206,10 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
| }] | ||||
| async fn inner_revolve( | ||||
|     data: RevolveData, | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidSet, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     if let Some(angle) = data.angle { | ||||
|         // Return an error if the angle is zero. | ||||
|         // We don't use validate() here because we want to return a specific error message that is | ||||
| @ -224,7 +224,6 @@ async fn inner_revolve( | ||||
|  | ||||
|     let angle = Angle::from_degrees(data.angle.unwrap_or(360.0)); | ||||
|  | ||||
|     let sketches: Vec<Sketch> = sketch_set.into(); | ||||
|     let mut solids = Vec::new(); | ||||
|     for sketch in &sketches { | ||||
|         let id = exec_state.next_uuid(); | ||||
| @ -263,5 +262,5 @@ async fn inner_revolve( | ||||
|         solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?); | ||||
|     } | ||||
|  | ||||
|     Ok(solids.into()) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,7 @@ use kittycad_modeling_cmds::shared::Angle; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, Point2d, Sketch, TagIdentifier}, | ||||
|     execution::{kcl_value::RuntimeType, ExecState, KclValue, Point2d, PrimitiveType, Sketch, TagIdentifier}, | ||||
|     std::{utils::between, Args}, | ||||
| }; | ||||
|  | ||||
| @ -282,8 +282,9 @@ fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: | ||||
|     Ok(path.get_from()[1]) | ||||
| } | ||||
| /// Returns the last segment of x. | ||||
| pub async fn last_segment_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
| pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let result = inner_last_segment_x(sketch, args.clone())?; | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64(result)) | ||||
| @ -327,8 +328,9 @@ fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<f64, KclError> { | ||||
| } | ||||
|  | ||||
| /// Returns the last segment of y. | ||||
| pub async fn last_segment_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
| pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let result = inner_last_segment_y(sketch, args.clone())?; | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64(result)) | ||||
|  | ||||
| @ -139,7 +139,7 @@ async fn inner_circle( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -251,7 +251,7 @@ async fn inner_circle_three_point( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -414,7 +414,7 @@ async fn inner_polygon( | ||||
|         }; | ||||
|  | ||||
|         if let Some(tag) = &tag { | ||||
|             sketch.add_tag(tag, ¤t_path); | ||||
|             sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|         } | ||||
|  | ||||
|         sketch.paths.push(current_path); | ||||
| @ -450,7 +450,7 @@ async fn inner_polygon( | ||||
|     }; | ||||
|  | ||||
|     if let Some(tag) = &tag { | ||||
|         sketch.add_tag(tag, ¤t_path); | ||||
|         sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     sketch.paths.push(current_path); | ||||
|  | ||||
| @ -7,17 +7,24 @@ use kittycad_modeling_cmds as kcmc; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, Solid, SolidSet}, | ||||
|     execution::{ | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ExecState, KclValue, PrimitiveType, Solid, | ||||
|     }, | ||||
|     std::{sketch::FaceTag, Args}, | ||||
| }; | ||||
|  | ||||
| /// Create a shell. | ||||
| pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set = args.get_unlabeled_kw_arg("solidSet")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let thickness = args.get_kw_arg("thickness")?; | ||||
|     let faces = args.get_kw_arg("faces")?; | ||||
|  | ||||
|     let result = inner_shell(solid_set, thickness, faces, exec_state, args).await?; | ||||
|     let result = inner_shell(solids, thickness, faces, exec_state, args).await?; | ||||
|     Ok(result.into()) | ||||
| } | ||||
|  | ||||
| @ -173,18 +180,18 @@ pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = { docs = "Which solid (or solids) to shell out"}, | ||||
|         solids = { docs = "Which solid (or solids) to shell out"}, | ||||
|         thickness = {docs = "The thickness of the shell"}, | ||||
|         faces = {docs = "The faces you want removed"}, | ||||
|     } | ||||
| }] | ||||
| async fn inner_shell( | ||||
|     solid_set: SolidSet, | ||||
|     solids: Vec<Solid>, | ||||
|     thickness: f64, | ||||
|     faces: Vec<FaceTag>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidSet, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     if faces.is_empty() { | ||||
|         return Err(KclError::Type(KclErrorDetails { | ||||
|             message: "You must shell at least one face".to_string(), | ||||
| @ -192,7 +199,6 @@ async fn inner_shell( | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     let solids: Vec<Box<Solid>> = solid_set.clone().into(); | ||||
|     if solids.is_empty() { | ||||
|         return Err(KclError::Type(KclErrorDetails { | ||||
|             message: "You must shell at least one solid".to_string(), | ||||
| @ -204,7 +210,7 @@ async fn inner_shell( | ||||
|     for solid in &solids { | ||||
|         // Flush the batch for our fillets/chamfers if there are any. | ||||
|         // If we do not do these for sketch on face, things will fail with face does not exist. | ||||
|         args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?; | ||||
|         args.flush_batch_for_solids(exec_state, vec![solid.clone()]).await?; | ||||
|  | ||||
|         for tag in &faces { | ||||
|             let extrude_plane_id = tag.get_face_id(solid, exec_state, &args, false).await?; | ||||
| @ -241,12 +247,12 @@ async fn inner_shell( | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     Ok(solid_set) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| /// Make the inside of a 3D object hollow. | ||||
| pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (thickness, solid): (f64, Box<Solid>) = args.get_data_and_solid()?; | ||||
|     let (thickness, solid): (f64, Box<Solid>) = args.get_data_and_solid(exec_state)?; | ||||
|  | ||||
|     let value = inner_hollow(thickness, solid, exec_state, args).await?; | ||||
|     Ok(KclValue::Solid { value }) | ||||
| @ -314,7 +320,7 @@ async fn inner_hollow( | ||||
| ) -> Result<Box<Solid>, KclError> { | ||||
|     // Flush the batch for our fillets/chamfers if there are any. | ||||
|     // If we do not do these for sketch on face, things will fail with face does not exist. | ||||
|     args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?; | ||||
|     args.flush_batch_for_solids(exec_state, vec![(*solid).clone()]).await?; | ||||
|  | ||||
|     args.batch_modeling_cmd( | ||||
|         exec_state.next_uuid(), | ||||
|  | ||||
| @ -11,11 +11,13 @@ use parse_display::{Display, FromStr}; | ||||
| use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::execution::kcl_value::RuntimeType; | ||||
| use crate::execution::PrimitiveType; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
|         Artifact, ArtifactId, BasePath, CodeRef, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, | ||||
|         Sketch, SketchSet, SketchSurface, Solid, StartSketchOnFace, StartSketchOnPlane, TagEngineInfo, TagIdentifier, | ||||
|         Sketch, SketchSurface, Solid, StartSketchOnFace, StartSketchOnPlane, TagEngineInfo, TagIdentifier, | ||||
|     }, | ||||
|     parsing::ast::types::TagNode, | ||||
|     std::{ | ||||
| @ -96,7 +98,8 @@ pub const NEW_TAG_KW: &str = "tag"; | ||||
| /// Draw a line to a point. | ||||
| pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     // let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let end = args.get_kw_arg_opt("end")?; | ||||
|     let end_absolute = args.get_kw_arg_opt("endAbsolute")?; | ||||
|     let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; | ||||
| @ -253,7 +256,7 @@ async fn straight_line( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -263,7 +266,8 @@ async fn straight_line( | ||||
|  | ||||
| /// Draw a line on the x-axis. | ||||
| pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let length = args.get_kw_arg_opt("length")?; | ||||
|     let end_absolute = args.get_kw_arg_opt("endAbsolute")?; | ||||
|     let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; | ||||
| @ -331,7 +335,8 @@ async fn inner_x_line( | ||||
|  | ||||
| /// Draw a line on the y-axis. | ||||
| pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let length = args.get_kw_arg_opt("length")?; | ||||
|     let end_absolute = args.get_kw_arg_opt("endAbsolute")?; | ||||
|     let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; | ||||
| @ -410,7 +415,8 @@ pub enum AngledLineData { | ||||
|  | ||||
| /// Draw an angled line. | ||||
| pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_angled_line(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -489,7 +495,7 @@ async fn inner_angled_line( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -498,7 +504,8 @@ async fn inner_angled_line( | ||||
|  | ||||
| /// Draw an angled line of a given x length. | ||||
| pub async fn angled_line_of_x_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -568,7 +575,8 @@ pub struct AngledLineToData { | ||||
|  | ||||
| /// Draw an angled line to a given x coordinate. | ||||
| pub async fn angled_line_to_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_angled_line_to_x(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -632,7 +640,8 @@ async fn inner_angled_line_to_x( | ||||
|  | ||||
| /// Draw an angled line of a given y length. | ||||
| pub async fn angled_line_of_y_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (AngledLineData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, exec_state, args).await?; | ||||
|  | ||||
| @ -694,7 +703,8 @@ async fn inner_angled_line_of_y_length( | ||||
|  | ||||
| /// Draw an angled line to a given y coordinate. | ||||
| pub async fn angled_line_to_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_angled_line_to_y(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -773,7 +783,7 @@ pub struct AngledLineThatIntersectsData { | ||||
| /// Draw an angled line that intersects with a given line. | ||||
| pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (AngledLineThatIntersectsData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag()?; | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|     let new_sketch = inner_angled_line_that_intersects(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
|         value: Box::new(new_sketch), | ||||
| @ -1098,6 +1108,8 @@ async fn make_sketch_plane_from_orientation( | ||||
|     let hide = Some(true); | ||||
|     match data { | ||||
|         PlaneData::XY | PlaneData::NegXY | PlaneData::XZ | PlaneData::NegXZ | PlaneData::YZ | PlaneData::NegYZ => { | ||||
|             // TODO: ignoring the default planes here since we already created them, breaks the | ||||
|             // front end for the feature tree which is stupid and we should fix it. | ||||
|             let x_axis = match data { | ||||
|                 PlaneData::NegXY => Point3d::new(-1.0, 0.0, 0.0), | ||||
|                 PlaneData::NegXZ => Point3d::new(-1.0, 0.0, 0.0), | ||||
| @ -1200,7 +1212,7 @@ pub(crate) async fn inner_start_profile_at( | ||||
|         SketchSurface::Face(face) => { | ||||
|             // Flush the batch for our fillets/chamfers if there are any. | ||||
|             // If we do not do these for sketch on face, things will fail with face does not exist. | ||||
|             args.flush_batch_for_solid_set(exec_state, face.solid.clone().into()) | ||||
|             args.flush_batch_for_solids(exec_state, vec![(*face.solid).clone()]) | ||||
|                 .await?; | ||||
|         } | ||||
|         SketchSurface::Plane(plane) if !plane.is_standard() => { | ||||
| @ -1278,14 +1290,17 @@ pub(crate) async fn inner_start_profile_at( | ||||
|         meta: vec![args.source_range.into()], | ||||
|         tags: if let Some(tag) = &tag { | ||||
|             let mut tag_identifier: TagIdentifier = tag.into(); | ||||
|             tag_identifier.info = Some(TagEngineInfo { | ||||
|                 id: current_path.geo_meta.id, | ||||
|                 sketch: path_id, | ||||
|                 path: Some(Path::Base { | ||||
|                     base: current_path.clone(), | ||||
|                 }), | ||||
|                 surface: None, | ||||
|             }); | ||||
|             tag_identifier.info = vec![( | ||||
|                 exec_state.stack().current_epoch(), | ||||
|                 TagEngineInfo { | ||||
|                     id: current_path.geo_meta.id, | ||||
|                     sketch: path_id, | ||||
|                     path: Some(Path::Base { | ||||
|                         base: current_path.clone(), | ||||
|                     }), | ||||
|                     surface: None, | ||||
|                 }, | ||||
|             )]; | ||||
|             IndexMap::from([(tag.name.to_string(), tag_identifier)]) | ||||
|         } else { | ||||
|             Default::default() | ||||
| @ -1296,8 +1311,8 @@ pub(crate) async fn inner_start_profile_at( | ||||
| } | ||||
|  | ||||
| /// Returns the X component of the sketch profile start point. | ||||
| pub async fn profile_start_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch()?; | ||||
| pub async fn profile_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch(exec_state)?; | ||||
|     let ty = sketch.units.into(); | ||||
|     let x = inner_profile_start_x(sketch)?; | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty))) | ||||
| @ -1321,8 +1336,8 @@ pub(crate) fn inner_profile_start_x(sketch: Sketch) -> Result<f64, KclError> { | ||||
| } | ||||
|  | ||||
| /// Returns the Y component of the sketch profile start point. | ||||
| pub async fn profile_start_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch()?; | ||||
| pub async fn profile_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch(exec_state)?; | ||||
|     let ty = sketch.units.into(); | ||||
|     let x = inner_profile_start_y(sketch)?; | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty))) | ||||
| @ -1345,8 +1360,8 @@ pub(crate) fn inner_profile_start_y(sketch: Sketch) -> Result<f64, KclError> { | ||||
| } | ||||
|  | ||||
| /// Returns the sketch profile start point. | ||||
| pub async fn profile_start(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch()?; | ||||
| pub async fn profile_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch: Sketch = args.get_sketch(exec_state)?; | ||||
|     let ty = sketch.units.into(); | ||||
|     let point = inner_profile_start(sketch)?; | ||||
|     Ok(KclValue::from_point2d(point, ty, args.into())) | ||||
| @ -1373,7 +1388,8 @@ pub(crate) fn inner_profile_start(sketch: Sketch) -> Result<[f64; 2], KclError> | ||||
|  | ||||
| /// Close the current sketch. | ||||
| pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch")?; | ||||
|     let sketch = | ||||
|         args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?; | ||||
|     let tag = args.get_kw_arg_opt(NEW_TAG_KW)?; | ||||
|     let new_sketch = inner_close(sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1440,7 +1456,7 @@ pub(crate) async fn inner_close( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -1490,7 +1506,7 @@ pub struct ArcToData { | ||||
|  | ||||
| /// Draw an arc. | ||||
| pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_arc(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1593,7 +1609,7 @@ pub(crate) async fn inner_arc( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -1603,7 +1619,7 @@ pub(crate) async fn inner_arc( | ||||
|  | ||||
| /// Draw a three point arc. | ||||
| pub async fn arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (ArcToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (ArcToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_arc_to(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1695,7 +1711,7 @@ pub(crate) async fn inner_arc_to( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -1739,7 +1755,8 @@ pub enum TangentialArcData { | ||||
|  | ||||
| /// Draw a tangential arc. | ||||
| pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagNode>) = | ||||
|         args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_tangential_arc(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1846,7 +1863,7 @@ async fn inner_tangential_arc( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -1866,7 +1883,7 @@ fn tan_arc_to(sketch: &Sketch, to: &[f64; 2]) -> ModelingCmd { | ||||
|  | ||||
| /// Draw a tangential arc to a specific point. | ||||
| pub async fn tangential_arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?; | ||||
|     let (to, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_tangential_arc_to(to, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1876,7 +1893,7 @@ pub async fn tangential_arc_to(exec_state: &mut ExecState, args: Args) -> Result | ||||
|  | ||||
| /// Draw a tangential arc to point some distance away.. | ||||
| pub async fn tangential_arc_to_relative(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = super::args::FromArgs::from_args(&args, 0)?; | ||||
|     let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -1943,7 +1960,7 @@ async fn inner_tangential_arc_to( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -2027,7 +2044,7 @@ async fn inner_tangential_arc_to_relative( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -2050,7 +2067,7 @@ pub struct BezierData { | ||||
|  | ||||
| /// Draw a bezier curve. | ||||
| pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (data, sketch, tag): (BezierData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; | ||||
|     let (data, sketch, tag): (BezierData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_bezier_curve(data, sketch, tag, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -2123,7 +2140,7 @@ async fn inner_bezier_curve( | ||||
|  | ||||
|     let mut new_sketch = sketch.clone(); | ||||
|     if let Some(tag) = &tag { | ||||
|         new_sketch.add_tag(tag, ¤t_path); | ||||
|         new_sketch.add_tag(tag, ¤t_path, exec_state); | ||||
|     } | ||||
|  | ||||
|     new_sketch.paths.push(current_path); | ||||
| @ -2133,7 +2150,7 @@ async fn inner_bezier_curve( | ||||
|  | ||||
| /// Use a sketch to cut a hole in another sketch. | ||||
| pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?; | ||||
|     let (hole_sketch, sketch): (Vec<Sketch>, Sketch) = args.get_sketches(exec_state)?; | ||||
|  | ||||
|     let new_sketch = inner_hole(hole_sketch, sketch, exec_state, args).await?; | ||||
|     Ok(KclValue::Sketch { | ||||
| @ -2177,13 +2194,12 @@ pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc | ||||
|     feature_tree_operation = true, | ||||
| }] | ||||
| async fn inner_hole( | ||||
|     hole_sketch: SketchSet, | ||||
|     hole_sketch: Vec<Sketch>, | ||||
|     sketch: Sketch, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<Sketch, KclError> { | ||||
|     let hole_sketches: Vec<Sketch> = hole_sketch.into(); | ||||
|     for hole_sketch in hole_sketches { | ||||
|     for hole_sketch in hole_sketch { | ||||
|         args.batch_modeling_cmd( | ||||
|             exec_state.next_uuid(), | ||||
|             ModelingCmd::from(mcmd::Solid2dAddHole { | ||||
| @ -2252,7 +2268,7 @@ mod tests { | ||||
|  | ||||
|         str_json = serde_json::to_string(&TagIdentifier { | ||||
|             value: "thing".to_string(), | ||||
|             info: None, | ||||
|             info: Vec::new(), | ||||
|             meta: Default::default(), | ||||
|         }) | ||||
|         .unwrap(); | ||||
| @ -2261,7 +2277,7 @@ mod tests { | ||||
|             data, | ||||
|             crate::std::sketch::FaceTag::Tag(Box::new(TagIdentifier { | ||||
|                 value: "thing".to_string(), | ||||
|                 info: None, | ||||
|                 info: Vec::new(), | ||||
|                 meta: Default::default() | ||||
|             })) | ||||
|         ); | ||||
|  | ||||
| @ -9,7 +9,10 @@ use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::KclError, | ||||
|     execution::{ExecState, Helix, KclValue, Sketch, SketchSet, SolidSet}, | ||||
|     execution::{ | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ExecState, Helix, KclValue, PrimitiveType, Sketch, Solid, | ||||
|     }, | ||||
|     std::{extrude::do_post_extrude, fillet::default_tolerance, Args}, | ||||
| }; | ||||
|  | ||||
| @ -24,12 +27,16 @@ pub enum SweepPath { | ||||
|  | ||||
| /// Extrude a sketch along a path. | ||||
| pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch_set = args.get_unlabeled_kw_arg("sketch_set")?; | ||||
|     let sketches = args.get_unlabeled_kw_arg_typed( | ||||
|         "sketches", | ||||
|         &RuntimeType::Array(PrimitiveType::Sketch, ArrayLen::NonEmpty), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let path: SweepPath = args.get_kw_arg("path")?; | ||||
|     let sectional = args.get_kw_arg_opt("sectional")?; | ||||
|     let tolerance = args.get_kw_arg_opt("tolerance")?; | ||||
|  | ||||
|     let value = inner_sweep(sketch_set, path, sectional, tolerance, exec_state, args).await?; | ||||
|     let value = inner_sweep(sketches, path, sectional, tolerance, exec_state, args).await?; | ||||
|     Ok(value.into()) | ||||
| } | ||||
|  | ||||
| @ -134,26 +141,25 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         sketch_set = { docs = "The sketch or set of sketches that should be swept in space" }, | ||||
|         sketches = { docs = "The sketch or set of sketches that should be swept in space" }, | ||||
|         path = { docs = "The path to sweep the sketch along" }, | ||||
|         sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." }, | ||||
|         tolerance = { docs = "Tolerance for this operation" }, | ||||
|     } | ||||
| }] | ||||
| async fn inner_sweep( | ||||
|     sketch_set: SketchSet, | ||||
|     sketches: Vec<Sketch>, | ||||
|     path: SweepPath, | ||||
|     sectional: Option<bool>, | ||||
|     tolerance: Option<f64>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidSet, KclError> { | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     let trajectory = match path { | ||||
|         SweepPath::Sketch(sketch) => sketch.id.into(), | ||||
|         SweepPath::Helix(helix) => helix.value.into(), | ||||
|     }; | ||||
|  | ||||
|     let sketches: Vec<Sketch> = sketch_set.into(); | ||||
|     let mut solids = Vec::new(); | ||||
|     for sketch in &sketches { | ||||
|         let id = exec_state.next_uuid(); | ||||
| @ -171,5 +177,5 @@ async fn inner_sweep( | ||||
|         solids.push(do_post_extrude(sketch.clone(), id.into(), 0.0, exec_state, args.clone()).await?); | ||||
|     } | ||||
|  | ||||
|     Ok(solids.into()) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| @ -13,18 +13,28 @@ use kittycad_modeling_cmds as kcmc; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ExecState, KclValue, SolidOrImportedGeometry}, | ||||
|     execution::{ | ||||
|         kcl_value::{ArrayLen, RuntimeType}, | ||||
|         ExecState, KclValue, PrimitiveType, SolidOrImportedGeometry, | ||||
|     }, | ||||
|     std::Args, | ||||
| }; | ||||
|  | ||||
| /// Scale a solid. | ||||
| pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set = args.get_unlabeled_kw_arg("solid_set")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Union(vec![ | ||||
|             RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|             RuntimeType::Primitive(PrimitiveType::ImportedGeometry), | ||||
|         ]), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let scale = args.get_kw_arg("scale")?; | ||||
|     let global = args.get_kw_arg_opt("global")?; | ||||
|  | ||||
|     let solid = inner_scale(solid_set, scale, global, exec_state, args).await?; | ||||
|     Ok(solid.into()) | ||||
|     let solids = inner_scale(solids, scale, global, exec_state, args).await?; | ||||
|     Ok(solids.into()) | ||||
| } | ||||
|  | ||||
| /// Scale a solid. | ||||
| @ -124,19 +134,19 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = {docs = "The solid or set of solids to scale."}, | ||||
|         solids = {docs = "The solid or set of solids to scale."}, | ||||
|         scale = {docs = "The scale factor for the x, y, and z axes."}, | ||||
|         global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."} | ||||
|     } | ||||
| }] | ||||
| async fn inner_scale( | ||||
|     solid_set: SolidOrImportedGeometry, | ||||
|     solids: SolidOrImportedGeometry, | ||||
|     scale: [f64; 3], | ||||
|     global: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidOrImportedGeometry, KclError> { | ||||
|     for solid_id in solid_set.ids() { | ||||
|     for solid_id in solids.ids() { | ||||
|         let id = exec_state.next_uuid(); | ||||
|  | ||||
|         args.batch_modeling_cmd( | ||||
| @ -162,17 +172,24 @@ async fn inner_scale( | ||||
|         .await?; | ||||
|     } | ||||
|  | ||||
|     Ok(solid_set) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| /// Move a solid. | ||||
| pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set = args.get_unlabeled_kw_arg("solid_set")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solids", | ||||
|         &RuntimeType::Union(vec![ | ||||
|             RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|             RuntimeType::Primitive(PrimitiveType::ImportedGeometry), | ||||
|         ]), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let translate = args.get_kw_arg("translate")?; | ||||
|     let global = args.get_kw_arg_opt("global")?; | ||||
|  | ||||
|     let solid = inner_translate(solid_set, translate, global, exec_state, args).await?; | ||||
|     Ok(solid.into()) | ||||
|     let solids = inner_translate(solids, translate, global, exec_state, args).await?; | ||||
|     Ok(solids.into()) | ||||
| } | ||||
|  | ||||
| /// Move a solid. | ||||
| @ -264,19 +281,19 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = {docs = "The solid or set of solids to move."}, | ||||
|         solids = {docs = "The solid or set of solids to move."}, | ||||
|         translate = {docs = "The amount to move the solid in all three axes."}, | ||||
|         global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."} | ||||
|     } | ||||
| }] | ||||
| async fn inner_translate( | ||||
|     solid_set: SolidOrImportedGeometry, | ||||
|     solids: SolidOrImportedGeometry, | ||||
|     translate: [f64; 3], | ||||
|     global: Option<bool>, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidOrImportedGeometry, KclError> { | ||||
|     for solid_id in solid_set.ids() { | ||||
|     for solid_id in solids.ids() { | ||||
|         let id = exec_state.next_uuid(); | ||||
|  | ||||
|         args.batch_modeling_cmd( | ||||
| @ -302,12 +319,19 @@ async fn inner_translate( | ||||
|         .await?; | ||||
|     } | ||||
|  | ||||
|     Ok(solid_set) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| /// Rotate a solid. | ||||
| pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let solid_set = args.get_unlabeled_kw_arg("solid_set")?; | ||||
|     let solids = args.get_unlabeled_kw_arg_typed( | ||||
|         "solid", | ||||
|         &RuntimeType::Union(vec![ | ||||
|             RuntimeType::Array(PrimitiveType::Solid, ArrayLen::NonEmpty), | ||||
|             RuntimeType::Primitive(PrimitiveType::ImportedGeometry), | ||||
|         ]), | ||||
|         exec_state, | ||||
|     )?; | ||||
|     let roll = args.get_kw_arg_opt("roll")?; | ||||
|     let pitch = args.get_kw_arg_opt("pitch")?; | ||||
|     let yaw = args.get_kw_arg_opt("yaw")?; | ||||
| @ -415,8 +439,8 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let solid = inner_rotate(solid_set, roll, pitch, yaw, axis, angle, global, exec_state, args).await?; | ||||
|     Ok(solid.into()) | ||||
|     let solids = inner_rotate(solids, roll, pitch, yaw, axis, angle, global, exec_state, args).await?; | ||||
|     Ok(solids.into()) | ||||
| } | ||||
|  | ||||
| /// Rotate a solid. | ||||
| @ -571,7 +595,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         solid_set = {docs = "The solid or set of solids to rotate."}, | ||||
|         solids = {docs = "The solid or set of solids to rotate."}, | ||||
|         roll = {docs = "The roll angle in degrees. Must be used with `pitch` and `yaw`. Must be between -360 and 360.", include_in_snippet = true}, | ||||
|         pitch = {docs = "The pitch angle in degrees. Must be used with `roll` and `yaw`. Must be between -360 and 360.", include_in_snippet = true}, | ||||
|         yaw = {docs = "The yaw angle in degrees. Must be used with `roll` and `pitch`. Must be between -360 and 360.", include_in_snippet = true}, | ||||
| @ -582,7 +606,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
| }] | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| async fn inner_rotate( | ||||
|     solid_set: SolidOrImportedGeometry, | ||||
|     solids: SolidOrImportedGeometry, | ||||
|     roll: Option<f64>, | ||||
|     pitch: Option<f64>, | ||||
|     yaw: Option<f64>, | ||||
| @ -592,7 +616,7 @@ async fn inner_rotate( | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| ) -> Result<SolidOrImportedGeometry, KclError> { | ||||
|     for solid_id in solid_set.ids() { | ||||
|     for solid_id in solids.ids() { | ||||
|         let id = exec_state.next_uuid(); | ||||
|  | ||||
|         if let (Some(roll), Some(pitch), Some(yaw)) = (roll, pitch, yaw) { | ||||
| @ -645,7 +669,7 @@ async fn inner_rotate( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Ok(solid_set) | ||||
|     Ok(solids) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -81,7 +81,7 @@ async fn do_execute_and_snapshot( | ||||
|     ctx: &ExecutorContext, | ||||
|     program: Program, | ||||
| ) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> { | ||||
|     let mut exec_state = ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = ExecState::new(ctx); | ||||
|     let result = ctx | ||||
|         .run(&program, &mut exec_state) | ||||
|         .await | ||||
| @ -156,7 +156,7 @@ pub async fn execute_and_export_step( | ||||
|     ExecErrorWithState, | ||||
| > { | ||||
|     let ctx = new_context(units, true, current_file).await?; | ||||
|     let mut exec_state = ExecState::new(&ctx.settings); | ||||
|     let mut exec_state = ExecState::new(&ctx); | ||||
|     let program = Program::parse_no_errs(code) | ||||
|         .map_err(|err| ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone()))?; | ||||
|     let result = ctx | ||||
|  | ||||
| @ -821,7 +821,7 @@ impl Type { | ||||
|     pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { | ||||
|         match self { | ||||
|             Type::Primitive(t) => t.to_string(), | ||||
|             Type::Array(t) => format!("{t}[]"), | ||||
|             Type::Array(t) => format!("[{t}]"), | ||||
|             Type::Object { properties } => { | ||||
|                 let mut result = "{".to_owned(); | ||||
|                 for p in properties { | ||||
| @ -1268,7 +1268,7 @@ thing(1) | ||||
|  | ||||
|     #[test] | ||||
|     fn test_recast_typed_fn() { | ||||
|         let some_program_string = r#"fn thing(x: string, y: bool[]): number { | ||||
|         let some_program_string = r#"fn thing(x: string, y: [bool]): number { | ||||
|   return x + 1 | ||||
| } | ||||
| "#; | ||||
|  | ||||
| @ -284,56 +284,7 @@ description: Variables in memory after executing angled_line.kcl | ||||
|         "tags": { | ||||
|           "seg01": { | ||||
|             "type": "TagIdentifier", | ||||
|             "value": "seg01", | ||||
|             "info": { | ||||
|               "type": "TagEngineInfo", | ||||
|               "id": "[uuid]", | ||||
|               "sketch": "[uuid]", | ||||
|               "path": { | ||||
|                 "__geoMeta": { | ||||
|                   "id": "[uuid]", | ||||
|                   "sourceRange": [ | ||||
|                     103, | ||||
|                     142, | ||||
|                     0 | ||||
|                   ] | ||||
|                 }, | ||||
|                 "from": [ | ||||
|                   19.93, | ||||
|                   15.04 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 141, | ||||
|                   "start": 135, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "to": [ | ||||
|                   23.08, | ||||
|                   5.19 | ||||
|                 ], | ||||
|                 "type": "ToPoint", | ||||
|                 "units": { | ||||
|                   "type": "Mm" | ||||
|                 } | ||||
|               }, | ||||
|               "surface": { | ||||
|                 "faceId": "[uuid]", | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   103, | ||||
|                   142, | ||||
|                   0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 141, | ||||
|                   "start": 135, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "type": "extrudePlane" | ||||
|               } | ||||
|             } | ||||
|             "value": "seg01" | ||||
|           } | ||||
|         }, | ||||
|         "artifactId": "[uuid]", | ||||
| @ -353,55 +304,6 @@ description: Variables in memory after executing angled_line.kcl | ||||
|   "seg01": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "seg01", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             103, | ||||
|             142, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           19.93, | ||||
|           15.04 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 141, | ||||
|           "start": 135, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "to": [ | ||||
|           23.08, | ||||
|           5.19 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": { | ||||
|         "faceId": "[uuid]", | ||||
|         "id": "[uuid]", | ||||
|         "sourceRange": [ | ||||
|           103, | ||||
|           142, | ||||
|           0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 141, | ||||
|           "start": 135, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "type": "extrudePlane" | ||||
|       } | ||||
|     } | ||||
|     "value": "seg01" | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -249,109 +249,11 @@ description: Variables in memory after executing artifact_graph_example_code1.kc | ||||
|         "tags": { | ||||
|           "seg01": { | ||||
|             "type": "TagIdentifier", | ||||
|             "value": "seg01", | ||||
|             "info": { | ||||
|               "type": "TagEngineInfo", | ||||
|               "id": "[uuid]", | ||||
|               "sketch": "[uuid]", | ||||
|               "path": { | ||||
|                 "__geoMeta": { | ||||
|                   "id": "[uuid]", | ||||
|                   "sourceRange": [ | ||||
|                     95, | ||||
|                     131, | ||||
|                     0 | ||||
|                   ] | ||||
|                 }, | ||||
|                 "from": [ | ||||
|                   -5.0, | ||||
|                   5.0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 130, | ||||
|                   "start": 124, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "to": [ | ||||
|                   5.55, | ||||
|                   5.0 | ||||
|                 ], | ||||
|                 "type": "ToPoint", | ||||
|                 "units": { | ||||
|                   "type": "Mm" | ||||
|                 } | ||||
|               }, | ||||
|               "surface": { | ||||
|                 "faceId": "[uuid]", | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   95, | ||||
|                   131, | ||||
|                   0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 130, | ||||
|                   "start": 124, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "type": "extrudePlane" | ||||
|               } | ||||
|             } | ||||
|             "value": "seg01" | ||||
|           }, | ||||
|           "seg02": { | ||||
|             "type": "TagIdentifier", | ||||
|             "value": "seg02", | ||||
|             "info": { | ||||
|               "type": "TagEngineInfo", | ||||
|               "id": "[uuid]", | ||||
|               "sketch": "[uuid]", | ||||
|               "path": { | ||||
|                 "__geoMeta": { | ||||
|                   "id": "[uuid]", | ||||
|                   "sourceRange": [ | ||||
|                     137, | ||||
|                     171, | ||||
|                     0 | ||||
|                   ] | ||||
|                 }, | ||||
|                 "from": [ | ||||
|                   5.55, | ||||
|                   5.0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 170, | ||||
|                   "start": 164, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg02" | ||||
|                 }, | ||||
|                 "to": [ | ||||
|                   5.55, | ||||
|                   -5.0 | ||||
|                 ], | ||||
|                 "type": "ToPoint", | ||||
|                 "units": { | ||||
|                   "type": "Mm" | ||||
|                 } | ||||
|               }, | ||||
|               "surface": { | ||||
|                 "faceId": "[uuid]", | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   137, | ||||
|                   171, | ||||
|                   0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 170, | ||||
|                   "start": 164, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg02" | ||||
|                 }, | ||||
|                 "type": "extrudePlane" | ||||
|               } | ||||
|             } | ||||
|             "value": "seg02" | ||||
|           } | ||||
|         }, | ||||
|         "artifactId": "[uuid]", | ||||
| @ -779,109 +681,11 @@ description: Variables in memory after executing artifact_graph_example_code1.kc | ||||
|               "tags": { | ||||
|                 "seg01": { | ||||
|                   "type": "TagIdentifier", | ||||
|                   "value": "seg01", | ||||
|                   "info": { | ||||
|                     "type": "TagEngineInfo", | ||||
|                     "id": "[uuid]", | ||||
|                     "sketch": "[uuid]", | ||||
|                     "path": { | ||||
|                       "__geoMeta": { | ||||
|                         "id": "[uuid]", | ||||
|                         "sourceRange": [ | ||||
|                           95, | ||||
|                           131, | ||||
|                           0 | ||||
|                         ] | ||||
|                       }, | ||||
|                       "from": [ | ||||
|                         -5.0, | ||||
|                         5.0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 130, | ||||
|                         "start": 124, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg01" | ||||
|                       }, | ||||
|                       "to": [ | ||||
|                         5.55, | ||||
|                         5.0 | ||||
|                       ], | ||||
|                       "type": "ToPoint", | ||||
|                       "units": { | ||||
|                         "type": "Mm" | ||||
|                       } | ||||
|                     }, | ||||
|                     "surface": { | ||||
|                       "faceId": "[uuid]", | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         95, | ||||
|                         131, | ||||
|                         0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 130, | ||||
|                         "start": 124, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg01" | ||||
|                       }, | ||||
|                       "type": "extrudePlane" | ||||
|                     } | ||||
|                   } | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "seg02": { | ||||
|                   "type": "TagIdentifier", | ||||
|                   "value": "seg02", | ||||
|                   "info": { | ||||
|                     "type": "TagEngineInfo", | ||||
|                     "id": "[uuid]", | ||||
|                     "sketch": "[uuid]", | ||||
|                     "path": { | ||||
|                       "__geoMeta": { | ||||
|                         "id": "[uuid]", | ||||
|                         "sourceRange": [ | ||||
|                           137, | ||||
|                           171, | ||||
|                           0 | ||||
|                         ] | ||||
|                       }, | ||||
|                       "from": [ | ||||
|                         5.55, | ||||
|                         5.0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 170, | ||||
|                         "start": 164, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg02" | ||||
|                       }, | ||||
|                       "to": [ | ||||
|                         5.55, | ||||
|                         -5.0 | ||||
|                       ], | ||||
|                       "type": "ToPoint", | ||||
|                       "units": { | ||||
|                         "type": "Mm" | ||||
|                       } | ||||
|                     }, | ||||
|                     "surface": { | ||||
|                       "faceId": "[uuid]", | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         137, | ||||
|                         171, | ||||
|                         0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 170, | ||||
|                         "start": 164, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg02" | ||||
|                       }, | ||||
|                       "type": "extrudePlane" | ||||
|                     } | ||||
|                   } | ||||
|                   "value": "seg02" | ||||
|                 } | ||||
|               }, | ||||
|               "artifactId": "[uuid]", | ||||
| @ -949,110 +753,12 @@ description: Variables in memory after executing artifact_graph_example_code1.kc | ||||
|   "seg01": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "seg01", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             95, | ||||
|             131, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           -5.0, | ||||
|           5.0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 130, | ||||
|           "start": 124, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "to": [ | ||||
|           5.55, | ||||
|           5.0 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": { | ||||
|         "faceId": "[uuid]", | ||||
|         "id": "[uuid]", | ||||
|         "sourceRange": [ | ||||
|           95, | ||||
|           131, | ||||
|           0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 130, | ||||
|           "start": 124, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "type": "extrudePlane" | ||||
|       } | ||||
|     } | ||||
|     "value": "seg01" | ||||
|   }, | ||||
|   "seg02": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "seg02", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             137, | ||||
|             171, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           5.55, | ||||
|           5.0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 170, | ||||
|           "start": 164, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg02" | ||||
|         }, | ||||
|         "to": [ | ||||
|           5.55, | ||||
|           -5.0 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": { | ||||
|         "faceId": "[uuid]", | ||||
|         "id": "[uuid]", | ||||
|         "sourceRange": [ | ||||
|           137, | ||||
|           171, | ||||
|           0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 170, | ||||
|           "start": 164, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg02" | ||||
|         }, | ||||
|         "type": "extrudePlane" | ||||
|       } | ||||
|     } | ||||
|     "value": "seg02" | ||||
|   }, | ||||
|   "sketch001": { | ||||
|     "type": "Sketch", | ||||
| @ -1240,109 +946,11 @@ description: Variables in memory after executing artifact_graph_example_code1.kc | ||||
|       "tags": { | ||||
|         "seg01": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "seg01", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   95, | ||||
|                   131, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 -5.0, | ||||
|                 5.0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 130, | ||||
|                 "start": 124, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg01" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 5.55, | ||||
|                 5.0 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": { | ||||
|               "faceId": "[uuid]", | ||||
|               "id": "[uuid]", | ||||
|               "sourceRange": [ | ||||
|                 95, | ||||
|                 131, | ||||
|                 0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 130, | ||||
|                 "start": 124, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg01" | ||||
|               }, | ||||
|               "type": "extrudePlane" | ||||
|             } | ||||
|           } | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "seg02": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "seg02", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   137, | ||||
|                   171, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 5.55, | ||||
|                 5.0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 170, | ||||
|                 "start": 164, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg02" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 5.55, | ||||
|                 -5.0 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": { | ||||
|               "faceId": "[uuid]", | ||||
|               "id": "[uuid]", | ||||
|               "sourceRange": [ | ||||
|                 137, | ||||
|                 171, | ||||
|                 0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 170, | ||||
|                 "start": 164, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg02" | ||||
|               }, | ||||
|               "type": "extrudePlane" | ||||
|             } | ||||
|           } | ||||
|           "value": "seg02" | ||||
|         } | ||||
|       }, | ||||
|       "artifactId": "[uuid]", | ||||
| @ -1715,109 +1323,11 @@ description: Variables in memory after executing artifact_graph_example_code1.kc | ||||
|             "tags": { | ||||
|               "seg01": { | ||||
|                 "type": "TagIdentifier", | ||||
|                 "value": "seg01", | ||||
|                 "info": { | ||||
|                   "type": "TagEngineInfo", | ||||
|                   "id": "[uuid]", | ||||
|                   "sketch": "[uuid]", | ||||
|                   "path": { | ||||
|                     "__geoMeta": { | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         95, | ||||
|                         131, | ||||
|                         0 | ||||
|                       ] | ||||
|                     }, | ||||
|                     "from": [ | ||||
|                       -5.0, | ||||
|                       5.0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 130, | ||||
|                       "start": 124, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg01" | ||||
|                     }, | ||||
|                     "to": [ | ||||
|                       5.55, | ||||
|                       5.0 | ||||
|                     ], | ||||
|                     "type": "ToPoint", | ||||
|                     "units": { | ||||
|                       "type": "Mm" | ||||
|                     } | ||||
|                   }, | ||||
|                   "surface": { | ||||
|                     "faceId": "[uuid]", | ||||
|                     "id": "[uuid]", | ||||
|                     "sourceRange": [ | ||||
|                       95, | ||||
|                       131, | ||||
|                       0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 130, | ||||
|                       "start": 124, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg01" | ||||
|                     }, | ||||
|                     "type": "extrudePlane" | ||||
|                   } | ||||
|                 } | ||||
|                 "value": "seg01" | ||||
|               }, | ||||
|               "seg02": { | ||||
|                 "type": "TagIdentifier", | ||||
|                 "value": "seg02", | ||||
|                 "info": { | ||||
|                   "type": "TagEngineInfo", | ||||
|                   "id": "[uuid]", | ||||
|                   "sketch": "[uuid]", | ||||
|                   "path": { | ||||
|                     "__geoMeta": { | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         137, | ||||
|                         171, | ||||
|                         0 | ||||
|                       ] | ||||
|                     }, | ||||
|                     "from": [ | ||||
|                       5.55, | ||||
|                       5.0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 170, | ||||
|                       "start": 164, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg02" | ||||
|                     }, | ||||
|                     "to": [ | ||||
|                       5.55, | ||||
|                       -5.0 | ||||
|                     ], | ||||
|                     "type": "ToPoint", | ||||
|                     "units": { | ||||
|                       "type": "Mm" | ||||
|                     } | ||||
|                   }, | ||||
|                   "surface": { | ||||
|                     "faceId": "[uuid]", | ||||
|                     "id": "[uuid]", | ||||
|                     "sourceRange": [ | ||||
|                       137, | ||||
|                       171, | ||||
|                       0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 170, | ||||
|                       "start": 164, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg02" | ||||
|                     }, | ||||
|                     "type": "extrudePlane" | ||||
|                   } | ||||
|                 } | ||||
|                 "value": "seg02" | ||||
|               } | ||||
|             }, | ||||
|             "artifactId": "[uuid]", | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| --- | ||||
| source: kcl/src/simulation_tests.rs | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact graph flowchart artifact_graph_example_code_no_3d.kcl | ||||
| extension: md | ||||
| snapshot_kind: binary | ||||
|  | ||||
| @ -6,119 +6,17 @@ description: Variables in memory after executing artifact_graph_example_code_no_ | ||||
|   "rectangleSegmentA001": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "rectangleSegmentA001", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             71, | ||||
|             121, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           5.82, | ||||
|           0.0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 120, | ||||
|           "start": 99, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "rectangleSegmentA001" | ||||
|         }, | ||||
|         "to": [ | ||||
|           -5.72, | ||||
|           0.0 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": null | ||||
|     } | ||||
|     "value": "rectangleSegmentA001" | ||||
|   }, | ||||
|   "rectangleSegmentB001": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "rectangleSegmentB001", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             127, | ||||
|             227, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           -5.72, | ||||
|           0.0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 226, | ||||
|           "start": 205, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "rectangleSegmentB001" | ||||
|         }, | ||||
|         "to": [ | ||||
|           -5.72, | ||||
|           8.21 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": null | ||||
|     } | ||||
|     "value": "rectangleSegmentB001" | ||||
|   }, | ||||
|   "rectangleSegmentC001": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "rectangleSegmentC001", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             233, | ||||
|             353, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           -5.72, | ||||
|           8.21 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 352, | ||||
|           "start": 331, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "rectangleSegmentC001" | ||||
|         }, | ||||
|         "to": [ | ||||
|           5.82, | ||||
|           8.21 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": null | ||||
|     } | ||||
|     "value": "rectangleSegmentC001" | ||||
|   }, | ||||
|   "sketch003": { | ||||
|     "type": "Sketch", | ||||
| @ -311,117 +209,15 @@ description: Variables in memory after executing artifact_graph_example_code_no_ | ||||
|       "tags": { | ||||
|         "rectangleSegmentA001": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "rectangleSegmentA001", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   71, | ||||
|                   121, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 5.82, | ||||
|                 0.0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 120, | ||||
|                 "start": 99, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "rectangleSegmentA001" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 -5.72, | ||||
|                 0.0 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": null | ||||
|           } | ||||
|           "value": "rectangleSegmentA001" | ||||
|         }, | ||||
|         "rectangleSegmentB001": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "rectangleSegmentB001", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   127, | ||||
|                   227, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 -5.72, | ||||
|                 0.0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 226, | ||||
|                 "start": 205, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "rectangleSegmentB001" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 -5.72, | ||||
|                 8.21 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": null | ||||
|           } | ||||
|           "value": "rectangleSegmentB001" | ||||
|         }, | ||||
|         "rectangleSegmentC001": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "rectangleSegmentC001", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   233, | ||||
|                   353, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 -5.72, | ||||
|                 8.21 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 352, | ||||
|                 "start": 331, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "rectangleSegmentC001" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 5.82, | ||||
|                 8.21 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": null | ||||
|           } | ||||
|           "value": "rectangleSegmentC001" | ||||
|         } | ||||
|       }, | ||||
|       "artifactId": "[uuid]", | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| --- | ||||
| source: kcl/src/simulation_tests.rs | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact graph flowchart artifact_graph_example_code_offset_planes.kcl | ||||
| extension: md | ||||
| snapshot_kind: binary | ||||
|  | ||||
| @ -205,56 +205,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|         "tags": { | ||||
|           "seg01": { | ||||
|             "type": "TagIdentifier", | ||||
|             "value": "seg01", | ||||
|             "info": { | ||||
|               "type": "TagEngineInfo", | ||||
|               "id": "[uuid]", | ||||
|               "sketch": "[uuid]", | ||||
|               "path": { | ||||
|                 "__geoMeta": { | ||||
|                   "id": "[uuid]", | ||||
|                   "sourceRange": [ | ||||
|                     92, | ||||
|                     125, | ||||
|                     0 | ||||
|                   ] | ||||
|                 }, | ||||
|                 "from": [ | ||||
|                   4.0, | ||||
|                   8.0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 124, | ||||
|                   "start": 118, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "to": [ | ||||
|                   9.0, | ||||
|                   0.0 | ||||
|                 ], | ||||
|                 "type": "ToPoint", | ||||
|                 "units": { | ||||
|                   "type": "Mm" | ||||
|                 } | ||||
|               }, | ||||
|               "surface": { | ||||
|                 "faceId": "[uuid]", | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   92, | ||||
|                   125, | ||||
|                   0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 124, | ||||
|                   "start": 118, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg01" | ||||
|                 }, | ||||
|                 "type": "extrudePlane" | ||||
|               } | ||||
|             } | ||||
|             "value": "seg01" | ||||
|           } | ||||
|         }, | ||||
|         "artifactId": "[uuid]", | ||||
| @ -629,56 +580,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|               "tags": { | ||||
|                 "seg01": { | ||||
|                   "type": "TagIdentifier", | ||||
|                   "value": "seg01", | ||||
|                   "info": { | ||||
|                     "type": "TagEngineInfo", | ||||
|                     "id": "[uuid]", | ||||
|                     "sketch": "[uuid]", | ||||
|                     "path": { | ||||
|                       "__geoMeta": { | ||||
|                         "id": "[uuid]", | ||||
|                         "sourceRange": [ | ||||
|                           92, | ||||
|                           125, | ||||
|                           0 | ||||
|                         ] | ||||
|                       }, | ||||
|                       "from": [ | ||||
|                         4.0, | ||||
|                         8.0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 124, | ||||
|                         "start": 118, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg01" | ||||
|                       }, | ||||
|                       "to": [ | ||||
|                         9.0, | ||||
|                         0.0 | ||||
|                       ], | ||||
|                       "type": "ToPoint", | ||||
|                       "units": { | ||||
|                         "type": "Mm" | ||||
|                       } | ||||
|                     }, | ||||
|                     "surface": { | ||||
|                       "faceId": "[uuid]", | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         92, | ||||
|                         125, | ||||
|                         0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 124, | ||||
|                         "start": 118, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg01" | ||||
|                       }, | ||||
|                       "type": "extrudePlane" | ||||
|                     } | ||||
|                   } | ||||
|                   "value": "seg01" | ||||
|                 } | ||||
|               }, | ||||
|               "artifactId": "[uuid]", | ||||
| @ -1258,56 +1160,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|                     "tags": { | ||||
|                       "seg01": { | ||||
|                         "type": "TagIdentifier", | ||||
|                         "value": "seg01", | ||||
|                         "info": { | ||||
|                           "type": "TagEngineInfo", | ||||
|                           "id": "[uuid]", | ||||
|                           "sketch": "[uuid]", | ||||
|                           "path": { | ||||
|                             "__geoMeta": { | ||||
|                               "id": "[uuid]", | ||||
|                               "sourceRange": [ | ||||
|                                 92, | ||||
|                                 125, | ||||
|                                 0 | ||||
|                               ] | ||||
|                             }, | ||||
|                             "from": [ | ||||
|                               4.0, | ||||
|                               8.0 | ||||
|                             ], | ||||
|                             "tag": { | ||||
|                               "end": 124, | ||||
|                               "start": 118, | ||||
|                               "type": "TagDeclarator", | ||||
|                               "value": "seg01" | ||||
|                             }, | ||||
|                             "to": [ | ||||
|                               9.0, | ||||
|                               0.0 | ||||
|                             ], | ||||
|                             "type": "ToPoint", | ||||
|                             "units": { | ||||
|                               "type": "Mm" | ||||
|                             } | ||||
|                           }, | ||||
|                           "surface": { | ||||
|                             "faceId": "[uuid]", | ||||
|                             "id": "[uuid]", | ||||
|                             "sourceRange": [ | ||||
|                               92, | ||||
|                               125, | ||||
|                               0 | ||||
|                             ], | ||||
|                             "tag": { | ||||
|                               "end": 124, | ||||
|                               "start": 118, | ||||
|                               "type": "TagDeclarator", | ||||
|                               "value": "seg01" | ||||
|                             }, | ||||
|                             "type": "extrudePlane" | ||||
|                           } | ||||
|                         } | ||||
|                         "value": "seg01" | ||||
|                       } | ||||
|                     }, | ||||
|                     "artifactId": "[uuid]", | ||||
| @ -1391,56 +1244,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|         "tags": { | ||||
|           "seg02": { | ||||
|             "type": "TagIdentifier", | ||||
|             "value": "seg02", | ||||
|             "info": { | ||||
|               "type": "TagEngineInfo", | ||||
|               "id": "[uuid]", | ||||
|               "sketch": "[uuid]", | ||||
|               "path": { | ||||
|                 "__geoMeta": { | ||||
|                   "id": "[uuid]", | ||||
|                   "sourceRange": [ | ||||
|                     577, | ||||
|                     611, | ||||
|                     0 | ||||
|                   ] | ||||
|                 }, | ||||
|                 "from": [ | ||||
|                   1.0, | ||||
|                   1.5 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 610, | ||||
|                   "start": 604, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg02" | ||||
|                 }, | ||||
|                 "to": [ | ||||
|                   1.5, | ||||
|                   3.5 | ||||
|                 ], | ||||
|                 "type": "ToPoint", | ||||
|                 "units": { | ||||
|                   "type": "Mm" | ||||
|                 } | ||||
|               }, | ||||
|               "surface": { | ||||
|                 "faceId": "[uuid]", | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   577, | ||||
|                   611, | ||||
|                   0 | ||||
|                 ], | ||||
|                 "tag": { | ||||
|                   "end": 610, | ||||
|                   "start": 604, | ||||
|                   "type": "TagDeclarator", | ||||
|                   "value": "seg02" | ||||
|                 }, | ||||
|                 "type": "extrudePlane" | ||||
|               } | ||||
|             } | ||||
|             "value": "seg02" | ||||
|           } | ||||
|         }, | ||||
|         "artifactId": "[uuid]", | ||||
| @ -2137,56 +1941,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|                           "tags": { | ||||
|                             "seg01": { | ||||
|                               "type": "TagIdentifier", | ||||
|                               "value": "seg01", | ||||
|                               "info": { | ||||
|                                 "type": "TagEngineInfo", | ||||
|                                 "id": "[uuid]", | ||||
|                                 "sketch": "[uuid]", | ||||
|                                 "path": { | ||||
|                                   "__geoMeta": { | ||||
|                                     "id": "[uuid]", | ||||
|                                     "sourceRange": [ | ||||
|                                       92, | ||||
|                                       125, | ||||
|                                       0 | ||||
|                                     ] | ||||
|                                   }, | ||||
|                                   "from": [ | ||||
|                                     4.0, | ||||
|                                     8.0 | ||||
|                                   ], | ||||
|                                   "tag": { | ||||
|                                     "end": 124, | ||||
|                                     "start": 118, | ||||
|                                     "type": "TagDeclarator", | ||||
|                                     "value": "seg01" | ||||
|                                   }, | ||||
|                                   "to": [ | ||||
|                                     9.0, | ||||
|                                     0.0 | ||||
|                                   ], | ||||
|                                   "type": "ToPoint", | ||||
|                                   "units": { | ||||
|                                     "type": "Mm" | ||||
|                                   } | ||||
|                                 }, | ||||
|                                 "surface": { | ||||
|                                   "faceId": "[uuid]", | ||||
|                                   "id": "[uuid]", | ||||
|                                   "sourceRange": [ | ||||
|                                     92, | ||||
|                                     125, | ||||
|                                     0 | ||||
|                                   ], | ||||
|                                   "tag": { | ||||
|                                     "end": 124, | ||||
|                                     "start": 118, | ||||
|                                     "type": "TagDeclarator", | ||||
|                                     "value": "seg01" | ||||
|                                   }, | ||||
|                                   "type": "extrudePlane" | ||||
|                                 } | ||||
|                               } | ||||
|                               "value": "seg01" | ||||
|                             } | ||||
|                           }, | ||||
|                           "artifactId": "[uuid]", | ||||
| @ -2270,56 +2025,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|               "tags": { | ||||
|                 "seg02": { | ||||
|                   "type": "TagIdentifier", | ||||
|                   "value": "seg02", | ||||
|                   "info": { | ||||
|                     "type": "TagEngineInfo", | ||||
|                     "id": "[uuid]", | ||||
|                     "sketch": "[uuid]", | ||||
|                     "path": { | ||||
|                       "__geoMeta": { | ||||
|                         "id": "[uuid]", | ||||
|                         "sourceRange": [ | ||||
|                           577, | ||||
|                           611, | ||||
|                           0 | ||||
|                         ] | ||||
|                       }, | ||||
|                       "from": [ | ||||
|                         1.0, | ||||
|                         1.5 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 610, | ||||
|                         "start": 604, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg02" | ||||
|                       }, | ||||
|                       "to": [ | ||||
|                         1.5, | ||||
|                         3.5 | ||||
|                       ], | ||||
|                       "type": "ToPoint", | ||||
|                       "units": { | ||||
|                         "type": "Mm" | ||||
|                       } | ||||
|                     }, | ||||
|                     "surface": { | ||||
|                       "faceId": "[uuid]", | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         577, | ||||
|                         611, | ||||
|                         0 | ||||
|                       ], | ||||
|                       "tag": { | ||||
|                         "end": 610, | ||||
|                         "start": 604, | ||||
|                         "type": "TagDeclarator", | ||||
|                         "value": "seg02" | ||||
|                       }, | ||||
|                       "type": "extrudePlane" | ||||
|                     } | ||||
|                   } | ||||
|                   "value": "seg02" | ||||
|                 } | ||||
|               }, | ||||
|               "artifactId": "[uuid]", | ||||
| @ -2378,110 +2084,12 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|   "seg01": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "seg01", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             92, | ||||
|             125, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           4.0, | ||||
|           8.0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 124, | ||||
|           "start": 118, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "to": [ | ||||
|           9.0, | ||||
|           0.0 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": { | ||||
|         "faceId": "[uuid]", | ||||
|         "id": "[uuid]", | ||||
|         "sourceRange": [ | ||||
|           92, | ||||
|           125, | ||||
|           0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 124, | ||||
|           "start": 118, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg01" | ||||
|         }, | ||||
|         "type": "extrudePlane" | ||||
|       } | ||||
|     } | ||||
|     "value": "seg01" | ||||
|   }, | ||||
|   "seg02": { | ||||
|     "type": "TagIdentifier", | ||||
|     "type": "TagIdentifier", | ||||
|     "value": "seg02", | ||||
|     "info": { | ||||
|       "type": "TagEngineInfo", | ||||
|       "id": "[uuid]", | ||||
|       "sketch": "[uuid]", | ||||
|       "path": { | ||||
|         "__geoMeta": { | ||||
|           "id": "[uuid]", | ||||
|           "sourceRange": [ | ||||
|             577, | ||||
|             611, | ||||
|             0 | ||||
|           ] | ||||
|         }, | ||||
|         "from": [ | ||||
|           1.0, | ||||
|           1.5 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 610, | ||||
|           "start": 604, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg02" | ||||
|         }, | ||||
|         "to": [ | ||||
|           1.5, | ||||
|           3.5 | ||||
|         ], | ||||
|         "type": "ToPoint", | ||||
|         "units": { | ||||
|           "type": "Mm" | ||||
|         } | ||||
|       }, | ||||
|       "surface": { | ||||
|         "faceId": "[uuid]", | ||||
|         "id": "[uuid]", | ||||
|         "sourceRange": [ | ||||
|           577, | ||||
|           611, | ||||
|           0 | ||||
|         ], | ||||
|         "tag": { | ||||
|           "end": 610, | ||||
|           "start": 604, | ||||
|           "type": "TagDeclarator", | ||||
|           "value": "seg02" | ||||
|         }, | ||||
|         "type": "extrudePlane" | ||||
|       } | ||||
|     } | ||||
|     "value": "seg02" | ||||
|   }, | ||||
|   "sketch001": { | ||||
|     "type": "Sketch", | ||||
| @ -2641,56 +2249,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|       "tags": { | ||||
|         "seg01": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "seg01", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   92, | ||||
|                   125, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 4.0, | ||||
|                 8.0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 124, | ||||
|                 "start": 118, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg01" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 9.0, | ||||
|                 0.0 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": { | ||||
|               "faceId": "[uuid]", | ||||
|               "id": "[uuid]", | ||||
|               "sourceRange": [ | ||||
|                 92, | ||||
|                 125, | ||||
|                 0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 124, | ||||
|                 "start": 118, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg01" | ||||
|               }, | ||||
|               "type": "extrudePlane" | ||||
|             } | ||||
|           } | ||||
|           "value": "seg01" | ||||
|         } | ||||
|       }, | ||||
|       "artifactId": "[uuid]", | ||||
| @ -3019,56 +2578,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|             "tags": { | ||||
|               "seg01": { | ||||
|                 "type": "TagIdentifier", | ||||
|                 "value": "seg01", | ||||
|                 "info": { | ||||
|                   "type": "TagEngineInfo", | ||||
|                   "id": "[uuid]", | ||||
|                   "sketch": "[uuid]", | ||||
|                   "path": { | ||||
|                     "__geoMeta": { | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         92, | ||||
|                         125, | ||||
|                         0 | ||||
|                       ] | ||||
|                     }, | ||||
|                     "from": [ | ||||
|                       4.0, | ||||
|                       8.0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 124, | ||||
|                       "start": 118, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg01" | ||||
|                     }, | ||||
|                     "to": [ | ||||
|                       9.0, | ||||
|                       0.0 | ||||
|                     ], | ||||
|                     "type": "ToPoint", | ||||
|                     "units": { | ||||
|                       "type": "Mm" | ||||
|                     } | ||||
|                   }, | ||||
|                   "surface": { | ||||
|                     "faceId": "[uuid]", | ||||
|                     "id": "[uuid]", | ||||
|                     "sourceRange": [ | ||||
|                       92, | ||||
|                       125, | ||||
|                       0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 124, | ||||
|                       "start": 118, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg01" | ||||
|                     }, | ||||
|                     "type": "extrudePlane" | ||||
|                   } | ||||
|                 } | ||||
|                 "value": "seg01" | ||||
|               } | ||||
|             }, | ||||
|             "artifactId": "[uuid]", | ||||
| @ -3597,56 +3107,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|                   "tags": { | ||||
|                     "seg01": { | ||||
|                       "type": "TagIdentifier", | ||||
|                       "value": "seg01", | ||||
|                       "info": { | ||||
|                         "type": "TagEngineInfo", | ||||
|                         "id": "[uuid]", | ||||
|                         "sketch": "[uuid]", | ||||
|                         "path": { | ||||
|                           "__geoMeta": { | ||||
|                             "id": "[uuid]", | ||||
|                             "sourceRange": [ | ||||
|                               92, | ||||
|                               125, | ||||
|                               0 | ||||
|                             ] | ||||
|                           }, | ||||
|                           "from": [ | ||||
|                             4.0, | ||||
|                             8.0 | ||||
|                           ], | ||||
|                           "tag": { | ||||
|                             "end": 124, | ||||
|                             "start": 118, | ||||
|                             "type": "TagDeclarator", | ||||
|                             "value": "seg01" | ||||
|                           }, | ||||
|                           "to": [ | ||||
|                             9.0, | ||||
|                             0.0 | ||||
|                           ], | ||||
|                           "type": "ToPoint", | ||||
|                           "units": { | ||||
|                             "type": "Mm" | ||||
|                           } | ||||
|                         }, | ||||
|                         "surface": { | ||||
|                           "faceId": "[uuid]", | ||||
|                           "id": "[uuid]", | ||||
|                           "sourceRange": [ | ||||
|                             92, | ||||
|                             125, | ||||
|                             0 | ||||
|                           ], | ||||
|                           "tag": { | ||||
|                             "end": 124, | ||||
|                             "start": 118, | ||||
|                             "type": "TagDeclarator", | ||||
|                             "value": "seg01" | ||||
|                           }, | ||||
|                           "type": "extrudePlane" | ||||
|                         } | ||||
|                       } | ||||
|                       "value": "seg01" | ||||
|                     } | ||||
|                   }, | ||||
|                   "artifactId": "[uuid]", | ||||
| @ -3730,56 +3191,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|       "tags": { | ||||
|         "seg02": { | ||||
|           "type": "TagIdentifier", | ||||
|           "value": "seg02", | ||||
|           "info": { | ||||
|             "type": "TagEngineInfo", | ||||
|             "id": "[uuid]", | ||||
|             "sketch": "[uuid]", | ||||
|             "path": { | ||||
|               "__geoMeta": { | ||||
|                 "id": "[uuid]", | ||||
|                 "sourceRange": [ | ||||
|                   577, | ||||
|                   611, | ||||
|                   0 | ||||
|                 ] | ||||
|               }, | ||||
|               "from": [ | ||||
|                 1.0, | ||||
|                 1.5 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 610, | ||||
|                 "start": 604, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg02" | ||||
|               }, | ||||
|               "to": [ | ||||
|                 1.5, | ||||
|                 3.5 | ||||
|               ], | ||||
|               "type": "ToPoint", | ||||
|               "units": { | ||||
|                 "type": "Mm" | ||||
|               } | ||||
|             }, | ||||
|             "surface": { | ||||
|               "faceId": "[uuid]", | ||||
|               "id": "[uuid]", | ||||
|               "sourceRange": [ | ||||
|                 577, | ||||
|                 611, | ||||
|                 0 | ||||
|               ], | ||||
|               "tag": { | ||||
|                 "end": 610, | ||||
|                 "start": 604, | ||||
|                 "type": "TagDeclarator", | ||||
|                 "value": "seg02" | ||||
|               }, | ||||
|               "type": "extrudePlane" | ||||
|             } | ||||
|           } | ||||
|           "value": "seg02" | ||||
|         } | ||||
|       }, | ||||
|       "artifactId": "[uuid]", | ||||
| @ -4430,56 +3842,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|                         "tags": { | ||||
|                           "seg01": { | ||||
|                             "type": "TagIdentifier", | ||||
|                             "value": "seg01", | ||||
|                             "info": { | ||||
|                               "type": "TagEngineInfo", | ||||
|                               "id": "[uuid]", | ||||
|                               "sketch": "[uuid]", | ||||
|                               "path": { | ||||
|                                 "__geoMeta": { | ||||
|                                   "id": "[uuid]", | ||||
|                                   "sourceRange": [ | ||||
|                                     92, | ||||
|                                     125, | ||||
|                                     0 | ||||
|                                   ] | ||||
|                                 }, | ||||
|                                 "from": [ | ||||
|                                   4.0, | ||||
|                                   8.0 | ||||
|                                 ], | ||||
|                                 "tag": { | ||||
|                                   "end": 124, | ||||
|                                   "start": 118, | ||||
|                                   "type": "TagDeclarator", | ||||
|                                   "value": "seg01" | ||||
|                                 }, | ||||
|                                 "to": [ | ||||
|                                   9.0, | ||||
|                                   0.0 | ||||
|                                 ], | ||||
|                                 "type": "ToPoint", | ||||
|                                 "units": { | ||||
|                                   "type": "Mm" | ||||
|                                 } | ||||
|                               }, | ||||
|                               "surface": { | ||||
|                                 "faceId": "[uuid]", | ||||
|                                 "id": "[uuid]", | ||||
|                                 "sourceRange": [ | ||||
|                                   92, | ||||
|                                   125, | ||||
|                                   0 | ||||
|                                 ], | ||||
|                                 "tag": { | ||||
|                                   "end": 124, | ||||
|                                   "start": 118, | ||||
|                                   "type": "TagDeclarator", | ||||
|                                   "value": "seg01" | ||||
|                                 }, | ||||
|                                 "type": "extrudePlane" | ||||
|                               } | ||||
|                             } | ||||
|                             "value": "seg01" | ||||
|                           } | ||||
|                         }, | ||||
|                         "artifactId": "[uuid]", | ||||
| @ -4563,56 +3926,7 @@ description: Variables in memory after executing artifact_graph_sketch_on_face_e | ||||
|             "tags": { | ||||
|               "seg02": { | ||||
|                 "type": "TagIdentifier", | ||||
|                 "value": "seg02", | ||||
|                 "info": { | ||||
|                   "type": "TagEngineInfo", | ||||
|                   "id": "[uuid]", | ||||
|                   "sketch": "[uuid]", | ||||
|                   "path": { | ||||
|                     "__geoMeta": { | ||||
|                       "id": "[uuid]", | ||||
|                       "sourceRange": [ | ||||
|                         577, | ||||
|                         611, | ||||
|                         0 | ||||
|                       ] | ||||
|                     }, | ||||
|                     "from": [ | ||||
|                       1.0, | ||||
|                       1.5 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 610, | ||||
|                       "start": 604, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg02" | ||||
|                     }, | ||||
|                     "to": [ | ||||
|                       1.5, | ||||
|                       3.5 | ||||
|                     ], | ||||
|                     "type": "ToPoint", | ||||
|                     "units": { | ||||
|                       "type": "Mm" | ||||
|                     } | ||||
|                   }, | ||||
|                   "surface": { | ||||
|                     "faceId": "[uuid]", | ||||
|                     "id": "[uuid]", | ||||
|                     "sourceRange": [ | ||||
|                       577, | ||||
|                       611, | ||||
|                       0 | ||||
|                     ], | ||||
|                     "tag": { | ||||
|                       "end": 610, | ||||
|                       "start": 604, | ||||
|                       "type": "TagDeclarator", | ||||
|                       "value": "seg02" | ||||
|                     }, | ||||
|                     "type": "extrudePlane" | ||||
|                   } | ||||
|                 } | ||||
|                 "value": "seg02" | ||||
|               } | ||||
|             }, | ||||
|             "artifactId": "[uuid]", | ||||
|  | ||||
| @ -0,0 +1,836 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact commands assembly_mixed_units_cubes.kcl | ||||
| --- | ||||
| [ | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       0, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "edge_lines_visible", | ||||
|       "hidden": false | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       0, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "set_scene_units", | ||||
|       "unit": "mm" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       0, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "object_visible", | ||||
|       "object_id": "[uuid]", | ||||
|       "hidden": true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       0, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "object_visible", | ||||
|       "object_id": "[uuid]", | ||||
|       "hidden": true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       33, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "set_scene_units", | ||||
|       "unit": "in" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       33, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "set_scene_units", | ||||
|       "unit": "in" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       47, | ||||
|       66, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "make_plane", | ||||
|       "origin": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "x_axis": { | ||||
|         "x": 1.0, | ||||
|         "y": 0.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "y_axis": { | ||||
|         "x": 0.0, | ||||
|         "y": 1.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "size": 60.0, | ||||
|       "clobber": false, | ||||
|       "hide": true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       113, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "enable_sketch_mode", | ||||
|       "entity_id": "[uuid]", | ||||
|       "ortho": false, | ||||
|       "animated": false, | ||||
|       "adjust_camera": false, | ||||
|       "planar_normal": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 1.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       113, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "start_path" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       113, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "move_path_pen", | ||||
|       "path": "[uuid]", | ||||
|       "to": { | ||||
|         "x": -10.0, | ||||
|         "y": -10.0, | ||||
|         "z": 0.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       113, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "sketch_mode_disable" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       119, | ||||
|       136, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": 5.0, | ||||
|           "y": 0.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       142, | ||||
|       160, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": 0.0, | ||||
|           "y": -5.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       166, | ||||
|       184, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": -5.0, | ||||
|           "y": 0.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       190, | ||||
|       246, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": -10.0, | ||||
|           "y": -10.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": false | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       252, | ||||
|       259, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "close_path", | ||||
|       "path_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "enable_sketch_mode", | ||||
|       "entity_id": "[uuid]", | ||||
|       "ortho": false, | ||||
|       "animated": false, | ||||
|       "adjust_camera": false, | ||||
|       "planar_normal": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 1.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extrude", | ||||
|       "target": "[uuid]", | ||||
|       "distance": 5.0, | ||||
|       "faces": null | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "sketch_mode_disable" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "object_bring_to_front", | ||||
|       "object_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_extrusion_face_info", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       265, | ||||
|       287, | ||||
|       3 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       33, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "set_scene_units", | ||||
|       "unit": "mm" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       47, | ||||
|       66, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "make_plane", | ||||
|       "origin": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "x_axis": { | ||||
|         "x": 1.0, | ||||
|         "y": 0.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "y_axis": { | ||||
|         "x": 0.0, | ||||
|         "y": 1.0, | ||||
|         "z": 0.0 | ||||
|       }, | ||||
|       "size": 60.0, | ||||
|       "clobber": false, | ||||
|       "hide": true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       111, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "enable_sketch_mode", | ||||
|       "entity_id": "[uuid]", | ||||
|       "ortho": false, | ||||
|       "animated": false, | ||||
|       "adjust_camera": false, | ||||
|       "planar_normal": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 1.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       111, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "start_path" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       111, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "move_path_pen", | ||||
|       "path": "[uuid]", | ||||
|       "to": { | ||||
|         "x": 10.0, | ||||
|         "y": 10.0, | ||||
|         "z": 0.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       76, | ||||
|       111, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "sketch_mode_disable" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       117, | ||||
|       134, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": 5.0, | ||||
|           "y": 0.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       140, | ||||
|       158, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": 0.0, | ||||
|           "y": -5.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       164, | ||||
|       182, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": -5.0, | ||||
|           "y": 0.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": true | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       188, | ||||
|       244, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extend_path", | ||||
|       "path": "[uuid]", | ||||
|       "segment": { | ||||
|         "type": "line", | ||||
|         "end": { | ||||
|           "x": 10.0, | ||||
|           "y": 10.0, | ||||
|           "z": 0.0 | ||||
|         }, | ||||
|         "relative": false | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       250, | ||||
|       257, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "close_path", | ||||
|       "path_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "enable_sketch_mode", | ||||
|       "entity_id": "[uuid]", | ||||
|       "ortho": false, | ||||
|       "animated": false, | ||||
|       "adjust_camera": false, | ||||
|       "planar_normal": { | ||||
|         "x": 0.0, | ||||
|         "y": 0.0, | ||||
|         "z": 1.0 | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "extrude", | ||||
|       "target": "[uuid]", | ||||
|       "distance": 5.0, | ||||
|       "faces": null | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "sketch_mode_disable" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "object_bring_to_front", | ||||
|       "object_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_extrusion_face_info", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_opposite_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       263, | ||||
|       285, | ||||
|       4 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "solid3d_get_next_adjacent_edge", | ||||
|       "object_id": "[uuid]", | ||||
|       "edge_id": "[uuid]", | ||||
|       "face_id": "[uuid]" | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     "cmdId": "[uuid]", | ||||
|     "range": [ | ||||
|       0, | ||||
|       0, | ||||
|       0 | ||||
|     ], | ||||
|     "command": { | ||||
|       "type": "set_scene_units", | ||||
|       "unit": "in" | ||||
|     } | ||||
|   } | ||||
| ] | ||||
| @ -0,0 +1,6 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Artifact graph flowchart assembly_mixed_units_cubes.kcl | ||||
| extension: md | ||||
| snapshot_kind: binary | ||||
| --- | ||||
| @ -0,0 +1,121 @@ | ||||
| ```mermaid | ||||
| flowchart LR | ||||
|   subgraph path2 [Path] | ||||
|     2["Path<br>[76, 113, 3]"] | ||||
|     3["Segment<br>[119, 136, 3]"] | ||||
|     4["Segment<br>[142, 160, 3]"] | ||||
|     5["Segment<br>[166, 184, 3]"] | ||||
|     6["Segment<br>[190, 246, 3]"] | ||||
|     7["Segment<br>[252, 259, 3]"] | ||||
|     8[Solid2d] | ||||
|   end | ||||
|   subgraph path25 [Path] | ||||
|     25["Path<br>[76, 111, 4]"] | ||||
|     26["Segment<br>[117, 134, 4]"] | ||||
|     27["Segment<br>[140, 158, 4]"] | ||||
|     28["Segment<br>[164, 182, 4]"] | ||||
|     29["Segment<br>[188, 244, 4]"] | ||||
|     30["Segment<br>[250, 257, 4]"] | ||||
|     31[Solid2d] | ||||
|   end | ||||
|   1["Plane<br>[47, 66, 3]"] | ||||
|   9["Sweep Extrusion<br>[265, 287, 3]"] | ||||
|   10[Wall] | ||||
|   11[Wall] | ||||
|   12[Wall] | ||||
|   13[Wall] | ||||
|   14["Cap Start"] | ||||
|   15["Cap End"] | ||||
|   16["SweepEdge Opposite"] | ||||
|   17["SweepEdge Adjacent"] | ||||
|   18["SweepEdge Opposite"] | ||||
|   19["SweepEdge Adjacent"] | ||||
|   20["SweepEdge Opposite"] | ||||
|   21["SweepEdge Adjacent"] | ||||
|   22["SweepEdge Opposite"] | ||||
|   23["SweepEdge Adjacent"] | ||||
|   24["Plane<br>[47, 66, 4]"] | ||||
|   32["Sweep Extrusion<br>[263, 285, 4]"] | ||||
|   33[Wall] | ||||
|   34[Wall] | ||||
|   35[Wall] | ||||
|   36[Wall] | ||||
|   37["Cap Start"] | ||||
|   38["Cap End"] | ||||
|   39["SweepEdge Opposite"] | ||||
|   40["SweepEdge Adjacent"] | ||||
|   41["SweepEdge Opposite"] | ||||
|   42["SweepEdge Adjacent"] | ||||
|   43["SweepEdge Opposite"] | ||||
|   44["SweepEdge Adjacent"] | ||||
|   45["SweepEdge Opposite"] | ||||
|   46["SweepEdge Adjacent"] | ||||
|   1 --- 2 | ||||
|   2 --- 3 | ||||
|   2 --- 4 | ||||
|   2 --- 5 | ||||
|   2 --- 6 | ||||
|   2 --- 7 | ||||
|   2 ---- 9 | ||||
|   2 --- 8 | ||||
|   3 --- 13 | ||||
|   3 --- 22 | ||||
|   3 --- 23 | ||||
|   4 --- 12 | ||||
|   4 --- 20 | ||||
|   4 --- 21 | ||||
|   5 --- 11 | ||||
|   5 --- 18 | ||||
|   5 --- 19 | ||||
|   6 --- 10 | ||||
|   6 --- 16 | ||||
|   6 --- 17 | ||||
|   9 --- 10 | ||||
|   9 --- 11 | ||||
|   9 --- 12 | ||||
|   9 --- 13 | ||||
|   9 --- 14 | ||||
|   9 --- 15 | ||||
|   9 --- 16 | ||||
|   9 --- 17 | ||||
|   9 --- 18 | ||||
|   9 --- 19 | ||||
|   9 --- 20 | ||||
|   9 --- 21 | ||||
|   9 --- 22 | ||||
|   9 --- 23 | ||||
|   24 --- 25 | ||||
|   25 --- 26 | ||||
|   25 --- 27 | ||||
|   25 --- 28 | ||||
|   25 --- 29 | ||||
|   25 --- 30 | ||||
|   25 ---- 32 | ||||
|   25 --- 31 | ||||
|   26 --- 36 | ||||
|   26 --- 45 | ||||
|   26 --- 46 | ||||
|   27 --- 35 | ||||
|   27 --- 43 | ||||
|   27 --- 44 | ||||
|   28 --- 34 | ||||
|   28 --- 41 | ||||
|   28 --- 42 | ||||
|   29 --- 33 | ||||
|   29 --- 39 | ||||
|   29 --- 40 | ||||
|   32 --- 33 | ||||
|   32 --- 34 | ||||
|   32 --- 35 | ||||
|   32 --- 36 | ||||
|   32 --- 37 | ||||
|   32 --- 38 | ||||
|   32 --- 39 | ||||
|   32 --- 40 | ||||
|   32 --- 41 | ||||
|   32 --- 42 | ||||
|   32 --- 43 | ||||
|   32 --- 44 | ||||
|   32 --- 45 | ||||
|   32 --- 46 | ||||
| ``` | ||||
							
								
								
									
										133
									
								
								rust/kcl-lib/tests/assembly_mixed_units_cubes/ast.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								rust/kcl-lib/tests/assembly_mixed_units_cubes/ast.snap
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| --- | ||||
| source: kcl-lib/src/simulation_tests.rs | ||||
| description: Result of parsing assembly_mixed_units_cubes.kcl | ||||
| --- | ||||
| { | ||||
|   "Ok": { | ||||
|     "body": [ | ||||
|       { | ||||
|         "end": 70, | ||||
|         "path": { | ||||
|           "type": "Kcl", | ||||
|           "filename": "cube-inches.kcl" | ||||
|         }, | ||||
|         "selector": { | ||||
|           "type": "None", | ||||
|           "alias": { | ||||
|             "end": 70, | ||||
|             "name": "cubeIn", | ||||
|             "start": 64, | ||||
|             "type": "Identifier" | ||||
|           } | ||||
|         }, | ||||
|         "start": 36, | ||||
|         "type": "ImportStatement", | ||||
|         "type": "ImportStatement" | ||||
|       }, | ||||
|       { | ||||
|         "end": 101, | ||||
|         "path": { | ||||
|           "type": "Kcl", | ||||
|           "filename": "cube-mm.kcl" | ||||
|         }, | ||||
|         "selector": { | ||||
|           "type": "None", | ||||
|           "alias": { | ||||
|             "end": 101, | ||||
|             "name": "cubeMm", | ||||
|             "start": 95, | ||||
|             "type": "Identifier" | ||||
|           } | ||||
|         }, | ||||
|         "start": 71, | ||||
|         "type": "ImportStatement", | ||||
|         "type": "ImportStatement" | ||||
|       }, | ||||
|       { | ||||
|         "end": 109, | ||||
|         "expression": { | ||||
|           "end": 109, | ||||
|           "name": "cubeIn", | ||||
|           "start": 103, | ||||
|           "type": "Identifier", | ||||
|           "type": "Identifier" | ||||
|         }, | ||||
|         "start": 103, | ||||
|         "type": "ExpressionStatement", | ||||
|         "type": "ExpressionStatement" | ||||
|       }, | ||||
|       { | ||||
|         "end": 116, | ||||
|         "expression": { | ||||
|           "end": 116, | ||||
|           "name": "cubeMm", | ||||
|           "start": 110, | ||||
|           "type": "Identifier", | ||||
|           "type": "Identifier" | ||||
|         }, | ||||
|         "start": 110, | ||||
|         "type": "ExpressionStatement", | ||||
|         "type": "ExpressionStatement" | ||||
|       } | ||||
|     ], | ||||
|     "end": 117, | ||||
|     "innerAttrs": [ | ||||
|       { | ||||
|         "end": 33, | ||||
|         "name": { | ||||
|           "end": 9, | ||||
|           "name": "settings", | ||||
|           "start": 1, | ||||
|           "type": "Identifier" | ||||
|         }, | ||||
|         "properties": [ | ||||
|           { | ||||
|             "end": 32, | ||||
|             "key": { | ||||
|               "end": 27, | ||||
|               "name": "defaultLengthUnit", | ||||
|               "start": 10, | ||||
|               "type": "Identifier" | ||||
|             }, | ||||
|             "start": 10, | ||||
|             "type": "ObjectProperty", | ||||
|             "value": { | ||||
|               "end": 32, | ||||
|               "name": "in", | ||||
|               "start": 30, | ||||
|               "type": "Identifier", | ||||
|               "type": "Identifier" | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "start": 0, | ||||
|         "type": "Annotation" | ||||
|       } | ||||
|     ], | ||||
|     "nonCodeMeta": { | ||||
|       "nonCodeNodes": { | ||||
|         "1": [ | ||||
|           { | ||||
|             "end": 103, | ||||
|             "start": 101, | ||||
|             "type": "NonCodeNode", | ||||
|             "value": { | ||||
|               "type": "newLine" | ||||
|             } | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       "startNodes": [ | ||||
|         { | ||||
|           "end": 36, | ||||
|           "start": 33, | ||||
|           "type": "NonCodeNode", | ||||
|           "value": { | ||||
|             "type": "newLine" | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "start": 0 | ||||
|   } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user