@code3d/core 0.0.1-alpha.10 → 0.0.1-alpha.12

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 +18 -13
  2. package/bld/chunks/{chunk-TTJOM4DJ.js → chunk-CQKOD54C.js} +575 -32
  3. package/bld/chunks/chunk-CQKOD54C.js.map +7 -0
  4. package/bld/chunks/{chunk-IJHQ57VL.js → chunk-JF6QRICU.js} +2 -2
  5. package/bld/chunks/{chunk-5IP37Z5U.js → chunk-L4V6X7RH.js} +2 -2
  6. package/bld/chunks/{chunk-HBRLIGQN.js → chunk-QJVTVAYM.js} +3 -3
  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 +11 -1
  12. package/bld/library/inspect.d.ts +5 -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 +68 -7
  18. package/bld/library/runtime.d.ts.map +1 -1
  19. package/bld/node/index.js +13 -3
  20. package/bld/node/replicad.js +4 -4
  21. package/bld/tooling/index.js +2 -2
  22. package/docs/api.md +134 -13
  23. package/docs/local-coordinates.md +2 -0
  24. package/docs/runtime.md +42 -2
  25. package/docs/topology.md +6 -0
  26. package/docs/values.md +9 -0
  27. package/package.json +1 -1
  28. package/src/library/extrude.ts +129 -2
  29. package/src/library/index.ts +6 -0
  30. package/src/library/inspect.ts +6 -2
  31. package/src/library/loft.ts +67 -2
  32. package/src/library/runtime.ts +624 -13
  33. package/bld/chunks/chunk-TTJOM4DJ.js.map +0 -7
  34. /package/bld/chunks/{chunk-IJHQ57VL.js.map → chunk-JF6QRICU.js.map} +0 -0
  35. /package/bld/chunks/{chunk-5IP37Z5U.js.map → chunk-L4V6X7RH.js.map} +0 -0
  36. /package/bld/chunks/{chunk-HBRLIGQN.js.map → chunk-QJVTVAYM.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,
@@ -227,6 +230,7 @@ type RelateInspectionContext = RelateInspectData &
227
230
  owns(value: unknown): boolean;
228
231
  poses(
229
232
  relation?: RelationExpression,
233
+ insertion?: number,
230
234
  ): ReadonlyMap<RelationObject, RigidTransform>;
231
235
  }>;
232
236
 
@@ -276,6 +280,8 @@ export type ModelOperationKind =
276
280
  | 'sketchFace'
277
281
  | 'text'
278
282
  | 'extrude'
283
+ | 'revolve'
284
+ | 'sweep'
279
285
  | 'primitive'
280
286
  | 'material'
281
287
  | 'scaled'
@@ -850,8 +856,33 @@ export interface Vertex
850
856
  readonly id: VertexId;
851
857
  }
852
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
+
853
880
  export interface Edge
