@code3d/core 0.0.1-alpha.11 → 0.0.1-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +55 -41
  2. package/bld/chunks/{chunk-HZRQUHM2.js → chunk-776Q5P2L.js} +2 -2
  3. package/bld/chunks/{chunk-QK6ZET47.js → chunk-N7JJWNVL.js} +3 -3
  4. package/bld/chunks/{chunk-CL3E2DE4.js → chunk-VFPWXSFD.js} +2 -2
  5. package/bld/chunks/{chunk-JBVIAMJ6.js → chunk-VINZNODV.js} +589 -26
  6. package/bld/chunks/chunk-VINZNODV.js.map +7 -0
  7. package/bld/library/extrude.d.ts +5 -0
  8. package/bld/library/extrude.d.ts.map +1 -1
  9. package/bld/library/index.d.ts +2 -2
  10. package/bld/library/index.d.ts.map +1 -1
  11. package/bld/library/index.js +17 -1
  12. package/bld/library/inspect.d.ts +3 -1
  13. package/bld/library/inspect.d.ts.map +1 -1
  14. package/bld/library/loft.d.ts +7 -1
  15. package/bld/library/loft.d.ts.map +1 -1
  16. package/bld/library/replicad.js +2 -2
  17. package/bld/library/runtime.d.ts +75 -8
  18. package/bld/library/runtime.d.ts.map +1 -1
  19. package/bld/node/index.js +19 -3
  20. package/bld/node/replicad.js +4 -4
  21. package/bld/tooling/index.js +2 -2
  22. package/docs/api.md +143 -13
  23. package/docs/local-coordinates.md +2 -0
  24. package/docs/runtime.md +52 -2
  25. package/docs/topology.md +6 -0
  26. package/docs/values.md +34 -31
  27. package/package.json +1 -1
  28. package/src/library/extrude.ts +129 -2
  29. package/src/library/index.ts +9 -0
  30. package/src/library/inspect.ts +4 -2
  31. package/src/library/loft.ts +67 -2
  32. package/src/library/runtime.ts +646 -8
  33. package/bld/chunks/chunk-JBVIAMJ6.js.map +0 -7
  34. /package/bld/chunks/{chunk-HZRQUHM2.js.map → chunk-776Q5P2L.js.map} +0 -0
  35. /package/bld/chunks/{chunk-QK6ZET47.js.map → chunk-N7JJWNVL.js.map} +0 -0
  36. /package/bld/chunks/{chunk-CL3E2DE4.js.map → chunk-VFPWXSFD.js.map} +0 -0
