@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.
- package/README.md +18 -13
- package/bld/chunks/{chunk-TTJOM4DJ.js → chunk-CQKOD54C.js} +575 -32
- package/bld/chunks/chunk-CQKOD54C.js.map +7 -0
- package/bld/chunks/{chunk-IJHQ57VL.js → chunk-JF6QRICU.js} +2 -2
- package/bld/chunks/{chunk-5IP37Z5U.js → chunk-L4V6X7RH.js} +2 -2
- package/bld/chunks/{chunk-HBRLIGQN.js → chunk-QJVTVAYM.js} +3 -3
- package/bld/library/extrude.d.ts +5 -0
- package/bld/library/extrude.d.ts.map +1 -1
- package/bld/library/index.d.ts +2 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +11 -1
- package/bld/library/inspect.d.ts +5 -1
- package/bld/library/inspect.d.ts.map +1 -1
- package/bld/library/loft.d.ts +7 -1
- package/bld/library/loft.d.ts.map +1 -1
- package/bld/library/replicad.js +2 -2
- package/bld/library/runtime.d.ts +68 -7
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/node/index.js +13 -3
- package/bld/node/replicad.js +4 -4
- package/bld/tooling/index.js +2 -2
- package/docs/api.md +134 -13
- package/docs/local-coordinates.md +2 -0
- package/docs/runtime.md +42 -2
- package/docs/topology.md +6 -0
- package/docs/values.md +9 -0
- package/package.json +1 -1
- package/src/library/extrude.ts +129 -2
- package/src/library/index.ts +6 -0
- package/src/library/inspect.ts +6 -2
- package/src/library/loft.ts +67 -2
- package/src/library/runtime.ts +624 -13
- package/bld/chunks/chunk-TTJOM4DJ.js.map +0 -7
- /package/bld/chunks/{chunk-IJHQ57VL.js.map → chunk-JF6QRICU.js.map} +0 -0
- /package/bld/chunks/{chunk-5IP37Z5U.js.map → chunk-L4V6X7RH.js.map} +0 -0
- /package/bld/chunks/{chunk-HBRLIGQN.js.map → chunk-QJVTVAYM.js.map} +0 -0
package/src/library/runtime.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 &
|
|
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
|
-
|
|
5751
|
-
|
|
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(
|