854
- extends LineAnchor, GeometryQueryCapabilities, EdgeTopologyCapabilities {
881
+ extends
882
+ LineAnchor,
883
+ GeometryQueryCapabilities,
884
+ EdgeTopologyCapabilities,
885
+ LengthMeasurement {
855
886
  readonly kind: 'edge';
856
887
  readonly id: EdgeId;
857
888
  readonly start: PointAnchor;
@@ -860,7 +891,11 @@ export interface Edge
860
891
  }
861
892
 
862
893
  export interface Surface
863
- extends FaceAnchor, GeometryQueryCapabilities, SurfaceTopologyCapabilities {
894
+ extends
895
+ FaceAnchor,
896
+ GeometryQueryCapabilities,
897
+ SurfaceTopologyCapabilities,
898
+ AreaMeasurement {
864
899
  readonly kind: 'surface';
865
900
  readonly id: SurfaceId;
866
901
  }
@@ -869,7 +904,9 @@ export interface Solid
869
904
  extends
870
905
  Anchor<'frame'>,
871
906
  GeometryQueryCapabilities,
872
- SurfaceTopologyCapabilities {
907
+ SurfaceTopologyCapabilities,
908
+ AreaMeasurement,
909
+ VolumeMeasurement {
873
910
  readonly kind: 'solid';
874
911
  }
875
912
 
@@ -1098,12 +1135,14 @@ export type VertexModel<Elements extends NamedElements = {}> =
1098
1135
  export type EdgeModel<Elements extends NamedElements = CurveElements> =
1099
1136
  ModelCapabilities<Elements, 'edge'> &
1100
1137
  GeometryCapabilities<Elements, 'edge'> &
1101
- EdgeTopologyCapabilities & {reverse(): Edge} & Elements;
1138
+ EdgeTopologyCapabilities &
1139
+ LengthMeasurement & {reverse(): Edge} & Elements;
1102
1140
 
1103
1141
  export type FaceModel<Elements extends NamedElements = PlanarElements> =
1104
1142
  ModelCapabilities<Elements, 'face'> &
1105
1143
  GeometryCapabilities<Elements, 'face'> &
1106
- SurfaceTopologyCapabilities & {
1144
+ SurfaceTopologyCapabilities &
1145
+ AreaMeasurement & {
1107
1146
  flip(): Surface;
1108
1147
  /**
1109
1148
  * Extrudes along the face's local plane normal. Signed distance; no recentering.
@@ -1112,13 +1151,35 @@ export type FaceModel<Elements extends NamedElements = PlanarElements> =
1112
1151
  * @code3d.param distance {kind: 'length', default: 10, label: 'Extrusion distance'}
1113
1152
  */
1114
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;
1115
1168
  } & Elements;
1116
1169
 
1170
+ /** Core parameters for rotational and screw-motion solids. */
1171
+ export type RevolveConfig = Readonly<{
1172
+ angle: number;
1173
+ advance?: number;
1174
+ }>;
1175
+
1117
1176
  export type SolidModel<Elements extends NamedElements = CanonicalElements> =
1118
1177
  ModelCapabilities<Elements, 'solid'> &
1119
1178
  GeometryCapabilities<Elements, 'solid'> &
1120
1179
  SurfaceTopologyCapabilities &
1121
1180
  SolidModificationCapabilities<Elements> &
1181
+ AreaMeasurement &
1182
+ VolumeMeasurement &
1122
1183
  Elements;
1123
1184
 
1124
1185
  type RuntimeModel<
@@ -1260,6 +1321,21 @@ class ModelTopologyElement extends ModelAnchor {
1260
1321
  );
1261
1322
  }
1262
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
+
1263
1339
  get start(): PointAnchor {
1264
1340
  return this.#curvePoint('start', 0);
1265
1341
  }
@@ -1992,7 +2068,21 @@ export abstract class RelationObject {
1992
2068
  /** @internal Continuous constraints solve together; transformations cut the ordered prefix. */
1993
2069
  inspectionPoses(
1994
2070
  relation?: RelationExpression,
2071
+ insertion?: number,
1995
2072
  ): ReadonlyMap<RelationObject, RigidTransform> {
2073
+ if (insertion !== undefined) {
2074
+ const placements = this.placements.slice(0, insertion);
2075
+ if (placements.at(-1)?.kind !== 'transformation')
2076
+ while (
2077
+ placements.length < this.placements.length &&
2078
+ this.placements[placements.length].kind !== 'transformation'
2079
+ )
2080
+ placements.push(this.placements[placements.length]);
2081
+ return RelationObject.createSolveContext(
2082
+ [this],
2083
+ new Map([[this, placements]]),
2084
+ ).poses;
2085
+ }
1996
2086
  if (!relation) return RelationObject.createSolveContext([this]).poses;
1997
2087
  const reference = relation.traceReference();
1998
2088
  const id =
@@ -3022,6 +3112,21 @@ export class ModelObject<
3022
3112
  return new ModelObject<Elements, Kind>(init);
3023
3113
  }
3024
3114
 
3115
+ /** @code3d.inspect inspectLength */
3116
+ get length(): number {
3117
+ return measureFiniteGeometry(this, 'length');
3118
+ }
3119
+
3120
+ /** @code3d.inspect inspectArea */
3121
+ get area(): number {
3122
+ return measureFiniteGeometry(this, 'area');
3123
+ }
3124
+
3125
+ /** @code3d.inspect inspectVolume */
3126
+ get volume(): number {
3127
+ return measureFiniteGeometry(this, 'volume');
3128
+ }
3129
+
3025
3130
  get origin(): PointAnchor {
3026
3131
  return this.frame.origin;
3027
3132
  }
@@ -3569,6 +3674,145 @@ export class ModelObject<
3569
3674
  return result as unknown as SolidModel;
3570
3675
  }
3571
3676
 
3677
+ revolve(
3678
+ this: ModelObject<Elements, 'face'>,
3679
+ axis: LineAnchor,
3680
+ config: RevolveConfig = {angle: 360},
3681
+ ): SolidModel {
3682
+ if (this.kind !== 'face')
3683
+ throw new Error('revolve requires a single face model.');
3684
+ const {angle = 360, advance = 0} = config;
3685
+ if (!Number.isFinite(angle) || angle === 0)
3686
+ throw new Error('Revolution angle must be finite and non-zero.');
3687
+ if (!Number.isFinite(advance))
3688
+ throw new Error('Revolution advance must be finite.');
3689
+ if (advance === 0 && Math.abs(angle) > 360)
3690
+ throw new Error('A revolution without advance cannot exceed one turn.');
3691
+ const axisReference = straightAxisReference(axis, 'revolve() axis');
3692
+ const context = ModelObject.createSolveContext([this, axisReference.model]);
3693
+ const facePose = this.solvePose(context);
3694
+ const axisFrame = relativeTransform(
3695
+ composeTransforms(
3696
+ axisReference.model.solvePose(context),
3697
+ axisReference.transform,
3698
+ ),
3699
+ facePose,
3700
+ );
3701
+ const direction = rotateVector(
3702
+ [0, axisReference.direction ?? 1, 0],
3703
+ axisFrame.quaternion,
3704
+ );
3705
+ const source = this.requireGeometry();
3706
+ const axisModel = isModelObject(axisReference.model)
3707
+ ? axisReference.model
3708
+ : undefined;
3709
+ ModelObject.recordCompositionInspection(context, []);
3710
+ const geometry = evaluateSolidGeometry(
3711
+ 'revolve',
3712
+ [axisFrame.position, direction, angle, advance],
3713
+ [source],
3714
+ () =>
3715
+ revolveWithTopology(
3716
+ {
3717
+ shape: source.value.shape,
3718
+ topology: source.value.topology,
3719
+ namespace: 1,
3720
+ },
3721
+ axisFrame.position,
3722
+ direction,
3723
+ angle,
3724
+ advance,
3725
+ ),
3726
+ );
3727
+ const result = ModelObject.create<CanonicalElements, 'solid'>({
3728
+ kind: 'solid',
3729
+ name: 'Revolve',
3730
+ geometry,
3731
+ material: this.materialSnapshot,
3732
+ placements: this.placements,
3733
+ sourceRefs: [...this.sourceRefs, ...(axisModel?.sourceRefs ?? [])],
3734
+ parameters: uniqueParameters([
3735
+ ...this.allParameters(),
3736
+ ...(axisModel?.allParameters() ?? []),
3737
+ ]),
3738
+ meshTolerance: this.meshTolerance,
3739
+ operation: storedOperation('revolve', [
3740
+ {model: this, role: 'receiver', index: 0},
3741
+ ...(axisModel
3742
+ ? [{model: axisModel, role: 'reference' as const, index: 1}]
3743
+ : []),
3744
+ ]),
3745
+ });
3746
+ ModelObject.recordCompositionInspection(context, [[result, this]]);
3747
+ return result as unknown as SolidModel;
3748
+ }
3749
+
3750
+ sweep(this: ModelObject<Elements, 'face'>, spine: EdgeModel<{}>): SolidModel {
3751
+ if (this.kind !== 'face')
3752
+ throw new Error('sweep requires a single face model.');
3753
+ const path = requireModelKind(
3754
+ spine,
3755
+ 'edge',
3756
+ 'sweep requires an edge model as its spine.',
3757
+ );
3758
+ const context = ModelObject.createSolveContext([this, path]);
3759
+ const profilePose = this.solvePose(context);
3760
+ const pathTransform = relativeTransform(
3761
+ path.solvePose(context),
3762
+ profilePose,
3763
+ );
3764
+ const source = this.requireGeometry();
3765
+ const pathGeometry = path.requireGeometry();
3766
+ const normal = rotateVector(
3767
+ [0, 1, 0],
3768
+ this.geometryAnchor.transform.quaternion,
3769
+ );
3770
+ ModelObject.recordCompositionInspection(context, []);
3771
+ const geometry = evaluateSolidGeometry(
3772
+ 'sweep',
3773
+ [pathTransform.position, pathTransform.quaternion, normal],
3774
+ [source, pathGeometry],
3775
+ () => {
3776
+ const positionedPath = shapeWithTransform(
3777
+ pathGeometry.value.shape as ReplicadEdge,
3778
+ pathTransform,
3779
+ );
3780
+ try {
3781
+ return sweepWithTopology(
3782
+ {
3783
+ shape: source.value.shape,
3784
+ topology: source.value.topology,
3785
+ namespace: 1,
3786
+ },
3787
+ positionedPath,
3788
+ normal,
3789
+ );
3790
+ } finally {
3791
+ positionedPath.delete();
3792
+ }
3793
+ },
3794
+ );
3795
+ const result = ModelObject.create<CanonicalElements, 'solid'>({
3796
+ kind: 'solid',
3797
+ name: 'Sweep',
3798
+ geometry,
3799
+ material: this.materialSnapshot,
3800
+ placements: this.placements,
3801
+ sourceRefs: [...this.sourceRefs, ...path.sourceRefs],
3802
+ parameters: uniqueParameters([
3803
+ ...this.allParameters(),
3804
+ ...path.allParameters(),
3805
+ ]),
3806
+ meshTolerance: Math.min(this.meshTolerance, path.meshTolerance),
3807
+ operation: storedOperation('sweep', [
3808
+ {model: this, role: 'receiver', index: 0},
3809
+ {model: path, role: 'spine', index: 1},
3810
+ ]),
3811
+ });
3812
+ ModelObject.recordCompositionInspection(context, [[result, this]]);
3813
+ return result as unknown as SolidModel;
3814
+ }
3815
+
3572
3816
  cut(
3573
3817
  this: ModelObject<Elements, 'solid'>,
3574
3818
  tools: readonly SolidModel<{}>[],
@@ -4517,6 +4761,46 @@ export class ModelObject<
4517
4761
  };
4518
4762
  }
4519
4763
 
4764
+ /** @internal */
4765
+ static recordRevolve(
4766
+ profile: ModelObject,
4767
+ axis: LineAnchor,
4768
+ result?: ModelObject,
4769
+ ): void {
4770
+ if (!isRecordingInspection()) return;
4771
+ const reference = straightAxisReference(axis, 'revolve() axis');
4772
+ const context = this.createSolveContext([profile, reference.model]);
4773
+ this.recordCompositionInspection(
4774
+ context,
4775
+ result ? [[result, profile]] : [],
4776
+ );
4777
+ }
4778
+
4779
+ /** @internal */
4780
+ static inspectRevolve(
4781
+ profile: FaceModel<{}>,
4782
+ axis: LineAnchor,
4783
+ result: SolidModel | undefined,
4784
+ data: CompositionInspectData,
4785
+ focus: 'profile' | 'axis',
4786
+ ): InspectResult {
4787
+ const frame = this.inspectionFrame(data.poses);
4788
+ const shownProfile = frame.positioned(profile)!;
4789
+ const shownAxis = anchorAnnotation(frame.positioned(axis)! as LineAnchor, {
4790
+ direction: 'forward',
4791
+ });
4792
+ const shownResult = result ? frame.positioned(result)! : undefined;
4793
+ return focus === 'axis'
4794
+ ? {
4795
+ ambient: [shownProfile, ...(shownResult ? [shownResult] : [])],
4796
+ target: [shownAxis],
4797
+ }
4798
+ : {
4799
+ ambient: [shownAxis, ...(shownResult ? [shownResult] : [])],
4800
+ target: [shownProfile],
4801
+ };
4802
+ }
4803
+
4520
4804
  /** @internal Derive the focused cut volume using the original solved operands. */
4521
4805
  static inspectCutTools(
4522
4806
  stock: SolidModel<{}>,
@@ -4653,9 +4937,11 @@ export class ModelObject<
4653
4937
  data: RelateInspectionContext,
4654
4938
  focus: unknown,
4655
4939
  values: readonly PreviewValue[],
4940
+ insertion?: number,
4656
4941
  ): InspectResult {
4657
4942
  const poses = data.poses(
4658
4943
  focus instanceof RelationExpression ? focus : undefined,
4944
+ insertion,
4659
4945
  );
4660
4946
  const frame = this.inspectionFrame(poses);
4661
4947
  const selected = values.flatMap<PreviewValue>(value => {
@@ -5695,7 +5981,9 @@ export namespace relate {
5695
5981
  );
5696
5982
  const context: RelateInspectionContext = {
5697
5983
  ...data,
5698
- poses(relation) {
5984
+ poses(relation, insertion) {
5985
+ if (insertion !== undefined)
5986
+ return data.self.inspectionPoses(undefined, insertion);
5699
5987
  let frame = frames.get(relation);
5700
5988
  if (!frame) {
5701
5989
  frame = data.self.inspectionPoses(relation);
@@ -5730,8 +6018,7 @@ export namespace relate {
5730
6018
  : Set.prototype.values
5731
6019
  ).call(value),
5732
6020
  ];
5733
- const related =
5734
- members.length > 0 && members.every(member => owns(member, seen));
6021
+ const related = members.every(member => owns(member, seen));
5735
6022
  seen.delete(value);
5736
6023
  return related;
5737
6024
  }
@@ -5746,9 +6033,9 @@ export namespace relate {
5746
6033
  const members = Object.keys(value).map(key =>
5747
6034
  Object.getOwnPropertyDescriptor(value, key)!,
5748
6035
  );
5749
- const related =
5750
- members.length > 0 &&
5751
- members.every(member => 'value' in member && owns(member.value, seen));
6036
+ const related = members.every(
6037
+ member => 'value' in member && owns(member.value, seen),
6038
+ );
5752
6039
  seen.delete(value);
5753
6040
  return related;
5754
6041
  }
@@ -5797,10 +6084,141 @@ export namespace relate {
5797
6084
  data,
5798
6085
  context.focused.value,
5799
6086
  context.focused.values,
6087
+ context.focused.insertion,
5800
6088
  );
5801
6089
  }
5802
6090
  }
5803
6091
 
6092
+ type MeasurementInspectData = Readonly<{
6093
+ owner: Model;
6094
+ target: Model | Anchor;
6095
+ placement: DimensionSegment | Readonly<{at: Vec3}>;
6096
+ }>;
6097
+
6098
+ function measureFiniteGeometry(
6099
+ value: ModelObject | ModelTopologyElement,
6100
+ measure: 'length' | 'area' | 'volume',
6101
+ ): number {
6102
+ const topology =
6103
+ value instanceof ModelTopologyElement
6104
+ ? value[anchorReferenceValue].topology
6105
+ : undefined;
6106
+ const owner =
6107
+ value instanceof ModelObject ? value : value[anchorReferenceValue].model;
6108
+ const geometry = (topology?.source ?? (value as ModelObject))[
6109
+ modelGeometry
6110
+ ]()!.value;
6111
+ const scale = topology?.scale ?? 1;
6112
+ const position = (point: Vec3): Vec3 =>
6113
+ topology ? topologyTransform(topology, translation(point)).position : point;
6114
+ const read = (shape: AnyShape): number => {
6115
+ const properties =
6116
+ measure === 'length'
6117
+ ? measureShapeLinearProperties(shape)
6118
+ : measure === 'area'
6119
+ ? measureShapeSurfaceProperties(shape as ReplicadFace | Shape3D)
6120
+ : measureShapeVolumeProperties(shape as Shape3D);
6121
+ try {
6122
+ const magnitude =
6123
+ 'length' in properties
6124
+ ? properties.length
6125
+ : 'area' in properties
6126
+ ? properties.area
6127
+ : properties.volume;
6128
+ const power = measure === 'length' ? 1 : measure === 'area' ? 2 : 3;
6129
+ const result = magnitude * scale ** power;
6130
+ if (isRecordingInspection()) {
6131
+ const edge = shape as ReplicadEdge;
6132
+ const edgePoint = (parameter: number): Vec3 => {
6133
+ const point = edge.pointAt(parameter);
6134
+ try {
6135
+ return position(point.toTuple());
6136
+ } finally {
6137
+ point.delete();
6138
+ }
6139
+ };
6140
+ const placement =
6141
+ measure === 'length' && edge.geomType === 'LINE'
6142
+ ? {start: edgePoint(0), end: edgePoint(1)}
6143
+ : {
6144
+ at:
6145
+ measure === 'length'
6146
+ ? edgePoint(0.5)
6147
+ : position(properties.centerOfMass),
6148
+ };
6149
+ const target =
6150
+ value instanceof ModelObject
6151
+ ? value.kind === 'edge'
6152
+ ? value.edges()[0]
6153
+ : value.kind === 'face'
6154
+ ? value.surfaces()[0]
6155
+ : value
6156
+ : value;
6157
+ captureInspectData({owner, target, placement});
6158
+ }
6159
+ return result;
6160
+ } finally {
6161
+ properties.delete();
6162
+ }
6163
+ };
6164
+ return topology
6165
+ ? withTopologyShape(
6166
+ geometry.shape,
6167
+ geometry.topology,
6168
+ topology.selection,
6169
+ read,
6170
+ )
6171
+ : read(geometry.shape);
6172
+ }
6173
+
6174
+ /** @internal */
6175
+ export function inspectLength(
6176
+ _args: readonly [],
6177
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6178
+ ): InspectResult | undefined {
6179
+ return inspectMeasurement(context, 'length');
6180
+ }
6181
+
6182
+ /** @internal */
6183
+ export function inspectArea(
6184
+ _args: readonly [],
6185
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6186
+ ): InspectResult | undefined {
6187
+ return inspectMeasurement(context, 'area');
6188
+ }
6189
+
6190
+ /** @internal */
6191
+ export function inspectVolume(
6192
+ _args: readonly [],
6193
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6194
+ ): InspectResult | undefined {
6195
+ return inspectMeasurement(context, 'volume');
6196
+ }
6197
+
6198
+ function inspectMeasurement(
6199
+ context: InspectContext<number, unknown, MeasurementInspectData | undefined>,
6200
+ kind: 'length' | 'area' | 'volume',
6201
+ ): InspectResult | undefined {
6202
+ if (context.return === undefined || !context.data) return;
6203
+ const {owner, target, placement} = context.data;
6204
+ return {
6205
+ target: [
6206
+ target,
6207
+ dimension({
6208
+ owner,
6209
+ value: context.return,
6210
+ ...placement,
6211
+ axisLabel:
6212
+ kind === 'length'
6213
+ ? 'at' in placement
6214
+ ? 'arc length'
6215
+ : undefined
6216
+ : kind,
6217
+ }),
6218
+ ],
6219
+ };
6220
+ }
6221
+
5804
6222
  /** @internal */
5805
6223
  export function inspectTopologyReference(
5806
6224
  _args: readonly unknown[],
@@ -5908,6 +6326,57 @@ export function extrude(
5908
6326
  }
5909
6327
  }
5910
6328
 
6329
+ /**
6330
+ * @code3d.inspect profile revolve.inspectProfile
6331
+ * @code3d.inspect axis revolve.inspectAxis
6332
+ * @code3d.inspect config revolve.inspectProfile
6333
+ * @code3d.param config.angle {kind: 'angle', default: 360, label: 'Revolution angle'}
6334
+ * @code3d.param config.advance {kind: 'length', default: 0, label: 'Axial advance'}
6335
+ */
6336
+ export function revolve(
6337
+ profile: FaceModel<{}>,
6338
+ axis: LineAnchor,
6339
+ config: RevolveConfig,
6340
+ ): SolidModel;
6341
+ export function revolve(
6342
+ profile: FaceModel<{}>,
6343
+ axis: LineAnchor,
6344
+ config: RevolveConfig = {angle: 360},
6345
+ ): SolidModel {
6346
+ const runtimeProfile = requireModelKind(
6347
+ profile,
6348
+ 'face',
6349
+ 'revolve requires a face model.',
6350
+ );
6351
+ let result: SolidModel | undefined;
6352
+ try {
6353
+ result = runtimeProfile.revolve(axis, config);
6354
+ return result;
6355
+ } finally {
6356
+ ModelObject.recordRevolve(
6357
+ runtimeProfile,
6358
+ axis,
6359
+ result as ModelObject | undefined,
6360
+ );
6361
+ }
6362
+ }
6363
+
6364
+ /**
6365
+ * Sweeps one planar profile along an open curve.
6366
+ * @code3d.inspect profile sweep.inspectProfile
6367
+ * @code3d.inspect spine sweep.inspectSpine
6368
+ */
6369
+ export function sweep(
6370
+ profile: FaceModel<{}>,
6371
+ spine: EdgeModel<{}>,
6372
+ ): SolidModel {
6373
+ return requireModelKind(
6374
+ profile,
6375
+ 'face',
6376
+ 'sweep requires a face model.',
6377
+ ).sweep(spine);
6378
+ }
6379
+
5911
6380
  /**
5912
6381
  * Creates connected text faces on the XZ plane: +X right, -Z up, normal +Y.
5913
6382
  * All faces share the baseline origin. Size is the font em in model units.
@@ -6455,6 +6924,8 @@ export const authoringApi = Object.freeze({
6455
6924
  bezier,
6456
6925
  spline,
6457
6926
  loft,
6927
+ revolve,
6928
+ sweep,
6458
6929
  box,
6459
6930
  cylinder,
6460
6931
  tube,
@@ -7485,6 +7956,146 @@ export namespace extrude {
7485
7956
  }
7486
7957
  }
7487
7958
 
7959
+ /** @internal */
7960
+ export namespace revolve {
7961
+ export function inspectProfile(
7962
+ [profile, axis]: [FaceModel<{}>, LineAnchor, RevolveConfig],
7963
+ context: InspectContext<
7964
+ SolidModel,
7965
+ unknown,
7966
+ CompositionInspectData | undefined
7967
+ >,
7968
+ ): InspectResult | undefined {
7969
+ return (
7970
+ context.data &&
7971
+ ModelObject.inspectRevolve(
7972
+ profile,
7973
+ axis,
7974
+ context.return,
7975
+ context.data,
7976
+ 'profile',
7977
+ )
7978
+ );
7979
+ }
7980
+
7981
+ export function inspectAxis(
7982
+ [profile, axis]: [FaceModel<{}>, LineAnchor, RevolveConfig],
7983
+ context: InspectContext<
7984
+ SolidModel,
7985
+ unknown,
7986
+ CompositionInspectData | undefined
7987
+ >,
7988
+ ): InspectResult | undefined {
7989
+ return (
7990
+ context.data &&
7991
+ ModelObject.inspectRevolve(
7992
+ profile,
7993
+ axis,
7994
+ context.return,
7995
+ context.data,
7996
+ 'axis',
7997
+ )
7998
+ );
7999
+ }
8000
+
8001
+ export function inspectMethodProfile(
8002
+ [axis]: [LineAnchor, RevolveConfig],
8003
+ context: InspectContext<
8004
+ SolidModel,
8005
+ FaceModel<{}>,
8006
+ CompositionInspectData | undefined
8007
+ >,
8008
+ ): InspectResult | undefined {
8009
+ return (
8010
+ context.data &&
8011
+ ModelObject.inspectRevolve(
8012
+ context.receiver,
8013
+ axis,
8014
+ context.return,
8015
+ context.data,
8016
+ 'profile',
8017
+ )
8018
+ );
8019
+ }
8020
+
8021
+ export function inspectMethodAxis(
8022
+ [axis]: [LineAnchor, RevolveConfig],
8023
+ context: InspectContext<
8024
+ SolidModel,
8025
+ FaceModel<{}>,
8026
+ CompositionInspectData | undefined
8027
+ >,
8028
+ ): InspectResult | undefined {
8029
+ return (
8030
+ context.data &&
8031
+ ModelObject.inspectRevolve(
8032
+ context.receiver,
8033
+ axis,
8034
+ context.return,
8035
+ context.data,
8036
+ 'axis',
8037
+ )
8038
+ );
8039
+ }
8040
+ }
8041
+
8042
+ /** @internal */
8043
+ export namespace sweep {
8044
+ export function inspectProfile(
8045
+ [profile, spine]: [FaceModel<{}>, EdgeModel<{}>],
8046
+ context: InspectContext<
8047
+ SolidModel,
8048
+ unknown,
8049
+ CompositionInspectData | undefined
8050
+ >,
8051
+ ): InspectResult | undefined {
8052
+ if (!context.data) return undefined;
8053
+ return ModelObject.inspectComposition(
8054
+ context.data,
8055
+ [spine, ...(context.return ? [context.return] : [])],
8056
+ [profile],
8057
+ );
8058
+ }
8059
+
8060
+ export function inspectSpine(
8061
+ [profile, spine]: [FaceModel<{}>, EdgeModel<{}>],
8062
+ context: InspectContext<
8063
+ SolidModel,
8064
+ unknown,
8065
+ CompositionInspectData | undefined
8066
+ >,
8067
+ ): InspectResult | undefined {
8068
+ if (!context.data) return undefined;
8069
+ return ModelObject.inspectComposition(
8070
+ context.data,
8071
+ [profile, ...(context.return ? [context.return] : [])],
8072
+ [spine],
8073
+ );
8074
+ }
8075
+
8076
+ export function inspectMethodProfile(
8077
+ [spine]: [EdgeModel<{}>],
8078
+ context: InspectContext<
8079
+ SolidModel,
8080
+ FaceModel<{}>,
8081
+ CompositionInspectData | undefined
8082
+ >,
8083
+ ): InspectResult | undefined {
8084
+ return inspectProfile([context.receiver, spine], context);
8085
+ }
8086
+
8087
+ export function inspectMethodSpine(
8088
+ [spine]: [EdgeModel<{}>],
8089
+ context: InspectContext<
8090
+ SolidModel,
8091
+ FaceModel<{}>,
8092
+ CompositionInspectData | undefined
8093
+ >,
8094
+ ): InspectResult | undefined {
8095
+ return inspectSpine([context.receiver, spine], context);
8096
+ }
8097
+ }
8098
+
7488
8099
  /** @internal */
7489
8100
  export namespace union {
7490
8101
  export function inspectOperands(