@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.
- package/README.md +55 -41
- package/bld/chunks/{chunk-HZRQUHM2.js → chunk-776Q5P2L.js} +2 -2
- package/bld/chunks/{chunk-QK6ZET47.js → chunk-N7JJWNVL.js} +3 -3
- package/bld/chunks/{chunk-CL3E2DE4.js → chunk-VFPWXSFD.js} +2 -2
- package/bld/chunks/{chunk-JBVIAMJ6.js → chunk-VINZNODV.js} +589 -26
- package/bld/chunks/chunk-VINZNODV.js.map +7 -0
- 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 +17 -1
- package/bld/library/inspect.d.ts +3 -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 +75 -8
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/node/index.js +19 -3
- package/bld/node/replicad.js +4 -4
- package/bld/tooling/index.js +2 -2
- package/docs/api.md +143 -13
- package/docs/local-coordinates.md +2 -0
- package/docs/runtime.md +52 -2
- package/docs/topology.md +6 -0
- package/docs/values.md +34 -31
- package/package.json +1 -1
- package/src/library/extrude.ts +129 -2
- package/src/library/index.ts +9 -0
- package/src/library/inspect.ts +4 -2
- package/src/library/loft.ts +67 -2
- package/src/library/runtime.ts +646 -8
- package/bld/chunks/chunk-JBVIAMJ6.js.map +0 -7
- /package/bld/chunks/{chunk-HZRQUHM2.js.map → chunk-776Q5P2L.js.map} +0 -0
- /package/bld/chunks/{chunk-QK6ZET47.js.map → chunk-N7JJWNVL.js.map} +0 -0
- /package/bld/chunks/{chunk-CL3E2DE4.js.map → chunk-VFPWXSFD.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,
|
|
@@ -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
|
|
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
|
|
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 &
|
|
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
|
-
|
|
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(
|