@@ -14,6 +14,9 @@ import {
14
14
  makeSphere,
15
15
  makeThreePointArc,
16
16
  makeVertex,
17
+ measureShapeLinearProperties,
18
+ measureShapeSurfaceProperties,
19
+ measureShapeVolumeProperties,
17
20
  sketchCircle,
18
21
  sketchEllipse,
19
22
  sketchPolysides,
@@ -35,7 +38,7 @@ import {
35
38
  type AlignmentGeometry,
36
39
  } from './alignment-geometry.js';
37
40
  import {cache, cachedArtifact} from './cached.js';
38
- import {extrudeWithTopology} from './extrude.js';
41
+ import {extrudeWithTopology, revolveWithTopology} from './extrude.js';
39
42
  import {font, googleFont, type Font} from './font.js';
40
43
  import {
41
44
  anchorAnnotation,
@@ -66,7 +69,7 @@ import {
66
69
  shapeSubshapes,
67
70
  transformShape,
68
71
  } from './kernel-shapes.js';
69
- import {loftWithTopology} from './loft.js';
72
+ import {loftWithTopology, sweepWithTopology} from './loft.js';
70
73
  import {captureModelMaterial, type ModelMaterialSnapshot} from './material.js';
71
74
  import {
72
75
  axisRotation,
@@ -277,6 +280,8 @@ export type ModelOperationKind =
277
280
  | 'sketchFace'
278
281
  | 'text'
279
282
  | 'extrude'
283
+ | 'revolve'
284
+ | 'sweep'
280
285
  | 'primitive'
281
286
  | 'material'
282
287
  | 'scaled'
@@ -851,8 +856,33 @@ export interface Vertex
851
856
  readonly id: VertexId;
852
857
  }
853
858
 
859
+ interface LengthMeasurement {
860
+ /** Actual arc length of this finite edge.
861
+ * @code3d.inspect inspectLength
862
+ */
863
+ readonly length: number;
864
+ }
865
+
866
+ interface AreaMeasurement {
867
+ /** Finite surface area; solids include all boundary faces.
868
+ * @code3d.inspect inspectArea
869
+ */
870
+ readonly area: number;
871
+ }
872
+
873
+ interface VolumeMeasurement {
874
+ /** Volume occupied by the solid, excluding cavities.
875
+ * @code3d.inspect inspectVolume
876
+ */
877
+ readonly volume: number;
878
+ }
879
+
854
880
  export interface Edge
855
- extends LineAnchor, GeometryQueryCapabilities, EdgeTopologyCapabilities {
881
+ extends
882
+ LineAnchor,
883
+ GeometryQueryCapabilities,
884
+ EdgeTopologyCapabilities,
885
+ LengthMeasurement {
856
886
  readonly kind: 'edge';
857
887
  readonly id: EdgeId;
858
888
  readonly start: PointAnchor;
@@ -861,7 +891,11 @@ export interface Edge
861
891
  }
862
892
 
863
893
  export interface Surface
864
- extends FaceAnchor, GeometryQueryCapabilities, SurfaceTopologyCapabilities {
894
+ extends
895
+ FaceAnchor,
896
+ GeometryQueryCapabilities,
897
+ SurfaceTopologyCapabilities,
898
+ AreaMeasurement {
865
899
  readonly kind: 'surface';
866
900
  readonly id: SurfaceId;
867
901
  }
@@ -870,7 +904,9 @@ export interface Solid
870
904
  extends
871
905
  Anchor<'frame'>,
872
906
  GeometryQueryCapabilities,
873
- SurfaceTopologyCapabilities {
907
+ SurfaceTopologyCapabilities,
908
+ AreaMeasurement,
909
+ VolumeMeasurement {
874
910
  readonly kind: 'solid';
875
911
  }
876
912
 
@@ -1099,12 +1135,14 @@ export type VertexModel<Elements extends NamedElements = {}> =
1099
1135
  export type EdgeModel<Elements extends NamedElements = CurveElements> =
1100
1136
  ModelCapabilities<Elements, 'edge'> &
1101
1137
  GeometryCapabilities<Elements, 'edge'> &
1102
- EdgeTopologyCapabilities & {reverse(): Edge} & Elements;
1138
+ EdgeTopologyCapabilities &
1139
+ LengthMeasurement & {reverse(): Edge} & Elements;
1103
1140
 
1104
1141
  export type FaceModel<Elements extends NamedElements = PlanarElements> =
1105
1142
  ModelCapabilities<Elements, 'face'> &
1106
1143
  GeometryCapabilities<Elements, 'face'> &
1107
- SurfaceTopologyCapabilities & {
1144
+ SurfaceTopologyCapabilities &
1145
+ AreaMeasurement & {
1108
1146
  flip(): Surface;
1109
1147
  /**
1110
1148
  * Extrudes along the face's local plane normal. Signed distance; no recentering.
@@ -1113,13 +1151,35 @@ export type FaceModel<Elements extends NamedElements = PlanarElements> =
1113
1151
  * @code3d.param distance {kind: 'length', default: 10, label: 'Extrusion distance'}
1114
1152
  */
1115
1153
  extrude(distance: number): SolidModel;
1154
+ /**
1155
+ * Rotate this face about a straight directed axis.
1156
+ * @code3d.inspect axis revolve.inspectMethodAxis
1157
+ * @code3d.inspect config revolve.inspectMethodProfile
1158
+ * @code3d.param config.angle {kind: 'angle', default: 360, label: 'Revolution angle'}
1159
+ * @code3d.param config.advance {kind: 'length', default: 0, label: 'Axial advance'}
1160
+ */
1161
+ revolve(axis: LineAnchor, config: RevolveConfig): SolidModel;
1162
+ /**
1163
+ * Sweep this face along an open curve beginning at its local origin.
1164
+ * @code3d.inspect this sweep.inspectMethodProfile
1165
+ * @code3d.inspect spine sweep.inspectMethodSpine
1166
+ */
1167
+ sweep(spine: EdgeModel<{}>): SolidModel;
1116
1168
  } & Elements;
1117
1169
 
1170
+ /** Core parameters for rotational and screw-motion solids. */
1171
+ export type RevolveConfig = Readonly<{
1172
+ angle: number;
1173
+ advance?: number;
1174
+ }>;
1175
+
1118
1176
  export type SolidModel<Elements extends NamedElements = CanonicalElements> =
1119
1177
  ModelCapabilities<Elements, 'solid'> &
1120
1178
  GeometryCapabilities<Elements, 'solid'> &
1121
1179
  SurfaceTopologyCapabilities &
1122
1180
  SolidModificationCapabilities<Elements> &
1181
+ AreaMeasurement &
1182
+ VolumeMeasurement &
1123
1183
  Elements;
1124
1184
 
1125
1185
  type RuntimeModel<
@@ -1261,6 +1321,21 @@ class ModelTopologyElement extends ModelAnchor {
1261
1321
  );
1262
1322
  }
1263
1323
 
1324
+ /** @code3d.inspect inspectLength */
1325
+ get length(): number {
1326
+ return measureFiniteGeometry(this, 'length');
1327
+ }
1328
+
1329
+ /** @code3d.inspect inspectArea */
1330
+ get area(): number {
1331
+ return measureFiniteGeometry(this, 'area');
1332
+ }
1333
+
1334
+ /** @code3d.inspect inspectVolume */
1335
+ get volume(): number {
1336
+ return measureFiniteGeometry(this, 'volume');
1337
+ }
1338
+
1264
1339
  get start(): PointAnchor {
1265
1340
  return this.#curvePoint('start', 0);
1266
1341
  }
@@ -1871,6 +1946,35 @@ const referenceBounds = Symbol('referenceBounds');
1871
1946
  const referenceBoundsParts = Symbol('referenceBoundsParts');
1872
1947
  const modelSnapshotQueries = Symbol('modelSnapshotQueries');
1873
1948
  const previewRelation = Symbol('previewRelation');
1949
+ const modelData = new WeakMap<ModelObject, ReadonlyMap<symbol, unknown>>();
1950
+
1951
+ /** Associate package data with a newly constructed model value. */
1952
+ export function setModelData<Value>(
1953
+ model: Model,
1954
+ key: symbol,
1955
+ value: Value,
1956
+ ): void {
1957
+ const object = requireModelObject(
1958
+ model,
1959
+ 'Model data requires a model value.',
1960
+ );
1961
+ modelData.set(
1962
+ object,
1963
+ new Map([...(modelData.get(object) ?? []), [key, value]]),
1964
+ );
1965
+ }
1966
+
1967
+ /** Read package data retained by relation and material copies. */
1968
+ export function getModelData<Value>(
1969
+ model: Model,
1970
+ key: symbol,
1971
+ ): Value | undefined {
1972
+ const object = requireModelObject(
1973
+ model,
1974
+ 'Model data requires a model value.',
1975
+ );
1976
+ return modelData.get(object)?.get(key) as Value | undefined;
1977
+ }
1874
1978
 
1875
1979
  type RelationObjectInit = Readonly<{
1876
1980
  nodeId?: string;
@@ -3037,6 +3141,21 @@ export class ModelObject<
3037
3141
  return new ModelObject<Elements, Kind>(init);
3038
3142
  }
3039
3143
 
3144
+ /** @code3d.inspect inspectLength */
3145
+ get length(): number {
3146
+ return measureFiniteGeometry(this, 'length');
3147
+ }
3148
+
3149
+ /** @code3d.inspect inspectArea */
3150
+ get area(): number {
3151
+ return measureFiniteGeometry(this, 'area');
3152
+ }
3153
+
3154
+ /** @code3d.inspect inspectVolume */
3155
+ get volume(): number {
3156
+ return measureFiniteGeometry(this, 'volume');
3157
+ }
3158
+
3040
3159
  get origin(): PointAnchor {
3041
3160
  return this.frame.origin;
3042
3161
  }
@@ -3584,6 +3703,145 @@ export class ModelObject<
3584
3703
  return result as unknown as SolidModel;
3585
3704
  }
3586
3705
 
3706
+ revolve(
3707
+ this: ModelObject<Elements, 'face'>,
3708
+ axis: LineAnchor,
3709
+ config: RevolveConfig = {angle: 360},
3710
+ ): SolidModel {
3711
+ if (this.kind !== 'face')
3712
+ throw new Error('revolve requires a single face model.');
3713
+ const {angle = 360, advance = 0} = config;
3714
+ if (!Number.isFinite(angle) || angle === 0)
3715
+ throw new Error('Revolution angle must be finite and non-zero.');
3716
+ if (!Number.isFinite(advance))
3717
+ throw new Error('Revolution advance must be finite.');
3718
+ if (advance === 0 && Math.abs(angle) > 360)
3719
+ throw new Error('A revolution without advance cannot exceed one turn.');
3720
+ const axisReference = straightAxisReference(axis, 'revolve() axis');
3721
+ const context = ModelObject.createSolveContext([this, axisReference.model]);
3722
+ const facePose = this.solvePose(context);
3723
+ const axisFrame = relativeTransform(
3724
+ composeTransforms(
3725
+ axisReference.model.solvePose(context),
3726
+ axisReference.transform,
3727
+ ),
3728
+ facePose,
3729
+ );
3730
+ const direction = rotateVector(
3731
+ [0, axisReference.direction ?? 1, 0],
3732
+ axisFrame.quaternion,
3733
+ );
3734
+ const source = this.requireGeometry();
3735
+ const axisModel = isModelObject(axisReference.model)
3736
+ ? axisReference.model
3737
+ : undefined;
3738
+ ModelObject.recordCompositionInspection(context, []);
3739
+ const geometry = evaluateSolidGeometry(
3740
+ 'revolve',
3741
+ [axisFrame.position, direction, angle, advance],
3742
+ [source],
3743
+ () =>
3744
+ revolveWithTopology(
3745
+ {
3746
+ shape: source.value.shape,
3747
+ topology: source.value.topology,
3748
+ namespace: 1,
3749
+ },
3750
+ axisFrame.position,
3751
+ direction,
3752
+ angle,
3753
+ advance,
3754
+ ),
3755
+ );
3756
+ const result = ModelObject.create<CanonicalElements, 'solid'>({
3757
+ kind: 'solid',
3758
+ name: 'Revolve',
3759
+ geometry,
3760
+ material: this.materialSnapshot,
3761
+ placements: this.placements,
3762
+ sourceRefs: [...this.sourceRefs, ...(axisModel?.sourceRefs ?? [])],
3763
+ parameters: uniqueParameters([
3764
+ ...this.allParameters(),
3765
+ ...(axisModel?.allParameters() ?? []),
3766
+ ]),
3767
+ meshTolerance: this.meshTolerance,
3768
+ operation: storedOperation('revolve', [
3769
+ {model: this, role: 'receiver', index: 0},
3770
+ ...(axisModel
3771
+ ? [{model: axisModel, role: 'reference' as const, index: 1}]
3772
+ : []),
3773
+ ]),
3774
+ });
3775
+ ModelObject.recordCompositionInspection(context, [[result, this]]);
3776
+ return result as unknown as SolidModel;
3777
+ }
3778
+
3779
+ sweep(this: ModelObject<Elements, 'face'>, spine: EdgeModel<{}>): SolidModel {
3780
+ if (this.kind !== 'face')
3781
+ throw new Error('sweep requires a single face model.');
3782
+ const path = requireModelKind(
3783
+ spine,
3784
+ 'edge',
3785
+ 'sweep requires an edge model as its spine.',
3786
+ );
3787
+ const context = ModelObject.createSolveContext([this, path]);
3788
+ const profilePose = this.solvePose(context);
3789
+ const pathTransform = relativeTransform(
3790
+ path.solvePose(context),
3791
+ profilePose,
3792
+ );
3793
+ const source = this.requireGeometry();
3794
+ const pathGeometry = path.requireGeometry();
3795
+ const normal = rotateVector(
3796
+ [0, 1, 0],
3797
+ this.geometryAnchor.transform.quaternion,
3798
+ );
3799
+ ModelObject.recordCompositionInspection(context, []);
3800
+ const geometry = evaluateSolidGeometry(
3801
+ 'sweep',
3802
+ [pathTransform.position, pathTransform.quaternion, normal],
3803
+ [source, pathGeometry],
3804
+ () => {
3805
+ const positionedPath = shapeWithTransform(
3806
+ pathGeometry.value.shape as ReplicadEdge,
3807
+ pathTransform,
3808
+ );
3809
+ try {
3810
+ return sweepWithTopology(
3811
+ {
3812
+ shape: source.value.shape,
3813
+ topology: source.value.topology,
3814
+ namespace: 1,
3815
+ },
3816
+ positionedPath,
3817
+ normal,
3818
+ );
3819
+ } finally {
3820
+ positionedPath.delete();
3821
+ }
3822
+ },
3823
+ );
3824
+ const result = ModelObject.create<CanonicalElements, 'solid'>({
3825
+ kind: 'solid',
3826
+ name: 'Sweep',
3827
+ geometry,
3828
+ material: this.materialSnapshot,
3829
+ placements: this.placements,
3830
+ sourceRefs: [...this.sourceRefs, ...path.sourceRefs],
3831
+ parameters: uniqueParameters([
3832
+ ...this.allParameters(),
3833
+ ...path.allParameters(),
3834
+ ]),
3835
+ meshTolerance: Math.min(this.meshTolerance, path.meshTolerance),
3836
+ operation: storedOperation('sweep', [
3837
+ {model: this, role: 'receiver', index: 0},
3838
+ {model: path, role: 'spine', index: 1},
3839
+ ]),
3840
+ });
3841
+ ModelObject.recordCompositionInspection(context, [[result, this]]);
3842
+ return result as unknown as SolidModel;
3843
+ }
3844
+
3587
3845
  cut(
3588
3846
  this: ModelObject<Elements, 'solid'>,
3589
3847
  tools: readonly SolidModel<{}>[],
@@ -4532,6 +4790,46 @@ export class ModelObject<
4532
4790
  };
4533
4791
  }
4534
4792
 
4793
+ /** @internal */
4794
+ static recordRevolve(
4795
+ profile: ModelObject,
4796
+ axis: LineAnchor,
4797
+ result?: ModelObject,
4798
+ ): void {
4799
+ if (!isRecordingInspection()) return;
4800
+ const reference = straightAxisReference(axis, 'revolve() axis');
4801
+ const context = this.createSolveContext([profile, reference.model]);
4802
+ this.recordCompositionInspection(
4803
+ context,
4804
+ result ? [[result, profile]] : [],
4805
+ );
4806
+ }
4807
+
4808
+ /** @internal */
4809
+ static inspectRevolve(
4810
+ profile: FaceModel<{}>,
4811
+ axis: LineAnchor,
4812
+ result: SolidModel | undefined,
4813
+ data: CompositionInspectData,
4814
+ focus: 'profile' | 'axis',
4815
+ ): InspectResult {
4816
+ const frame = this.inspectionFrame(data.poses);
4817
+ const shownProfile = frame.positioned(profile)!;
4818
+ const shownAxis = anchorAnnotation(frame.positioned(axis)! as LineAnchor, {
4819
+ direction: 'forward',
4820
+ });
4821
+ const shownResult = result ? frame.positioned(result)! : undefined;
4822
+ return focus === 'axis'
4823
+ ? {
4824
+ ambient: [shownProfile, ...(shownResult ? [shownResult] : [])],
4825
+ target: [shownAxis],
4826
+ }
4827
+ : {
4828
+ ambient: [shownAxis, ...(shownResult ? [shownResult] : [])],
4829
+ target: [shownProfile],
4830
+ };
4831
+ }
4832
+
4535
4833
  /** @internal Derive the focused cut volume using the original solved operands. */
4536
4834
  static inspectCutTools(
4537
4835
  stock: SolidModel<{}>,
@@ -5023,7 +5321,7 @@ export class ModelObject<
5023
5321
  overrides: Partial<ModelObjectInit<Kind>>,
5024
5322
  operation: StoredOperation,
5025
5323
  ): RuntimeModel<Elements, Kind> {
5026
- return ModelObject.create<Elements, Kind>({
5324
+ const result = ModelObject.create<Elements, Kind>({
5027
5325
  kind: this.kind,
5028
5326
  geometry: this.geometry,
5029
5327
  geometryAnchor: this.geometryAnchor,
@@ -5039,6 +5337,11 @@ export class ModelObject<
5039
5337
  operation,
5040
5338
  ...overrides,
5041
5339
  }) as RuntimeModel<Elements, Kind>;
5340
+ if (operation.kind === 'relate' || operation.kind === 'material') {
5341
+ const data = modelData.get(this);
5342
+ if (data) modelData.set(result, data);
5343
+ }
5344
+ return result;
5042
5345
  }
5043
5346
  }
5044
5347
 
@@ -5659,6 +5962,18 @@ export namespace group {
5659
5962
  }
5660
5963
  }
5661
5964
 
5965
+ /** Inspect derived group members at solved poses while preserving input focus. */
5966
+ export function inspectGroupMembers(
5967
+ children: readonly Model[],
5968
+ inputs: readonly Model[],
5969
+ ): InspectResult {
5970
+ const result = ModelObject.inspectGroup(group(children));
5971
+ result.target?.forEach((member, index) => {
5972
+ retainInspectionIdentity(member, inputs[index]);
5973
+ });
5974
+ return result;
5975
+ }
5976
+
5662
5977
  /**
5663
5978
  * Measure finite models, topology, bounds or point references in their solved placement.
5664
5979
  * Without axis, returns the shortest geometric distance. With axis, returns the
@@ -5820,6 +6135,136 @@ export namespace relate {
5820
6135
  }
5821
6136
  }
5822
6137
 
6138
+ type MeasurementInspectData = Readonly<{
6139
+ owner: Model;
6140
+ target: Model | Anchor;
6141
+ placement: DimensionSegment | Readonly<{at: Vec3}>;
6142
+ }>;
6143
+
6144
+ function measureFiniteGeometry(
6145
+ value: ModelObject | ModelTopologyElement,
6146
+ measure: 'length' | 'area' | 'volume',
6147
+ ): number {
6148
+ const topology =
6149
+ value instanceof ModelTopologyElement
6150
+ ? value[anchorReferenceValue].topology
6151
+ : undefined;
6152
+ const owner =
6153
+ value instanceof ModelObject ? value : value[anchorReferenceValue].model;
6154
+ const geometry = (topology?.source ?? (value as ModelObject))[
6155
+ modelGeometry
6156
+ ]()!.value;
6157
+ const scale = topology?.scale ?? 1;
6158
+ const position = (point: Vec3): Vec3 =>
6159
+ topology ? topologyTransform(topology, translation(point)).position : point;
6160
+ const read = (shape: AnyShape): number => {
6161
+ const properties =
6162
+ measure === 'length'
6163
+ ? measureShapeLinearProperties(shape)
6164
+ : measure === 'area'
6165
+ ? measureShapeSurfaceProperties(shape as ReplicadFace | Shape3D)
6166
+ : measureShapeVolumeProperties(shape as Shape3D);
6167
+ try {
6168
+ const magnitude =
6169
+ 'length' in properties
6170
+ ? properties.length
6171
+ : 'area' in properties
6172
+ ? properties.area
6173
+ : properties.volume;
6174
+ const power = measure === 'length' ? 1 : measure === 'area' ? 2 : 3;
6175
+ const result = magnitude * scale ** power;
6176
+ if (isRecordingInspection()) {
6177
+ const edge = shape as ReplicadEdge;
6178
+ const edgePoint = (parameter: number): Vec3 => {
6179
+ const point = edge.pointAt(parameter);
6180
+ try {
6181
+ return position(point.toTuple());
6182
+ } finally {
6183
+ point.delete();
6184
+ }
6185
+ };
6186
+ const placement =
6187
+ measure === 'length' && edge.geomType === 'LINE'
6188
+ ? {start: edgePoint(0), end: edgePoint(1)}
6189
+ : {
6190
+ at:
6191
+ measure === 'length'
6192
+ ? edgePoint(0.5)
6193
+ : position(properties.centerOfMass),
6194
+ };
6195
+ const target =
6196
+ value instanceof ModelObject
6197
+ ? value.kind === 'edge'
6198
+ ? value.edges()[0]
6199
+ : value.kind === 'face'
6200
+ ? value.surfaces()[0]
6201
+ : value
6202
+ : value;
6203
+ captureInspectData({owner, target, placement});
6204
+ }
6205
+ return result;
6206
+ } finally {
6207
+ properties.delete();
6208
+ }
6209
+ };
6210
+ return topology
6211
+ ? withTopologyShape(
6212
+ geometry.shape,
6213
+ geometry.topology,
6214
+ topology.selection,
6215
+ read,
6216
+ )
6217
+ : read(geometry.shape);
6218
+ }
6219
+
6220
+ /** @internal */
6221
+ export function inspectLength(
6222
+ _args: readonly [],
6223
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6224
+ ): InspectResult | undefined {
6225
+ return inspectMeasurement(context, 'length');
6226
+ }
6227
+
6228
+ /** @internal */
6229
+ export function inspectArea(
6230
+ _args: readonly [],
6231
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6232
+ ): InspectResult | undefined {
6233
+ return inspectMeasurement(context, 'area');
6234
+ }
6235
+
6236
+ /** @internal */
6237
+ export function inspectVolume(
6238
+ _args: readonly [],
6239
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6240
+ ): InspectResult | undefined {
6241
+ return inspectMeasurement(context, 'volume');
6242
+ }
6243
+
6244
+ function inspectMeasurement(
6245
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6246
+ kind: 'length' | 'area' | 'volume',
6247
+ ): InspectResult | undefined {
6248
+ if (context.return === undefined || !context.data) return;
6249
+ const {owner, target, placement} = context.data;
6250
+ return {
6251
+ target: [
6252
+ target,
6253
+ dimension({
6254
+ owner,
6255
+ value: context.return,
6256
+ ...placement,
6257
+ axisLabel:
6258
+ kind === 'length'
6259
+ ? 'at' in placement
6260
+ ? 'arc length'
6261
+ : undefined
6262
+ : kind,
6263
+ }),
6264
+ ],
6265
+ };
6266
+ }
6267
+
5823
6268
  /** @internal */
5824
6269
  export function inspectTopologyReference(
5825
6270
  _args: readonly unknown[],
@@ -5927,6 +6372,57 @@ export function extrude(
5927
6372
  }
5928
6373
  }
5929
6374
 
6375
+ /**
6376
+ * @code3d.inspect profile revolve.inspectProfile
6377
+ * @code3d.inspect axis revolve.inspectAxis
6378
+ * @code3d.inspect config revolve.inspectProfile
6379
+ * @code3d.param config.angle {kind: 'angle', default: 360, label: 'Revolution angle'}
6380
+ * @code3d.param config.advance {kind: 'length', default: 0, label: 'Axial advance'}
6381
+ */
6382
+ export function revolve(
6383
+ profile: FaceModel<{}>,
6384
+ axis: LineAnchor,
6385
+ config: RevolveConfig,
6386
+ ): SolidModel;
6387
+ export function revolve(
6388
+ profile: FaceModel<{}>,
6389
+ axis: LineAnchor,
6390
+ config: RevolveConfig = {angle: 360},
6391
+ ): SolidModel {
6392
+ const runtimeProfile = requireModelKind(
6393
+ profile,
6394
+ 'face',
6395
+ 'revolve requires a face model.',
6396
+ );
6397
+ let result: SolidModel | undefined;
6398
+ try {
6399
+ result = runtimeProfile.revolve(axis, config);
6400
+ return result;
6401
+ } finally {
6402
+ ModelObject.recordRevolve(
6403
+ runtimeProfile,
6404
+ axis,
6405
+ result as ModelObject | undefined,
6406
+ );
6407
+ }
6408
+ }
6409
+
6410
+ /**
6411
+ * Sweeps one planar profile along an open curve.
6412
+ * @code3d.inspect profile sweep.inspectProfile
6413
+ * @code3d.inspect spine sweep.inspectSpine
6414
+ */
6415
+ export function sweep(
6416
+ profile: FaceModel<{}>,
6417
+ spine: EdgeModel<{}>,
6418
+ ): SolidModel {
6419
+ return requireModelKind(
6420
+ profile,
6421
+ 'face',
6422
+ 'sweep requires a face model.',
6423
+ ).sweep(spine);
6424
+ }
6425
+
5930
6426
  /**
5931
6427
  * Creates connected text faces on the XZ plane: +X right, -Z up, normal +Y.
5932
6428
  * All faces share the baseline origin. Size is the font em in model units.
@@ -6474,6 +6970,8 @@ export const authoringApi = Object.freeze({
6474
6970
  bezier,
6475
6971
  spline,
6476
6972
  loft,
6973
+ revolve,
6974
+ sweep,
6477
6975
  box,
6478
6976
  cylinder,
6479
6977
  tube,
@@ -7504,6 +8002,146 @@ export namespace extrude {
7504
8002
  }
7505
8003
  }
7506
8004
 
8005
+ /** @internal */
8006
+ export namespace revolve {
8007
+ export function inspectProfile(
8008
+ [profile, axis]: [FaceModel<{}>, LineAnchor, RevolveConfig],
8009
+ context: InspectContext<
8010
+ SolidModel,
8011
+ unknown,
8012
+ CompositionInspectData | undefined
8013
+ >,
8014
+ ): InspectResult | undefined {
8015
+ return (
8016
+ context.data &&
8017
+ ModelObject.inspectRevolve(
8018
+ profile,
8019
+ axis,
8020
+ context.return,
8021
+ context.data,
8022
+ 'profile',
8023
+ )
8024
+ );
8025
+ }
8026
+
8027
+ export function inspectAxis(
8028
+ [profile, axis]: [FaceModel<{}>, LineAnchor, RevolveConfig],
8029
+ context: InspectContext<
8030
+ SolidModel,
8031
+ unknown,
8032
+ CompositionInspectData | undefined
8033
+ >,
8034
+ ): InspectResult | undefined {
8035
+ return (
8036
+ context.data &&
8037
+ ModelObject.inspectRevolve(
8038
+ profile,
8039
+ axis,
8040
+ context.return,
8041
+ context.data,
8042
+ 'axis',
8043
+ )
8044
+ );
8045
+ }
8046
+
8047
+ export function inspectMethodProfile(
8048
+ [axis]: [LineAnchor, RevolveConfig],
8049
+ context: InspectContext<
8050
+ SolidModel,
8051
+ FaceModel<{}>,
8052
+ CompositionInspectData | undefined
8053
+ >,
8054
+ ): InspectResult | undefined {
8055
+ return (
8056
+ context.data &&
8057
+ ModelObject.inspectRevolve(
8058
+ context.receiver,
8059
+ axis,
8060
+ context.return,
8061
+ context.data,
8062
+ 'profile',
8063
+ )
8064
+ );
8065
+ }
8066
+
8067
+ export function inspectMethodAxis(
8068
+ [axis]: [LineAnchor, RevolveConfig],
8069
+ context: InspectContext<
8070
+ SolidModel,
8071
+ FaceModel<{}>,
8072
+ CompositionInspectData | undefined
8073
+ >,
8074
+ ): InspectResult | undefined {
8075
+ return (
8076
+ context.data &&
8077
+ ModelObject.inspectRevolve(
8078
+ context.receiver,
8079
+ axis,
8080
+ context.return,
8081
+ context.data,
8082
+ 'axis',
8083
+ )
8084
+ );
8085
+ }
8086
+ }
8087
+
8088
+ /** @internal */
8089
+ export namespace sweep {
8090
+ export function inspectProfile(
8091
+ [profile, spine]: [FaceModel<{}>, EdgeModel<{}>],
8092
+ context: InspectContext<
8093
+ SolidModel,
8094
+ unknown,
8095
+ CompositionInspectData | undefined
8096
+ >,
8097
+ ): InspectResult | undefined {
8098
+ if (!context.data) return undefined;
8099
+ return ModelObject.inspectComposition(
8100
+ context.data,
8101
+ [spine, ...(context.return ? [context.return] : [])],
8102
+ [profile],
8103
+ );
8104
+ }
8105
+
8106
+ export function inspectSpine(
8107
+ [profile, spine]: [FaceModel<{}>, EdgeModel<{}>],
8108
+ context: InspectContext<
8109
+ SolidModel,
8110
+ unknown,
8111
+ CompositionInspectData | undefined
8112
+ >,
8113
+ ): InspectResult | undefined {
8114
+ if (!context.data) return undefined;
8115
+ return ModelObject.inspectComposition(
8116
+ context.data,
8117
+ [profile, ...(context.return ? [context.return] : [])],
8118
+ [spine],
8119
+ );
8120
+ }
8121
+
8122
+ export function inspectMethodProfile(
8123
+ [spine]: [EdgeModel<{}>],
8124
+ context: InspectContext<
8125
+ SolidModel,
8126
+ FaceModel<{}>,
8127
+ CompositionInspectData | undefined
8128
+ >,
8129
+ ): InspectResult | undefined {
8130
+ return inspectProfile([context.receiver, spine], context);
8131
+ }
8132
+
8133
+ export function inspectMethodSpine(
8134
+ [spine]: [EdgeModel<{}>],
8135
+ context: InspectContext<
8136
+ SolidModel,
8137
+ FaceModel<{}>,
8138
+ CompositionInspectData | undefined
8139
+ >,
8140
+ ): InspectResult | undefined {
8141
+ return inspectSpine([context.receiver, spine], context);
8142
+ }
8143
+ }
8144
+
7507
8145
  /** @internal */
7508
8146
  export namespace union {
7509
8147
  export function inspectOperands(