@constructorfleet/sketchforge-engine 1.27.0 → 1.28.0
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/dist/cadAnalysis.d.ts.map +1 -1
- package/dist/cadAnalysis.js +14 -0
- package/dist/cadAnalysis.js.map +1 -1
- package/dist/cadBakeMetadata.d.ts +11 -2
- package/dist/cadBakeMetadata.d.ts.map +1 -1
- package/dist/cadBakeMetadata.js +9 -0
- package/dist/cadBakeMetadata.js.map +1 -1
- package/dist/cadProjection.d.ts.map +1 -1
- package/dist/cadProjection.js.map +1 -1
- package/dist/cadSection.d.ts.map +1 -1
- package/dist/cadShell.d.ts +1 -1
- package/dist/cadShell.d.ts.map +1 -1
- package/dist/cadShell.js +7 -0
- package/dist/cadShell.js.map +1 -1
- package/dist/cadSolids.d.ts.map +1 -1
- package/dist/cadSolids.js +18 -1
- package/dist/cadSolids.js.map +1 -1
- package/dist/cutBrepFrameMigration.d.ts +46 -0
- package/dist/cutBrepFrameMigration.d.ts.map +1 -0
- package/dist/cutBrepFrameMigration.js +105 -0
- package/dist/cutBrepFrameMigration.js.map +1 -0
- package/dist/cutterPadding.d.ts.map +1 -1
- package/dist/domain/cameraProjection.js.map +1 -1
- package/dist/domain/sketchforge.d.ts +30 -0
- package/dist/domain/sketchforge.d.ts.map +1 -1
- package/dist/editorHistory.d.ts.map +1 -1
- package/dist/editorHistory.js.map +1 -1
- package/dist/engine.d.ts +3 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +102 -66
- package/dist/engine.js.map +1 -1
- package/dist/exactBooleanParts.d.ts +4 -2
- package/dist/exactBooleanParts.d.ts.map +1 -1
- package/dist/exactBooleanParts.js +7 -3
- package/dist/exactBooleanParts.js.map +1 -1
- package/dist/exactBooleanSolids.d.ts.map +1 -1
- package/dist/gearProfile.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loftProfile.d.ts +1 -1
- package/dist/loftProfile.d.ts.map +1 -1
- package/dist/loftProfile.js.map +1 -1
- package/dist/meshEdgeProjection.d.ts +104 -0
- package/dist/meshEdgeProjection.d.ts.map +1 -0
- package/dist/meshEdgeProjection.js +439 -0
- package/dist/meshEdgeProjection.js.map +1 -0
- package/dist/meshImport.d.ts.map +1 -1
- package/dist/meshImport.js +2 -20
- package/dist/meshImport.js.map +1 -1
- package/dist/meshMassProperties.d.ts +92 -0
- package/dist/meshMassProperties.d.ts.map +1 -0
- package/dist/meshMassProperties.js +570 -0
- package/dist/meshMassProperties.js.map +1 -0
- package/dist/meshSection.d.ts +44 -0
- package/dist/meshSection.d.ts.map +1 -0
- package/dist/meshSection.js +154 -0
- package/dist/meshSection.js.map +1 -0
- package/dist/meshShellOverlap.d.ts +75 -0
- package/dist/meshShellOverlap.d.ts.map +1 -0
- package/dist/meshShellOverlap.js +797 -0
- package/dist/meshShellOverlap.js.map +1 -0
- package/dist/meshShellSolids.d.ts +44 -0
- package/dist/meshShellSolids.d.ts.map +1 -0
- package/dist/meshShellSolids.js +116 -0
- package/dist/meshShellSolids.js.map +1 -0
- package/dist/nodeSvgDomParser.d.ts +3 -0
- package/dist/nodeSvgDomParser.d.ts.map +1 -0
- package/dist/nodeSvgDomParser.js +53 -0
- package/dist/nodeSvgDomParser.js.map +1 -0
- package/dist/parametricRecompute.d.ts.map +1 -1
- package/dist/parametricRecompute.js +56 -6
- package/dist/parametricRecompute.js.map +1 -1
- package/dist/placementWorkplane.d.ts.map +1 -1
- package/dist/placementWorkplane.js.map +1 -1
- package/dist/projectAssets.d.ts +1 -1
- package/dist/projectStore.d.ts +1 -1
- package/dist/projectStore.d.ts.map +1 -1
- package/dist/projectStore.js +23 -2
- package/dist/projectStore.js.map +1 -1
- package/dist/revolveRequests.d.ts +57 -1
- package/dist/revolveRequests.d.ts.map +1 -1
- package/dist/revolveRequests.js +103 -4
- package/dist/revolveRequests.js.map +1 -1
- package/dist/sceneExport.d.ts +6 -0
- package/dist/sceneExport.d.ts.map +1 -1
- package/dist/sceneExport.js +6 -2
- package/dist/sceneExport.js.map +1 -1
- package/dist/serverCadInterference.d.ts.map +1 -1
- package/dist/serverCadInterference.js +9 -2
- package/dist/serverCadInterference.js.map +1 -1
- package/dist/serverCadOperations.d.ts +73 -4
- package/dist/serverCadOperations.d.ts.map +1 -1
- package/dist/serverCadOperations.js +285 -51
- package/dist/serverCadOperations.js.map +1 -1
- package/dist/serverCadProjection.d.ts +14 -1
- package/dist/serverCadProjection.d.ts.map +1 -1
- package/dist/serverCadProjection.js +135 -13
- package/dist/serverCadProjection.js.map +1 -1
- package/dist/serverCadProperties.d.ts +14 -7
- package/dist/serverCadProperties.d.ts.map +1 -1
- package/dist/serverCadProperties.js +60 -24
- package/dist/serverCadProperties.js.map +1 -1
- package/dist/serverCadSection.d.ts +21 -1
- package/dist/serverCadSection.d.ts.map +1 -1
- package/dist/serverCadSection.js +157 -43
- package/dist/serverCadSection.js.map +1 -1
- package/dist/serverCadShell.d.ts.map +1 -1
- package/dist/serverCadShell.js +0 -3
- package/dist/serverCadShell.js.map +1 -1
- package/dist/serverShapeBooleans.d.ts +75 -0
- package/dist/serverShapeBooleans.d.ts.map +1 -1
- package/dist/serverShapeBooleans.js +251 -9
- package/dist/serverShapeBooleans.js.map +1 -1
- package/dist/serverShapeGroups.js.map +1 -1
- package/dist/shapeBounds.d.ts.map +1 -1
- package/dist/shapeCatalog.d.ts.map +1 -1
- package/dist/shapeCatalog.js +1 -0
- package/dist/shapeCatalog.js.map +1 -1
- package/dist/shapeRequests.d.ts +13 -0
- package/dist/shapeRequests.d.ts.map +1 -1
- package/dist/shapeRequests.js +14 -0
- package/dist/shapeRequests.js.map +1 -1
- package/dist/sketchCadProfile.d.ts +13 -0
- package/dist/sketchCadProfile.d.ts.map +1 -1
- package/dist/sketchCadProfile.js +108 -0
- package/dist/sketchCadProfile.js.map +1 -1
- package/dist/sketchCadSweep.js.map +1 -1
- package/dist/sketchEdgeSegmentLineage.d.ts +32 -0
- package/dist/sketchEdgeSegmentLineage.d.ts.map +1 -0
- package/dist/sketchEdgeSegmentLineage.js +49 -0
- package/dist/sketchEdgeSegmentLineage.js.map +1 -0
- package/dist/sketchEdgeSegmentLineageKernel.d.ts +83 -0
- package/dist/sketchEdgeSegmentLineageKernel.d.ts.map +1 -0
- package/dist/sketchEdgeSegmentLineageKernel.js +125 -0
- package/dist/sketchEdgeSegmentLineageKernel.js.map +1 -0
- package/dist/sketchFaceDraftKernel.d.ts +11 -4
- package/dist/sketchFaceDraftKernel.d.ts.map +1 -1
- package/dist/sketchFaceDraftKernel.js +8 -2
- package/dist/sketchFaceDraftKernel.js.map +1 -1
- package/dist/sketchRevolve.d.ts +13 -0
- package/dist/sketchRevolve.d.ts.map +1 -1
- package/dist/sketchRevolve.js +4 -1
- package/dist/sketchRevolve.js.map +1 -1
- package/dist/skfProject.d.ts +1 -1
- package/dist/skfProject.d.ts.map +1 -1
- package/dist/skfProject.js +63 -4
- package/dist/skfProject.js.map +1 -1
- package/dist/sphereTessellation.d.ts.map +1 -1
- package/dist/stepAssemblyExplode.d.ts.map +1 -1
- package/dist/stepAssemblyExplode.js +1 -1
- package/dist/stepAssemblyExplode.js.map +1 -1
- package/dist/stepAssemblyHierarchy.js.map +1 -1
- package/dist/stepComponentMetadata.d.ts +1 -1
- package/dist/stepComponentMetadata.d.ts.map +1 -1
- package/dist/stepComponentMetadata.js +93 -39
- package/dist/stepComponentMetadata.js.map +1 -1
- package/dist/stepExport.d.ts +14 -1
- package/dist/stepExport.d.ts.map +1 -1
- package/dist/stepExport.js +81 -32
- package/dist/stepExport.js.map +1 -1
- package/dist/stepImportCommon.d.ts +4 -1
- package/dist/stepImportCommon.d.ts.map +1 -1
- package/dist/stepImportCommon.js +7 -6
- package/dist/stepImportCommon.js.map +1 -1
- package/dist/stlImport.js.map +1 -1
- package/dist/support/cadModifierTypes.d.ts +41 -1
- package/dist/support/cadModifierTypes.d.ts.map +1 -1
- package/dist/support/i18n/LocalizedError.d.ts.map +1 -1
- package/dist/support/i18n/locales/en/messages.json +30 -2
- package/dist/support/i18n/locales.d.ts.map +1 -1
- package/dist/support/lib/appTheme.d.ts.map +1 -1
- package/dist/support/lib/materialPresets.js.map +1 -1
- package/dist/support/lib/measurementUnits.d.ts.map +1 -1
- package/dist/support/lib/operator-configuration.json +13 -1
- package/dist/support/lib/operatorConfiguration.d.ts +5 -5
- package/dist/support/lib/workplaneGrid.js.map +1 -1
- package/dist/svgImport.d.ts +2 -0
- package/dist/svgImport.d.ts.map +1 -1
- package/dist/svgImport.js +7 -0
- package/dist/svgImport.js.map +1 -1
- package/dist/threadCatalog.js.map +1 -1
- package/dist/workplaneSettings.js.map +1 -1
- package/dist/workplaneShapes.d.ts +2 -2
- package/dist/workplaneShapes.d.ts.map +1 -1
- package/dist/workplaneShapes.js.map +1 -1
- package/package.json +7 -6
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* talks to `self.postMessage` and is close to upstream's, and prising it apart
|
|
13
13
|
* would conflict with every upstream change to it. What is shared instead is
|
|
14
14
|
* the result format, so a shape filleted here is indistinguishable in the
|
|
15
|
-
* editor from one filleted there
|
|
15
|
+
* editor from one filleted there -- and, through `meshShellSolids`, how a mesh
|
|
16
|
+
* of several closed shells becomes one solid, so the two agree on which meshes
|
|
17
|
+
* with internal voids are solids (#527).
|
|
16
18
|
*/
|
|
17
19
|
import type { EvolutionData, OcctKernel, ShapeHandle } from "occt-wasm";
|
|
18
20
|
import type { SketchProfile, WorkplaneShape } from "./domain/sketchforge.js";
|
|
@@ -48,6 +50,28 @@ export declare class ServerCadError extends Error {
|
|
|
48
50
|
constructor(message: string, status?: number);
|
|
49
51
|
}
|
|
50
52
|
export type { SketchFaceDraftRequest } from "./sketchFaceDraftKernel.js";
|
|
53
|
+
/**
|
|
54
|
+
* How far a retained sketch output has been resized since it was swept: its
|
|
55
|
+
* drawn dimensions over the ones its source geometry was built at.
|
|
56
|
+
*/
|
|
57
|
+
export declare function sketchResizeFactors(shape: WorkplaneShape): {
|
|
58
|
+
width: number;
|
|
59
|
+
depth: number;
|
|
60
|
+
height: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A sketch extrusion whose exact solid is still its bare sweep -- no draft,
|
|
64
|
+
* treatment or shell -- has its B-rep stored at the size it was drawn. A resize
|
|
65
|
+
* since then lives only in its dimensions, so its exact solid at the size it is
|
|
66
|
+
* shown is its profile scaled by that resize and extruded to its height, the
|
|
67
|
+
* rebuild `applySketchFaceDraft` makes (#452 for width and depth; #546 fixed
|
|
68
|
+
* the engine handing a height-resized extrusion to it at twice its height).
|
|
69
|
+
* Null when there is no resize to apply, or the solid is something else.
|
|
70
|
+
*
|
|
71
|
+
* Without this, a fillet, chamfer or shell of a resized extrusion was built
|
|
72
|
+
* from the B-rep as drawn and stored back at the original size (#539).
|
|
73
|
+
*/
|
|
74
|
+
export declare function resizedSketchExtrusionProfile(shape: WorkplaneShape): SketchProfile | null;
|
|
51
75
|
/**
|
|
52
76
|
* Whether the kernel considers a shape sound.
|
|
53
77
|
*
|
|
@@ -58,8 +82,17 @@ export type { SketchFaceDraftRequest } from "./sketchFaceDraftKernel.js";
|
|
|
58
82
|
* own check, so the two paths agree about what a usable result is.
|
|
59
83
|
*/
|
|
60
84
|
export declare function cadResultIsValid(cad: OcctKernel, shape: ShapeHandle): boolean;
|
|
61
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* A solid sewn from a flat list of triangle corners, which is how a stored mesh arrives.
|
|
87
|
+
*
|
|
88
|
+
* A mesh made of several closed shells -- a part with a sealed internal void,
|
|
89
|
+
* or separate bodies -- is built one shell at a time and put back together by
|
|
90
|
+
* nesting (#501). Sewn all at once, OCCT got a shell inside a shell, could make
|
|
91
|
+
* no one solid of it, and refused a watertight part outright.
|
|
92
|
+
*/
|
|
62
93
|
export declare function solidFromMeshPositions(cad: OcctKernel, positions: number[]): ShapeHandle;
|
|
94
|
+
/** The exact path's refusal for a mesh error the kernel-free measures throw; anything else unchanged. */
|
|
95
|
+
export declare function asServerCadError(error: unknown): unknown;
|
|
63
96
|
/**
|
|
64
97
|
* Whether a treated result has the shape's rotation baked into its geometry.
|
|
65
98
|
*
|
|
@@ -97,7 +130,9 @@ export declare function localSolidForShape(cad: OcctKernel, shape: WorkplaneShap
|
|
|
97
130
|
* reads the same predicate.
|
|
98
131
|
*
|
|
99
132
|
* For stored geometry this is the local solid with only its mirror applied:
|
|
100
|
-
* the rotation stays on the shape, which is where it already was.
|
|
133
|
+
* the rotation stays on the shape, which is where it already was. A resized
|
|
134
|
+
* bare sketch extrusion is the exception: it is rebuilt at its drawn size
|
|
135
|
+
* (`resizedSketchExtrusionProfile`), since its stored B-rep is not.
|
|
101
136
|
*
|
|
102
137
|
* Mirrored either way, because the result is stored with the shape's mirror
|
|
103
138
|
* flags cleared. Building it unmirrored handed back the unmirrored shape and
|
|
@@ -137,6 +172,40 @@ export declare function applyBakedMirror(shape: WorkplaneShape, axis: "x" | "y"
|
|
|
137
172
|
export declare function applyEdgeTreatment(shape: WorkplaneShape, treatment: ServerCadTreatment): Promise<ServerCadResult>;
|
|
138
173
|
/** Count currently chamferable/filletable edges without preserving topology indexes. */
|
|
139
174
|
export declare function allEdgeTreatmentEdgeCount(shape: WorkplaneShape, _kind: ServerCadTreatment["kind"]): Promise<number>;
|
|
175
|
+
/**
|
|
176
|
+
* Whether `placedSolidForShape` builds this shape as the exact solid of its
|
|
177
|
+
* revolve rather than sewing its stored mesh, which is what measuring it
|
|
178
|
+
* reports as its precision.
|
|
179
|
+
*/
|
|
180
|
+
export declare function hasExactRevolveSolid(shape: WorkplaneShape): boolean;
|
|
181
|
+
/**
|
|
182
|
+
* The angle a mesh of a result built from these shapes may turn through
|
|
183
|
+
* between facets, so that it is no coarser than the finest exact revolve
|
|
184
|
+
* among them, or undefined when none is one.
|
|
185
|
+
*
|
|
186
|
+
* Before a revolve was exact, a cut through it was made from its faceted
|
|
187
|
+
* sweep, so the stored result was exactly as fine as its `sides` asked. The
|
|
188
|
+
* exact result is meshed at a fixed chord height instead, which on a small
|
|
189
|
+
* ring is coarser than the sweep it replaced and ignores `sides` altogether.
|
|
190
|
+
* A group record is searched through, since it is cut as its members.
|
|
191
|
+
*
|
|
192
|
+
* Twice the sweep's facet angle, because OCCT's angular deflection bounds the
|
|
193
|
+
* turn between a facet's normal and the surface's, half a facet each way: at
|
|
194
|
+
* this value a full circle gets `sides` facets, the sweep's own count, and a
|
|
195
|
+
* 64-sided ring cut in half meshes to the 260 triangles its sweep gave.
|
|
196
|
+
*/
|
|
197
|
+
export declare function exactRevolveAngularDeflection(shapes: readonly WorkplaneShape[]): number | undefined;
|
|
198
|
+
/**
|
|
199
|
+
* The exact solid of a plain revolve, full turn or partial sweep, in the frame
|
|
200
|
+
* its stored mesh is drawn in -- centred on the mesh's box in X and Z, base at
|
|
201
|
+
* Y=0, at its drawn size -- or null when it has none (#523).
|
|
202
|
+
*
|
|
203
|
+
* That frame is what lets the caller place it exactly as the mesh is placed.
|
|
204
|
+
* The axis comes from the stored record rather than from the solid's own box,
|
|
205
|
+
* because a faceted partial sweep's box is not the exact sweep's box, and
|
|
206
|
+
* centring on the wrong one moves the solid off its axis.
|
|
207
|
+
*/
|
|
208
|
+
export declare function exactRevolveLocalSolid(cad: OcctKernel, shape: WorkplaneShape): ShapeHandle | null;
|
|
140
209
|
/**
|
|
141
210
|
* The solid `GET /edges` numbers and a fillet or chamfer rounds.
|
|
142
211
|
*
|
|
@@ -174,7 +243,7 @@ export declare function applySketchFaceDraft(shape: WorkplaneShape, request: Ske
|
|
|
174
243
|
export declare function meshedCadResult(cad: OcctKernel, result: ShapeHandle, sourceShape?: WorkplaneShape, lineage?: {
|
|
175
244
|
sourceFaceHashes: number[];
|
|
176
245
|
evolution: EvolutionData;
|
|
177
|
-
}, sourceSolid?: ShapeHandle): ServerCadResult;
|
|
246
|
+
}, sourceSolid?: ShapeHandle, angularDeflection?: number): ServerCadResult;
|
|
178
247
|
/**
|
|
179
248
|
* The shape as it stands after a treatment.
|
|
180
249
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverCadOperations.d.ts","sourceRoot":"","sources":["../src/serverCadOperations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serverCadOperations.d.ts","sourceRoot":"","sources":["../src/serverCadOperations.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,17 +12,22 @@
|
|
|
12
12
|
* talks to `self.postMessage` and is close to upstream's, and prising it apart
|
|
13
13
|
* would conflict with every upstream change to it. What is shared instead is
|
|
14
14
|
* the result format, so a shape filleted here is indistinguishable in the
|
|
15
|
-
* editor from one filleted there
|
|
15
|
+
* editor from one filleted there -- and, through `meshShellSolids`, how a mesh
|
|
16
|
+
* of several closed shells becomes one solid, so the two agree on which meshes
|
|
17
|
+
* with internal voids are solids (#527).
|
|
16
18
|
*/
|
|
17
19
|
import { faceIdsFromEvolution } from "./faceLineage.js";
|
|
18
20
|
import { withCadKernel } from "./serverCadKernel.js";
|
|
19
21
|
import { exactSolidForShape, mirroredInShapeFrame, shapeIsTurned, unrotatedSolidForShape, UnbuildableShapeError, EXACT_SOLID_KINDS, } from "./cadSolids.js";
|
|
20
|
-
import { canonicalizeShape } from "./workplaneShapes.js";
|
|
22
|
+
import { canonicalizeShape, shapeHasTaper } from "./workplaneShapes.js";
|
|
21
23
|
import { createLocalId } from "./localIds.js";
|
|
22
|
-
import { cadSketchRegions } from "./sketchCadProfile.js";
|
|
24
|
+
import { cadSketchRegions, draftPrismLoop } from "./sketchCadProfile.js";
|
|
23
25
|
import { sketchExtrusionSolid } from "./sketchExtrusionSolid.js";
|
|
24
26
|
import { normalizeSketchRevolveSettings, sketchProfileToRevolvePolygons } from "./sketchRevolve.js";
|
|
25
27
|
import { triangleFaceGroups } from "./cadFaceMesh.js";
|
|
28
|
+
import { meshDataFromPositions } from "./meshMassProperties.js";
|
|
29
|
+
import { MeshShellsOverlapError } from "./meshShellOverlap.js";
|
|
30
|
+
import { MeshShellSolidError, nestedMeshShellSolid } from "./meshShellSolids.js";
|
|
26
31
|
import { draftSketchFace, draftPrismProfileOffset, faceIdsForCadResult } from "./sketchFaceDraftKernel.js";
|
|
27
32
|
export class ServerCadError extends Error {
|
|
28
33
|
status;
|
|
@@ -35,9 +40,16 @@ export class ServerCadError extends Error {
|
|
|
35
40
|
const MESH_DEFLECTION = 0.1;
|
|
36
41
|
const MIN_CAD_DIMENSION = 0.01;
|
|
37
42
|
const FACE_HASH_UPPER_BOUND = 2_147_483_647;
|
|
38
|
-
|
|
43
|
+
/**
|
|
44
|
+
* How far a retained sketch output has been resized since it was swept: its
|
|
45
|
+
* drawn dimensions over the ones its source geometry was built at.
|
|
46
|
+
*/
|
|
47
|
+
export function sketchResizeFactors(shape) {
|
|
39
48
|
const mesh = shape.importedMesh;
|
|
40
|
-
|
|
49
|
+
// A face-drafted output's bounds include its taper, so the resize recorded
|
|
50
|
+
// with the draft is the one to use. Once the draft is gone the bounds are
|
|
51
|
+
// the extrusion's again, and a recorded scale would hide any resize since.
|
|
52
|
+
if (shape.faceDraft && shape.faceDraftSourceScale)
|
|
41
53
|
return shape.faceDraftSourceScale;
|
|
42
54
|
const factor = (drawn, base) => base && base > 0 ? drawn / base : 1;
|
|
43
55
|
return {
|
|
@@ -60,6 +72,39 @@ function scaledSketchProfile(profile, width, depth) {
|
|
|
60
72
|
});
|
|
61
73
|
return { ...profile, points: profile.points.map(scalePoint) };
|
|
62
74
|
}
|
|
75
|
+
const isResized = (factors) => [factors.width, factors.depth, factors.height].some((factor) => Math.abs(factor - 1) > 1e-8);
|
|
76
|
+
/**
|
|
77
|
+
* A sketch extrusion whose exact solid is still its bare sweep -- no draft,
|
|
78
|
+
* treatment or shell -- has its B-rep stored at the size it was drawn. A resize
|
|
79
|
+
* since then lives only in its dimensions, so its exact solid at the size it is
|
|
80
|
+
* shown is its profile scaled by that resize and extruded to its height, the
|
|
81
|
+
* rebuild `applySketchFaceDraft` makes (#452 for width and depth; #546 fixed
|
|
82
|
+
* the engine handing a height-resized extrusion to it at twice its height).
|
|
83
|
+
* Null when there is no resize to apply, or the solid is something else.
|
|
84
|
+
*
|
|
85
|
+
* Without this, a fillet, chamfer or shell of a resized extrusion was built
|
|
86
|
+
* from the B-rep as drawn and stored back at the original size (#539).
|
|
87
|
+
*/
|
|
88
|
+
export function resizedSketchExtrusionProfile(shape) {
|
|
89
|
+
if (shape.sketchOperation !== "extrude" || !shape.sketchProfile || !shape.cadBrep)
|
|
90
|
+
return null;
|
|
91
|
+
if (shape.faceDraft !== undefined || shape.draftPrismAngle !== undefined || shape.shell
|
|
92
|
+
|| (shape.edgeTreatments?.length ?? 0) > 0 || (shape.edgeTreatmentHistory?.length ?? 0) > 0)
|
|
93
|
+
return null;
|
|
94
|
+
const resize = sketchResizeFactors(shape);
|
|
95
|
+
return isResized(resize) ? scaledSketchProfile(shape.sketchProfile, resize.width, resize.depth) : null;
|
|
96
|
+
}
|
|
97
|
+
/** The bare extrusion's exact solid at its drawn size, or its stored solid when it has not been resized. */
|
|
98
|
+
function storedOrResizedSolid(cad, shape) {
|
|
99
|
+
const resizedProfile = resizedSketchExtrusionProfile(shape);
|
|
100
|
+
if (!resizedProfile)
|
|
101
|
+
return localSolidForShape(cad, shape);
|
|
102
|
+
const solid = sketchExtrusionSolid(cad, resizedProfile, shape.height);
|
|
103
|
+
if (!cadResultIsValid(cad, solid)) {
|
|
104
|
+
throw new ServerCadError("The resized sketch extrusion could not be rebuilt as exact CAD geometry.");
|
|
105
|
+
}
|
|
106
|
+
return solid;
|
|
107
|
+
}
|
|
63
108
|
function meshPositionsOf(shape) {
|
|
64
109
|
const positions = shape.importedMesh?.positions;
|
|
65
110
|
return positions && positions.length >= 9 ? positions : null;
|
|
@@ -117,8 +162,39 @@ function solidFromBrep(cad, brep) {
|
|
|
117
162
|
}
|
|
118
163
|
throw new ServerCadError("The stored exact geometry for that shape could not be read.");
|
|
119
164
|
}
|
|
120
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* A solid sewn from a flat list of triangle corners, which is how a stored mesh arrives.
|
|
167
|
+
*
|
|
168
|
+
* A mesh made of several closed shells -- a part with a sealed internal void,
|
|
169
|
+
* or separate bodies -- is built one shell at a time and put back together by
|
|
170
|
+
* nesting (#501). Sewn all at once, OCCT got a shell inside a shell, could make
|
|
171
|
+
* no one solid of it, and refused a watertight part outright.
|
|
172
|
+
*/
|
|
121
173
|
export function solidFromMeshPositions(cad, positions) {
|
|
174
|
+
let nested;
|
|
175
|
+
try {
|
|
176
|
+
nested = nestedMeshShellSolid(cad, meshDataFromPositions(positions), (shell) => sewnMeshSolid(cad, shell));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!(error instanceof MeshShellSolidError))
|
|
180
|
+
throw error;
|
|
181
|
+
throw new ServerCadError(error.reason === "shells-overlap" ? MESH_SHELLS_OVERLAP : MESH_NOT_SOLID);
|
|
182
|
+
}
|
|
183
|
+
return nested ?? sewnMeshSolid(cad, positions);
|
|
184
|
+
}
|
|
185
|
+
/** The exact path's refusal for a mesh error the kernel-free measures throw; anything else unchanged. */
|
|
186
|
+
export function asServerCadError(error) {
|
|
187
|
+
return error instanceof MeshShellsOverlapError ? new ServerCadError(MESH_SHELLS_OVERLAP) : error;
|
|
188
|
+
}
|
|
189
|
+
const MESH_SHELLS_OVERLAP = "That shape's mesh has closed bodies that overlap without one enclosing the other, so the CAD kernel cannot tell which side of each is material. Union the bodies in the mesh before importing it.";
|
|
190
|
+
/**
|
|
191
|
+
* Why a mesh could not become a solid. Shared by every exact path that reads a
|
|
192
|
+
* mesh -- properties, booleans, interference, drawings -- so it names none of
|
|
193
|
+
* them; it used to say "cannot be filleted" to all of them.
|
|
194
|
+
*/
|
|
195
|
+
const MESH_NOT_SOLID = "That shape's mesh is not a closed solid, so the CAD kernel cannot build exact geometry from it.";
|
|
196
|
+
/** One shell's triangles sewn into a solid, loosening the tolerance until it closes. */
|
|
197
|
+
function sewnMeshSolid(cad, positions) {
|
|
122
198
|
const imported = cad.importStl(asciiStlFromPositions(positions));
|
|
123
199
|
let shape = cad.fixShape(imported);
|
|
124
200
|
if (isValidSolid(cad, shape))
|
|
@@ -139,7 +215,7 @@ export function solidFromMeshPositions(cad, positions) {
|
|
|
139
215
|
// Try the next tolerance.
|
|
140
216
|
}
|
|
141
217
|
}
|
|
142
|
-
throw new ServerCadError(
|
|
218
|
+
throw new ServerCadError(MESH_NOT_SOLID);
|
|
143
219
|
}
|
|
144
220
|
/**
|
|
145
221
|
* Whether the shape's geometry has to be turned, or arrives already turned.
|
|
@@ -209,7 +285,9 @@ export function localSolidForShape(cad, shape) {
|
|
|
209
285
|
* reads the same predicate.
|
|
210
286
|
*
|
|
211
287
|
* For stored geometry this is the local solid with only its mirror applied:
|
|
212
|
-
* the rotation stays on the shape, which is where it already was.
|
|
288
|
+
* the rotation stays on the shape, which is where it already was. A resized
|
|
289
|
+
* bare sketch extrusion is the exception: it is rebuilt at its drawn size
|
|
290
|
+
* (`resizedSketchExtrusionProfile`), since its stored B-rep is not.
|
|
213
291
|
*
|
|
214
292
|
* Mirrored either way, because the result is stored with the shape's mirror
|
|
215
293
|
* flags cleared. Building it unmirrored handed back the unmirrored shape and
|
|
@@ -217,7 +295,7 @@ export function localSolidForShape(cad, shape) {
|
|
|
217
295
|
*/
|
|
218
296
|
export function orientedSolidForShape(cad, shape) {
|
|
219
297
|
if (!rotationIsRebuilt(shape))
|
|
220
|
-
return mirroredStoredSolid(cad,
|
|
298
|
+
return mirroredStoredSolid(cad, storedOrResizedSolid(cad, shape), shape);
|
|
221
299
|
try {
|
|
222
300
|
return exactSolidForShape(cad, shape);
|
|
223
301
|
}
|
|
@@ -417,11 +495,15 @@ export function applyEdgeTreatment(shape, treatment) {
|
|
|
417
495
|
}
|
|
418
496
|
}
|
|
419
497
|
catch (error) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
498
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
499
|
+
const wasmFault = typeof WebAssembly !== "undefined"
|
|
500
|
+
&& (error instanceof WebAssembly.Exception || error instanceof WebAssembly.RuntimeError);
|
|
501
|
+
if (wasmFault || /out of memory|memory access out of bounds|allocation failed|wasm.*abort/i.test(message)) {
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
// Keep geometric sizing failures actionable without disguising runtime
|
|
505
|
+
// faults as a user mistake.
|
|
506
|
+
throw new ServerCadError(`The requested ${treatment.kind} amount ${treatment.amount} could not be applied to ${edges.length} eligible edges. Try a smaller amount.`);
|
|
425
507
|
}
|
|
426
508
|
// Both fillet and chamfer hand back a compound wrapping the result, and
|
|
427
509
|
// OCCT refuses a compound as the input to another treatment -- so storing
|
|
@@ -524,6 +606,145 @@ function exactRevolveSolidForShape(cad, shape) {
|
|
|
524
606
|
const { radialX, axial } = revolveResizeFactors(shape);
|
|
525
607
|
return exactPlainSketchRevolveSolid(cad, shape.sketchProfile, normalizeSketchRevolveSettings(shape.sketchRevolve), undefined, { radial: radialX, axial });
|
|
526
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Where a stored revolve's axis sits in its mesh's natural frame, or null when
|
|
611
|
+
* that cannot be known without sweeping it again.
|
|
612
|
+
*
|
|
613
|
+
* The mesh is centred on its own box, so the axis is `-revolveAxisOffset`
|
|
614
|
+
* there (#503). A full turn saved before that offset was recorded is still
|
|
615
|
+
* symmetric about its axis under every one of its facet rotations, so the
|
|
616
|
+
* axis is the centroid of its triangles in X and Z -- which is not the box
|
|
617
|
+
* centre when the facet count is not a multiple of four. A partial sweep with
|
|
618
|
+
* no recorded axis has neither, and stays tessellated.
|
|
619
|
+
*/
|
|
620
|
+
function storedRevolveAxis(shape, fullTurn) {
|
|
621
|
+
if (shape.revolveAxisOffset)
|
|
622
|
+
return { x: -shape.revolveAxisOffset.x, z: -shape.revolveAxisOffset.z };
|
|
623
|
+
if (!fullTurn)
|
|
624
|
+
return null;
|
|
625
|
+
const positions = shape.importedMesh?.positions ?? [];
|
|
626
|
+
let volume = 0;
|
|
627
|
+
let x = 0;
|
|
628
|
+
let z = 0;
|
|
629
|
+
for (let index = 0; index + 8 < positions.length; index += 9) {
|
|
630
|
+
const [ax, ay, az, bx, by, bz, cx, cy, cz] = positions.slice(index, index + 9);
|
|
631
|
+
const tetra = (ax * (by * cz - bz * cy) - ay * (bx * cz - bz * cx) + az * (bx * cy - by * cx)) / 6;
|
|
632
|
+
volume += tetra;
|
|
633
|
+
x += tetra * (ax + bx + cx) / 4;
|
|
634
|
+
z += tetra * (az + bz + cz) / 4;
|
|
635
|
+
}
|
|
636
|
+
return Math.abs(volume) > 1e-12 ? { x: x / volume, z: z / volume } : null;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Whether any profile segment is drawn as a curve.
|
|
640
|
+
*
|
|
641
|
+
* The rule `sketchProfileToRevolvePolygons` samples by: a segment that is not
|
|
642
|
+
* a line and has both its handles (the start point's outgoing one and the end
|
|
643
|
+
* point's incoming one) is a cubic, sampled into straight pieces; anything
|
|
644
|
+
* else is drawn straight. A revolve of the samples would be exact about the
|
|
645
|
+
* axis but still an approximation of the drawn curve, so it is not reported
|
|
646
|
+
* as exact.
|
|
647
|
+
*/
|
|
648
|
+
function profileHasCurvedSegment(profile) {
|
|
649
|
+
const points = new Map(profile.points.map((point) => [point.id, point]));
|
|
650
|
+
return profile.segments.some((segment) => segment.kind !== "line"
|
|
651
|
+
&& Boolean(points.get(segment.startId)?.handleOut) && Boolean(points.get(segment.endId)?.handleIn));
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Why a revolve stored as a mesh is not measured, cut or exported as its exact
|
|
655
|
+
* solid, or null when it is.
|
|
656
|
+
*
|
|
657
|
+
* Wider than the edge listing's rule (`revolveWithoutExactEdges`): a partial
|
|
658
|
+
* sweep is exact here too, because nothing numbers its edges.
|
|
659
|
+
*/
|
|
660
|
+
function revolveWithoutExactSolid(shape) {
|
|
661
|
+
if (!isUntreatedSketchRevolve(shape))
|
|
662
|
+
return "not an untreated sketch revolve";
|
|
663
|
+
if ((shape.importedMesh?.positions.length ?? 0) < 9)
|
|
664
|
+
return "no stored sweep to place the solid by";
|
|
665
|
+
const settings = normalizeSketchRevolveSettings(shape.sketchRevolve);
|
|
666
|
+
if (settings.pitch !== 0)
|
|
667
|
+
return "a helical revolve has only its tessellated sweep";
|
|
668
|
+
if (settings.coreRadius > 0)
|
|
669
|
+
return "a revolve fused to a core has no exact solid";
|
|
670
|
+
const { radialX, radialZ } = revolveResizeFactors(shape);
|
|
671
|
+
if (Math.abs(radialX - radialZ) > 1e-6 * Math.max(radialX, radialZ))
|
|
672
|
+
return "resized unevenly across its axis, so no revolve describes it";
|
|
673
|
+
if (shapeHasTaper(shape))
|
|
674
|
+
return "a taper is applied to the mesh, not to a revolve";
|
|
675
|
+
const fullTurn = Math.abs(Math.abs(settings.sweepAngle) - 360) <= 1e-6;
|
|
676
|
+
if (!storedRevolveAxis(shape, fullTurn))
|
|
677
|
+
return "a partial sweep saved without its axis";
|
|
678
|
+
if (profileHasCurvedSegment(shape.sketchProfile))
|
|
679
|
+
return "a curved profile edge is sampled into straight segments, so no exact solid describes it";
|
|
680
|
+
try {
|
|
681
|
+
if (cadSketchRegions(revolveSectionProfile(shape.sketchProfile, settings, { radial: 1, axial: 1 })).length !== 1) {
|
|
682
|
+
return "the profile has several closed regions";
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
return "the profile has no closed region";
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Whether `placedSolidForShape` builds this shape as the exact solid of its
|
|
692
|
+
* revolve rather than sewing its stored mesh, which is what measuring it
|
|
693
|
+
* reports as its precision.
|
|
694
|
+
*/
|
|
695
|
+
export function hasExactRevolveSolid(shape) {
|
|
696
|
+
return revolveWithoutExactSolid(shape) === null;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* The angle a mesh of a result built from these shapes may turn through
|
|
700
|
+
* between facets, so that it is no coarser than the finest exact revolve
|
|
701
|
+
* among them, or undefined when none is one.
|
|
702
|
+
*
|
|
703
|
+
* Before a revolve was exact, a cut through it was made from its faceted
|
|
704
|
+
* sweep, so the stored result was exactly as fine as its `sides` asked. The
|
|
705
|
+
* exact result is meshed at a fixed chord height instead, which on a small
|
|
706
|
+
* ring is coarser than the sweep it replaced and ignores `sides` altogether.
|
|
707
|
+
* A group record is searched through, since it is cut as its members.
|
|
708
|
+
*
|
|
709
|
+
* Twice the sweep's facet angle, because OCCT's angular deflection bounds the
|
|
710
|
+
* turn between a facet's normal and the surface's, half a facet each way: at
|
|
711
|
+
* this value a full circle gets `sides` facets, the sweep's own count, and a
|
|
712
|
+
* 64-sided ring cut in half meshes to the 260 triangles its sweep gave.
|
|
713
|
+
*/
|
|
714
|
+
export function exactRevolveAngularDeflection(shapes) {
|
|
715
|
+
let finest = 0;
|
|
716
|
+
const visit = (shape) => {
|
|
717
|
+
if (hasExactRevolveSolid(shape)) {
|
|
718
|
+
finest = Math.max(finest, normalizeSketchRevolveSettings(shape.sketchRevolve).sides);
|
|
719
|
+
}
|
|
720
|
+
else if (!shape.importedMesh && !shape.cadBrep) {
|
|
721
|
+
shape.groupedShapes?.forEach(visit);
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
shapes.forEach(visit);
|
|
725
|
+
return finest > 0 ? (4 * Math.PI) / finest : undefined;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* The exact solid of a plain revolve, full turn or partial sweep, in the frame
|
|
729
|
+
* its stored mesh is drawn in -- centred on the mesh's box in X and Z, base at
|
|
730
|
+
* Y=0, at its drawn size -- or null when it has none (#523).
|
|
731
|
+
*
|
|
732
|
+
* That frame is what lets the caller place it exactly as the mesh is placed.
|
|
733
|
+
* The axis comes from the stored record rather than from the solid's own box,
|
|
734
|
+
* because a faceted partial sweep's box is not the exact sweep's box, and
|
|
735
|
+
* centring on the wrong one moves the solid off its axis.
|
|
736
|
+
*/
|
|
737
|
+
export function exactRevolveLocalSolid(cad, shape) {
|
|
738
|
+
if (!hasExactRevolveSolid(shape))
|
|
739
|
+
return null;
|
|
740
|
+
const settings = normalizeSketchRevolveSettings(shape.sketchRevolve);
|
|
741
|
+
const axis = storedRevolveAxis(shape, Math.abs(Math.abs(settings.sweepAngle) - 360) <= 1e-6);
|
|
742
|
+
const { radialX, radialZ, axial } = revolveResizeFactors(shape);
|
|
743
|
+
const solid = revolvedSectionSolid(cad, shape.sketchProfile, settings, { radial: radialX, axial });
|
|
744
|
+
if (!cad.isValid(solid) || !cad.isSolid(solid))
|
|
745
|
+
throw new ServerCadError("The retained sketch revolve could not be rebuilt as exact CAD geometry.");
|
|
746
|
+
return cad.translate(solid, axis.x * radialX, 0, axis.z * radialZ);
|
|
747
|
+
}
|
|
527
748
|
/**
|
|
528
749
|
* The solid `GET /edges` numbers and a fillet or chamfer rounds.
|
|
529
750
|
*
|
|
@@ -547,16 +768,16 @@ export function isPlainFullTurnSketchRevolve(shape) {
|
|
|
547
768
|
&& (settings.pitch ?? 0) === 0 && (settings.coreRadius ?? 0) === 0
|
|
548
769
|
&& Math.abs(Math.abs(settings.sweepAngle) - 360) <= 1e-6;
|
|
549
770
|
}
|
|
550
|
-
/**
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
771
|
+
/**
|
|
772
|
+
* A revolve's section as a sketch profile in the OCCT build frame, at the given
|
|
773
|
+
* section scale: x is the distance from the axis and z runs up it from the
|
|
774
|
+
* revolve's base, the same polygons the mesh sweep turns.
|
|
775
|
+
*/
|
|
776
|
+
function revolveSectionProfile(sketchProfile, sketchRevolve, sectionScale) {
|
|
556
777
|
const polygons = sketchProfileToRevolvePolygons(sketchProfile, sketchRevolve);
|
|
557
778
|
if (polygons.length === 0)
|
|
558
779
|
throw new ServerCadError("The retained revolve profile has no closed region.");
|
|
559
|
-
|
|
780
|
+
return {
|
|
560
781
|
points: polygons.flatMap((polygon, polygonIndex) => polygon.map(([x, z], pointIndex) => ({
|
|
561
782
|
id: `revolve-${polygonIndex}-point-${pointIndex}`,
|
|
562
783
|
x: x * sectionScale.radial,
|
|
@@ -569,25 +790,41 @@ export function exactPlainSketchRevolveSolid(cad, sketchProfile, sketchRevolve,
|
|
|
569
790
|
kind: "line",
|
|
570
791
|
}))),
|
|
571
792
|
};
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* The exact solid of a plain revolve's one section region, turned through its
|
|
796
|
+
* sweep about the Y axis, in the frame the mesh sweep builds before centring.
|
|
797
|
+
*
|
|
798
|
+
* Manifold sweeps from +X towards the editor's -Z and then turns the result by
|
|
799
|
+
* the start angle (or, for a negative sweep, by where the sweep ends), which
|
|
800
|
+
* is what a positive OCCT rotation about +Y does too, so the two agree for a
|
|
801
|
+
* partial sweep as well as for a full turn.
|
|
802
|
+
*/
|
|
803
|
+
function revolvedSectionSolid(cad, sketchProfile, sketchRevolve, sectionScale) {
|
|
804
|
+
const profile = revolveSectionProfile(sketchProfile, sketchRevolve, sectionScale);
|
|
572
805
|
const pathWire = (path) => cad.makeWire(path.steps.map(({ from, to }) => cad.makeLineEdge({ x: from.x, y: from.z, z: 0 }, { x: to.x, y: to.z, z: 0 })));
|
|
573
806
|
const regions = cadSketchRegions(profile);
|
|
574
807
|
if (regions.length !== 1) {
|
|
575
808
|
throw new ServerCadError("Exact revolve geometry requires one closed profile region, and this revolve's profile has several.");
|
|
576
809
|
}
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
810
|
+
const axis = { point: { x: 0, y: 0, z: 0 }, direction: { x: 0, y: 1, z: 0 } };
|
|
811
|
+
const region = regions[0];
|
|
812
|
+
let face = cad.makeFace(pathWire(region.outer));
|
|
813
|
+
if (region.holes.length)
|
|
814
|
+
face = cad.addHolesInFace(face, region.holes.map(pathWire));
|
|
815
|
+
const sweep = Math.abs(sketchRevolve.sweepAngle);
|
|
816
|
+
const solid = cad.revolve(face, axis, Math.abs(sweep - 360) <= 1e-6 ? Math.PI * 2 : (sweep * Math.PI) / 180);
|
|
817
|
+
const rotation = sketchRevolve.sweepAngle < 0 ? sketchRevolve.startAngle + sketchRevolve.sweepAngle : sketchRevolve.startAngle;
|
|
818
|
+
return Math.abs(rotation) < 1e-8 ? solid : cad.rotate(solid, axis, (rotation * Math.PI) / 180);
|
|
819
|
+
}
|
|
820
|
+
/** Build the analytical BREP for a plain, single-region full-turn revolve. */
|
|
821
|
+
export function exactPlainSketchRevolveSolid(cad, sketchProfile, sketchRevolve, targetShape, sectionScale = { radial: 1, axial: 1 }) {
|
|
822
|
+
if (!sketchRevolve || (sketchRevolve.pitch ?? 0) !== 0 || (sketchRevolve.coreRadius ?? 0) !== 0
|
|
823
|
+
|| Math.abs(Math.abs(sketchRevolve.sweepAngle) - 360) > 1e-6) {
|
|
824
|
+
throw new ServerCadError("Exact revolve geometry requires a plain full-turn sketch revolve.");
|
|
825
|
+
}
|
|
826
|
+
// A full turn starts where its start angle says, whichever way it is swept.
|
|
827
|
+
let solid = revolvedSectionSolid(cad, sketchProfile, { ...sketchRevolve, sweepAngle: 360 }, sectionScale);
|
|
591
828
|
// Callers that cannot express a resized revolve analytically (for example,
|
|
592
829
|
// exact face listing of a radially elliptical legacy shape) use this bounds
|
|
593
830
|
// fit. Face drafting of a supported resize passes profile scales instead.
|
|
@@ -639,18 +876,13 @@ export function applyDraftPrism(shape, request) {
|
|
|
639
876
|
}
|
|
640
877
|
const scale = sketchResizeFactors(shape);
|
|
641
878
|
const profile = scaledSketchProfile(shape.sketchProfile, scale.width, scale.depth);
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|| segment.startId !== points[index].id
|
|
646
|
-
|| segment.endId !== points[(index + 1) % points.length].id)
|
|
647
|
-
|| points.some((point) => point.handleIn || point.handleOut || !Number.isFinite(point.x) || !Number.isFinite(point.z))) {
|
|
648
|
-
return Promise.reject(new ServerCadError("Draft prism currently requires one closed polygonal sketch profile made from straight segments.", 400));
|
|
649
|
-
}
|
|
879
|
+
const loop = draftPrismLoop(profile);
|
|
880
|
+
if (typeof loop === "string")
|
|
881
|
+
return Promise.reject(new ServerCadError(loop, 400));
|
|
650
882
|
return withCadKernel((cad) => {
|
|
651
883
|
const { x: centerX, z: centerZ } = draftPrismProfileOffset(profile);
|
|
652
|
-
const edges =
|
|
653
|
-
const next =
|
|
884
|
+
const edges = loop.map((point, index) => {
|
|
885
|
+
const next = loop[(index + 1) % loop.length];
|
|
654
886
|
return cad.makeLineEdge({ x: point.x - centerX, y: 0, z: point.z - centerZ }, { x: next.x - centerX, y: 0, z: next.z - centerZ });
|
|
655
887
|
});
|
|
656
888
|
const face = cad.makeFace(cad.makeWire(edges));
|
|
@@ -703,8 +935,7 @@ export function applySketchFaceDraft(shape, request) {
|
|
|
703
935
|
solid = solidFromBrep(cad, shape.cadBrep);
|
|
704
936
|
}
|
|
705
937
|
const resize = sketchResizeFactors(shape);
|
|
706
|
-
if (!exactRevolve && shape.sketchOperation === "extrude"
|
|
707
|
-
&& [resize.width, resize.depth, resize.height].some((factor) => Math.abs(factor - 1) > 1e-8)) {
|
|
938
|
+
if (!exactRevolve && shape.sketchOperation === "extrude" && isResized(resize)) {
|
|
708
939
|
solid = sketchExtrusionSolid(cad, scaledSketchProfile(shape.sketchProfile, resize.width, resize.depth), shape.height);
|
|
709
940
|
if (!cadResultIsValid(cad, solid) || !cad.isSolid(solid)) {
|
|
710
941
|
throw new ServerCadError("The resized sketch extrusion could not be rebuilt as exact CAD geometry.");
|
|
@@ -712,7 +943,7 @@ export function applySketchFaceDraft(shape, request) {
|
|
|
712
943
|
}
|
|
713
944
|
const faces = cad.getSubShapes(solid, "face");
|
|
714
945
|
const ids = shape.faceIds?.length === faces.length ? shape.faceIds
|
|
715
|
-
: exactRevolve
|
|
946
|
+
: exactRevolve ? faces.map((_, index) => `face_${encodeURIComponent(shape.id)}_${index}`)
|
|
716
947
|
: undefined;
|
|
717
948
|
if (!ids || ids.length !== faces.length || new Set(ids).size !== faces.length) {
|
|
718
949
|
throw new ServerCadError("Face drafting requires a complete, unique stable face ID list.");
|
|
@@ -733,8 +964,10 @@ export function applySketchFaceDraft(shape, request) {
|
|
|
733
964
|
});
|
|
734
965
|
}
|
|
735
966
|
/** The exact geometry and a mesh of it, which is what a shape stores after any CAD operation. */
|
|
736
|
-
export function meshedCadResult(cad, result, sourceShape, lineage, sourceSolid) {
|
|
737
|
-
const mesh = cad.meshShape(result,
|
|
967
|
+
export function meshedCadResult(cad, result, sourceShape, lineage, sourceSolid, angularDeflection) {
|
|
968
|
+
const mesh = cad.meshShape(result, angularDeflection
|
|
969
|
+
? { linearDeflection: MESH_DEFLECTION, angularDeflection }
|
|
970
|
+
: { linearDeflection: MESH_DEFLECTION });
|
|
738
971
|
// The editor stores a flat triangle list, not an indexed mesh, so the
|
|
739
972
|
// indices are expanded here rather than carried.
|
|
740
973
|
const positions = [];
|
|
@@ -867,7 +1100,8 @@ export function shapeWithCadResult(shape, result) {
|
|
|
867
1100
|
// was created -- which is where a test shape sits -- subtracts zero.
|
|
868
1101
|
//
|
|
869
1102
|
// These are the result's own bounds, which is what the editor's worker
|
|
870
|
-
// records for exactly the same reason
|
|
1103
|
+
// records for exactly the same reason, and what both cut writers now
|
|
1104
|
+
// record for a cut's recentred B-rep (#548).
|
|
871
1105
|
cadBrepFrame: { x: centerX, z: centerZ, elevation: minY, width, depth, height },
|
|
872
1106
|
cadPrimitiveFrame: undefined,
|
|
873
1107
|
cadDisplayEdges: undefined,
|