@constructorfleet/sketchforge-engine 1.26.0 → 1.27.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 +94 -0
- package/dist/cadAnalysis.d.ts.map +1 -0
- package/dist/cadAnalysis.js +143 -0
- package/dist/cadAnalysis.js.map +1 -0
- package/dist/cadShell.d.ts +143 -0
- package/dist/cadShell.d.ts.map +1 -0
- package/dist/cadShell.js +306 -0
- package/dist/cadShell.js.map +1 -0
- package/dist/domain/sketchforge.d.ts +1 -1
- package/dist/domain/sketchforge.d.ts.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +57 -15
- package/dist/engine.js.map +1 -1
- package/dist/parametricRecompute.d.ts.map +1 -1
- package/dist/parametricRecompute.js +7 -4
- package/dist/parametricRecompute.js.map +1 -1
- package/dist/revolveRequests.d.ts.map +1 -1
- package/dist/serverCadInterference.d.ts +8 -45
- package/dist/serverCadInterference.d.ts.map +1 -1
- package/dist/serverCadInterference.js +13 -42
- package/dist/serverCadInterference.js.map +1 -1
- package/dist/serverCadOperations.d.ts.map +1 -1
- package/dist/serverCadOperations.js +76 -25
- package/dist/serverCadOperations.js.map +1 -1
- package/dist/serverCadShell.d.ts +7 -94
- package/dist/serverCadShell.d.ts.map +1 -1
- package/dist/serverCadShell.js +25 -269
- package/dist/serverCadShell.js.map +1 -1
- package/dist/serverCadThickness.d.ts +8 -8
- package/dist/serverCadThickness.d.ts.map +1 -1
- package/dist/serverCadThickness.js +15 -42
- package/dist/serverCadThickness.js.map +1 -1
- package/dist/serverStepExplode.d.ts +1 -2
- package/dist/serverStepExplode.d.ts.map +1 -1
- package/dist/serverStepExplode.js +15 -148
- package/dist/serverStepExplode.js.map +1 -1
- package/dist/sketchFaceDraftKernel.d.ts +5 -5
- package/dist/sketchFaceDraftKernel.d.ts.map +1 -1
- package/dist/sketchFaceDraftKernel.js +11 -9
- package/dist/sketchFaceDraftKernel.js.map +1 -1
- package/dist/stepAssemblyExplode.d.ts +45 -0
- package/dist/stepAssemblyExplode.d.ts.map +1 -0
- package/dist/stepAssemblyExplode.js +195 -0
- package/dist/stepAssemblyExplode.js.map +1 -0
- package/dist/support/cadModifierTypes.d.ts +59 -0
- package/dist/support/cadModifierTypes.d.ts.map +1 -1
- package/dist/support/i18n/locales/en/messages.json +13 -1
- package/package.json +1 -1
package/dist/serverCadShell.js
CHANGED
|
@@ -1,142 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hollowing a solid on the server, with a wall of a given thickness.
|
|
3
3
|
*
|
|
4
|
-
* This is the operation a 3D print needs most
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* This is the operation a 3D print needs most: a solid box weighs 8 grams of
|
|
5
|
+
* filament and a 2 mm-walled one weighs two, and a box with its top face left
|
|
6
|
+
* open is a tray. The hollowing itself is `cadShell.ts`, which the editor's
|
|
7
|
+
* CAD worker shares; this module is the Node side of it.
|
|
8
8
|
*
|
|
9
9
|
* Node-only, and built on the same footing as the fillet endpoint: the shape is
|
|
10
10
|
* rebuilt as true geometry rather than meshed, and the result is stored with
|
|
11
11
|
* its exact BREP alongside the triangles, so a hollowed part still exports to
|
|
12
12
|
* STEP and can still be filleted afterwards.
|
|
13
|
-
*
|
|
14
|
-
* Two things about `shell` are worth knowing before changing any of this, both
|
|
15
|
-
* found by measuring rather than by reading:
|
|
16
|
-
*
|
|
17
|
-
* - With no faces removed it does not hollow anything. It returns the *inner*
|
|
18
|
-
* solid -- the original offset inwards by the thickness -- so a sealed
|
|
19
|
-
* hollow has to be the original cut by that, which is what `hollowedSolid`
|
|
20
|
-
* does. Asking it to hollow a closed solid directly gives you back a
|
|
21
|
-
* smaller solid part, which looks plausible and is not what was asked for.
|
|
22
|
-
* - A thickness that does not fit does not reliably fail. Past half the
|
|
23
|
-
* part it variously returns an empty compound, a solid that reports
|
|
24
|
-
* `isValid: false` with a correct bounding box, or the original solid
|
|
25
|
-
* untouched with every extent and the validity flag intact. Only the volume
|
|
26
|
-
* tells those apart from a real answer, which is why `hollowedSolid`
|
|
27
|
-
* measures it rather than trusting the call to throw.
|
|
28
13
|
*/
|
|
29
14
|
import { withCadKernel } from "./serverCadKernel.js";
|
|
30
15
|
import { exactPlainSketchRevolveSolid, isPlainFullTurnSketchRevolve, meshedCadResult, orientedSolidForShape, ServerCadError } from "./serverCadOperations.js";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* `1e-6` is the kernel's own recommendation for a precise shell and matches
|
|
35
|
-
* what brepjs uses; the looser `1e-3` survives more inputs but produces
|
|
36
|
-
* different topology, which would make a shelled part disagree with the same
|
|
37
|
-
* part shelled in the editor's worker later.
|
|
38
|
-
*/
|
|
39
|
-
const SHELL_TOLERANCE = 1e-6;
|
|
40
|
-
/** How closely a face's normal must line up with an axis to be named for it. */
|
|
41
|
-
const AXIS_TOLERANCE = 1e-3;
|
|
42
|
-
/**
|
|
43
|
-
* Names for the faces a request is likely to want open, by where they point.
|
|
44
|
-
*
|
|
45
|
-
* A caller that knows the geometry can name face indexes from the listing, but
|
|
46
|
-
* nobody wants to list a box's faces to find out which one is the lid. The
|
|
47
|
-
* local frame these resolve in is the one every geometry builder here produces:
|
|
48
|
-
* centred in X and Z, base at Y=0, +Z towards the viewer.
|
|
49
|
-
*/
|
|
50
|
-
const FACE_DIRECTIONS = {
|
|
51
|
-
top: { x: 0, y: 1, z: 0 },
|
|
52
|
-
bottom: { x: 0, y: -1, z: 0 },
|
|
53
|
-
right: { x: 1, y: 0, z: 0 },
|
|
54
|
-
left: { x: -1, y: 0, z: 0 },
|
|
55
|
-
front: { x: 0, y: 0, z: 1 },
|
|
56
|
-
back: { x: 0, y: 0, z: -1 },
|
|
57
|
-
};
|
|
58
|
-
export const FACE_DIRECTION_NAMES = Object.keys(FACE_DIRECTIONS);
|
|
59
|
-
/**
|
|
60
|
-
* The normal pointing out of the solid at the middle of a face.
|
|
61
|
-
*
|
|
62
|
-
* Exported because the measurement endpoint needs the same normal to answer
|
|
63
|
-
* "what is the angle between these two faces", and a second copy of this would
|
|
64
|
-
* be free to drift from the one the face listing and the shell request agree on.
|
|
65
|
-
*/
|
|
66
|
-
export function faceOutwardNormal(cad, face) {
|
|
67
|
-
try {
|
|
68
|
-
const bounds = cad.uvBounds(face);
|
|
69
|
-
// `surfaceNormal` already accounts for the face's own orientation, so a
|
|
70
|
-
// reversed face reports the normal pointing out of the solid rather than
|
|
71
|
-
// the surface's natural one. Verified against a box's six faces and
|
|
72
|
-
// against the inner faces of a hollow, where the two disagree.
|
|
73
|
-
const normal = cad.surfaceNormal(face, (bounds.uMin + bounds.uMax) / 2, (bounds.vMin + bounds.vMax) / 2);
|
|
74
|
-
if (![normal.x, normal.y, normal.z].every(Number.isFinite))
|
|
75
|
-
return null;
|
|
76
|
-
return normal;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function axisOf(normal) {
|
|
83
|
-
if (!normal)
|
|
84
|
-
return null;
|
|
85
|
-
for (const name of FACE_DIRECTION_NAMES) {
|
|
86
|
-
const direction = FACE_DIRECTIONS[name];
|
|
87
|
-
const dot = normal.x * direction.x + normal.y * direction.y + normal.z * direction.z;
|
|
88
|
-
if (dot > 1 - AXIS_TOLERANCE)
|
|
89
|
-
return name;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
/** How far out along its own direction a face sits, which is what settles a tie. */
|
|
94
|
-
function reachAlong(centroid, direction) {
|
|
95
|
-
return centroid.x * direction.x + centroid.y * direction.y + centroid.z * direction.z;
|
|
96
|
-
}
|
|
16
|
+
import { CadShellError, describeShellFaces, shellSolid } from "./cadShell.js";
|
|
17
|
+
export { FACE_DIRECTION_NAMES, faceOutwardNormal } from "./cadShell.js";
|
|
97
18
|
function faceSummaries(cad, solid, shape, preferStoredIds = true) {
|
|
98
|
-
const
|
|
99
|
-
const storedIds = preferStoredIds && shape.faceIds?.length ===
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
const centroid = cad.getSurfaceCenterOfMass(handle);
|
|
110
|
-
return {
|
|
111
|
-
index,
|
|
112
|
-
id: storedIds?.[index] ?? `face_${encodeURIComponent(shape.id)}_${index}`,
|
|
113
|
-
surfaceType,
|
|
114
|
-
area: cad.getSurfaceArea(handle),
|
|
115
|
-
centroid: { x: centroid.x, y: centroid.y, z: centroid.z },
|
|
116
|
-
normal: normal ? { x: normal.x, y: normal.y, z: normal.z } : null,
|
|
117
|
-
// A flat face is the only kind a direction name can mean: "the top" of a
|
|
118
|
-
// sphere is a point on a curved surface, not a lid you can take off.
|
|
119
|
-
axis: surfaceType === "plane" ? axisOf(normal) : null,
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
// Several faces can point the same way -- the lid of a hollow box and the
|
|
123
|
-
// floor of its cavity both point up -- so a name belongs to the outermost of
|
|
124
|
-
// them. Without this, opening "top" on an already-hollow part would pick the
|
|
125
|
-
// cavity floor and quietly produce nonsense.
|
|
126
|
-
const furthest = new Map();
|
|
127
|
-
for (const face of described) {
|
|
128
|
-
if (!face.axis)
|
|
129
|
-
continue;
|
|
130
|
-
const reach = reachAlong(face.centroid, FACE_DIRECTIONS[face.axis]);
|
|
131
|
-
const best = furthest.get(face.axis);
|
|
132
|
-
if (best === undefined || reach > best)
|
|
133
|
-
furthest.set(face.axis, reach);
|
|
134
|
-
}
|
|
135
|
-
return described.map(({ axis, ...face }) => ({
|
|
136
|
-
...face,
|
|
137
|
-
facing: axis && Math.abs(reachAlong(face.centroid, FACE_DIRECTIONS[axis]) - (furthest.get(axis) ?? 0)) < AXIS_TOLERANCE
|
|
138
|
-
? axis
|
|
139
|
-
: null,
|
|
19
|
+
const described = describeShellFaces(cad, solid);
|
|
20
|
+
const storedIds = preferStoredIds && shape.faceIds?.length === described.length ? shape.faceIds : undefined;
|
|
21
|
+
return described.map((face) => ({
|
|
22
|
+
index: face.index,
|
|
23
|
+
id: storedIds?.[face.index] ?? `face_${encodeURIComponent(shape.id)}_${face.index}`,
|
|
24
|
+
surfaceType: face.surfaceType,
|
|
25
|
+
area: face.area,
|
|
26
|
+
centroid: face.centroid,
|
|
27
|
+
normal: face.normal,
|
|
28
|
+
facing: face.facing,
|
|
140
29
|
}));
|
|
141
30
|
}
|
|
142
31
|
/** A shape's faces, numbered as a shell request refers to them by index. */
|
|
@@ -152,143 +41,8 @@ export function shapeFaces(shape, options = {}) {
|
|
|
152
41
|
return faceSummaries(cad, orientedSolidForShape(cad, shape), shape);
|
|
153
42
|
});
|
|
154
43
|
}
|
|
155
|
-
/**
|
|
156
|
-
* The face indexes a request's `openFaces` names, in listing order.
|
|
157
|
-
*
|
|
158
|
-
* Mixed on purpose: `["top"]` is what a script asking for a tray wants to write,
|
|
159
|
-
* and an index from the listing is the only way to name a face that no direction
|
|
160
|
-
* describes -- one wall of a hexagonal pot, say.
|
|
161
|
-
*/
|
|
162
|
-
function resolveOpenFaces(requested, faces) {
|
|
163
|
-
const chosen = new Set();
|
|
164
|
-
for (const entry of requested) {
|
|
165
|
-
if (typeof entry === "number") {
|
|
166
|
-
if (!Number.isInteger(entry) || entry < 0 || entry >= faces.length) {
|
|
167
|
-
throw new ServerCadError(`Face indexes must be between 0 and ${faces.length - 1}.`, 400);
|
|
168
|
-
}
|
|
169
|
-
chosen.add(entry);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const name = entry.trim().toLowerCase();
|
|
173
|
-
if (!FACE_DIRECTION_NAMES.includes(name)) {
|
|
174
|
-
throw new ServerCadError(`"${entry}" is not a face direction. Use one of ${FACE_DIRECTION_NAMES.join(", ")}, or a face index from the face listing.`, 400);
|
|
175
|
-
}
|
|
176
|
-
const matched = faces.filter((face) => face.facing === name);
|
|
177
|
-
if (matched.length === 0) {
|
|
178
|
-
throw new ServerCadError(`This shape has no flat ${name} face to open. Name a face index from the face listing instead.`, 422);
|
|
179
|
-
}
|
|
180
|
-
for (const face of matched)
|
|
181
|
-
chosen.add(face.index);
|
|
182
|
-
}
|
|
183
|
-
return [...chosen].sort((left, right) => left - right);
|
|
184
|
-
}
|
|
185
|
-
/** The single solid inside whatever OCCT handed back, or the shape itself. */
|
|
186
|
-
function soleSolid(cad, result) {
|
|
187
|
-
const solids = cad.getSubShapes(result, "solid");
|
|
188
|
-
return solids.length === 1 ? solids[0] : result;
|
|
189
|
-
}
|
|
190
|
-
function volumeOf(cad, shape) {
|
|
191
|
-
try {
|
|
192
|
-
const volume = cad.getVolume(shape);
|
|
193
|
-
return Number.isFinite(volume) ? volume : null;
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const TOO_THICK = "That wall thickness is too large for this shape: hollowing it at that thickness leaves no cavity. Try a thinner wall.";
|
|
200
|
-
/**
|
|
201
|
-
* What OCCT refusing outright actually means, as far as it can be told apart.
|
|
202
|
-
*
|
|
203
|
-
* A thickness that merely does not fit does not throw: it comes back as an
|
|
204
|
-
* empty compound, a solid that fails its own validity check, or the original
|
|
205
|
-
* untouched, all of which `hollowedSolid` catches by measuring. A throw is the
|
|
206
|
-
* other failure, and it tracks the *kind of surface* rather than the number.
|
|
207
|
-
*
|
|
208
|
-
* It is not "curved": an analytic sphere, cylinder, cone and torus all offset
|
|
209
|
-
* perfectly well. What fails is a **spline** skin -- OCCT will not offset one
|
|
210
|
-
* inwards at any thickness or at any reconstruction tolerance (checked at 1e-6,
|
|
211
|
-
* 1e-4, 1e-3 and 1e-2). In this app that is an elliptical ball, a loft, and
|
|
212
|
-
* anything built by extruding a profile wire, because those surfaces are
|
|
213
|
-
* splines however round they look.
|
|
214
|
-
*/
|
|
215
|
-
const CANNOT_OFFSET = "OCCT could not hollow this shape: its surfaces cannot be offset inwards into a sound solid. A shape whose skin is a spline rather than an analytic surface -- a ball with unequal radii, a loft -- fails this way at any wall thickness, however round it looks.";
|
|
216
44
|
/** What a result OCCT built but cannot write out means, which is nothing good. */
|
|
217
45
|
const UNREADABLE = "The hollowed solid came out of OCCT in a state it cannot read back, so it has not been stored. Try a different wall thickness.";
|
|
218
|
-
/** Whether a handle is a solid OCCT itself will vouch for. */
|
|
219
|
-
function isSoundSolid(cad, shape) {
|
|
220
|
-
try {
|
|
221
|
-
return cad.isSolid(shape) && Boolean(cad.isValid(shape));
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// An empty compound throws from `isValid` rather than reporting false.
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* The hollowed solid, or a refusal.
|
|
230
|
-
*
|
|
231
|
-
* Both paths run through OCCT's thick-solid builder, which answers two
|
|
232
|
-
* different questions depending on whether any face is removed -- see the
|
|
233
|
-
* module note. The checks around it are not belt and braces: every one of them
|
|
234
|
-
* has a real input that reaches it, and the order matters, because it is what
|
|
235
|
-
* separates a wall that does not fit from a shape that cannot be hollowed at
|
|
236
|
-
* all. The inward offset is measured before it is cut away, so a thickness
|
|
237
|
-
* that collapses it is reported as a thickness rather than blamed on the
|
|
238
|
-
* shape's surfaces.
|
|
239
|
-
*/
|
|
240
|
-
function hollowedSolid(cad, solid, thickness, openFaces) {
|
|
241
|
-
const sourceVolume = volumeOf(cad, solid);
|
|
242
|
-
if (sourceVolume === null || sourceVolume <= 0) {
|
|
243
|
-
throw new ServerCadError("That shape has no volume to hollow out.");
|
|
244
|
-
}
|
|
245
|
-
let hollow;
|
|
246
|
-
if (openFaces.length > 0) {
|
|
247
|
-
const faces = cad.getSubShapes(solid, "face");
|
|
248
|
-
try {
|
|
249
|
-
hollow = cad.shell(solid, openFaces.map((index) => faces[index]), thickness, SHELL_TOLERANCE);
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
throw new ServerCadError(CANNOT_OFFSET);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
// No face removed: `shell` returns the inward offset solid rather than a
|
|
257
|
-
// hollow, so the sealed void is the original cut by it.
|
|
258
|
-
let inner;
|
|
259
|
-
try {
|
|
260
|
-
inner = cad.shell(solid, [], thickness, SHELL_TOLERANCE);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
throw new ServerCadError(CANNOT_OFFSET);
|
|
264
|
-
}
|
|
265
|
-
// The cavity, before it is a cavity. Past half the part this is an empty
|
|
266
|
-
// compound, and cutting the solid by it throws -- which would otherwise
|
|
267
|
-
// read as the shape's fault rather than the thickness's.
|
|
268
|
-
const innerVolume = volumeOf(cad, inner);
|
|
269
|
-
if (!isSoundSolid(cad, inner) || innerVolume === null || innerVolume <= 0 || innerVolume >= sourceVolume) {
|
|
270
|
-
throw new ServerCadError(TOO_THICK);
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
hollow = cad.cut(solid, inner);
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
throw new ServerCadError(CANNOT_OFFSET);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
const result = soleSolid(cad, hollow);
|
|
280
|
-
if (!isSoundSolid(cad, result))
|
|
281
|
-
throw new ServerCadError(TOO_THICK);
|
|
282
|
-
const volume = volumeOf(cad, result);
|
|
283
|
-
if (volume === null || volume <= 0)
|
|
284
|
-
throw new ServerCadError(TOO_THICK);
|
|
285
|
-
// Past the thickness that fits, OCCT hands back the original solid with
|
|
286
|
-
// every extent and the validity flag intact. Hollowing always removes the
|
|
287
|
-
// cavity, so a result that weighs what it started at did nothing.
|
|
288
|
-
if (volume >= sourceVolume * (1 - 1e-6))
|
|
289
|
-
throw new ServerCadError(TOO_THICK);
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
46
|
/**
|
|
293
47
|
* Hollow a shape out and return the exact result with a mesh of it.
|
|
294
48
|
*
|
|
@@ -301,14 +55,16 @@ export function applyShell(shape, request) {
|
|
|
301
55
|
}
|
|
302
56
|
return withCadKernel((cad) => {
|
|
303
57
|
const solid = orientedSolidForShape(cad, shape);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
58
|
+
let hollow;
|
|
59
|
+
try {
|
|
60
|
+
hollow = shellSolid(cad, solid, request).solid;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// The same refusal, said the way the REST API says it.
|
|
64
|
+
if (error instanceof CadShellError)
|
|
65
|
+
throw new ServerCadError(error.message, error.status);
|
|
66
|
+
throw error;
|
|
310
67
|
}
|
|
311
|
-
const hollow = hollowedSolid(cad, solid, request.thickness, openFaces);
|
|
312
68
|
try {
|
|
313
69
|
return meshedCadResult(cad, hollow, shape);
|
|
314
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverCadShell.js","sourceRoot":"","sources":["../src/serverCadShell.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serverCadShell.js","sourceRoot":"","sources":["../src/serverCadShell.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Sampled wall-thickness analysis for exact project solids. Node-only.
|
|
3
|
+
*
|
|
4
|
+
* The sampling lives in `cadAnalysis`, which the editor's CAD worker shares;
|
|
5
|
+
* this places the stored shape and runs it on the server's kernel.
|
|
6
|
+
*/
|
|
7
|
+
import { type WallThicknessResult } from "./cadAnalysis.js";
|
|
2
8
|
import type { WorkplaneShape } from "./domain/sketchforge.js";
|
|
3
|
-
export type WallThicknessResult
|
|
4
|
-
minimum: number | null;
|
|
5
|
-
unit: "mm";
|
|
6
|
-
method: "sampled-opposing-surface-distance";
|
|
7
|
-
samplesPerFace: number;
|
|
8
|
-
facesChecked: number;
|
|
9
|
-
};
|
|
9
|
+
export type { WallThicknessResult } from "./cadAnalysis.js";
|
|
10
10
|
/**
|
|
11
11
|
* Return the smallest sampled distance between opposing faces. This is a
|
|
12
12
|
* screening estimate, not a medial-axis or certified minimum-thickness result.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverCadThickness.d.ts","sourceRoot":"","sources":["../src/serverCadThickness.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serverCadThickness.d.ts","sourceRoot":"","sources":["../src/serverCadThickness.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA8C,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CASjF"}
|
|
@@ -1,54 +1,27 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Sampled wall-thickness analysis for exact project solids. Node-only.
|
|
3
|
+
*
|
|
4
|
+
* The sampling lives in `cadAnalysis`, which the editor's CAD worker shares;
|
|
5
|
+
* this places the stored shape and runs it on the server's kernel.
|
|
6
|
+
*/
|
|
2
7
|
import { withCadKernel } from "./serverCadKernel.js";
|
|
3
8
|
import { ServerCadError } from "./serverCadOperations.js";
|
|
4
9
|
import { placedSolidForShape } from "./serverShapeBooleans.js";
|
|
5
|
-
|
|
6
|
-
const GRID = 5;
|
|
7
|
-
function samples(cad, face) {
|
|
8
|
-
const uv = cad.uvBounds(face);
|
|
9
|
-
const points = [];
|
|
10
|
-
for (let i = 0; i < GRID; i += 1)
|
|
11
|
-
for (let j = 0; j < GRID; j += 1) {
|
|
12
|
-
const u = uv.uMin + (uv.uMax - uv.uMin) * (i + 0.5) / GRID;
|
|
13
|
-
const v = uv.vMin + (uv.vMax - uv.vMin) * (j + 0.5) / GRID;
|
|
14
|
-
if (cad.classifyPointOnFace(face, u, v) !== "out") {
|
|
15
|
-
points.push({ point: cad.pointOnSurface(face, u, v), normal: cad.surfaceNormal(face, u, v) });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return points;
|
|
19
|
-
}
|
|
10
|
+
import { sampledWallThickness, WallThicknessRefusal } from "./cadAnalysis.js";
|
|
20
11
|
/**
|
|
21
12
|
* Return the smallest sampled distance between opposing faces. This is a
|
|
22
13
|
* screening estimate, not a medial-axis or certified minimum-thickness result.
|
|
23
14
|
*/
|
|
24
15
|
export function wallThickness(shape) {
|
|
25
16
|
return withCadKernel((cad) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < faces.length; i += 1)
|
|
35
|
-
for (let j = i + 1; j < faces.length; j += 1) {
|
|
36
|
-
for (const source of sampled[i]) {
|
|
37
|
-
for (const target of sampled[j]) {
|
|
38
|
-
const dot = source.normal.x * target.normal.x + source.normal.y * target.normal.y + source.normal.z * target.normal.z;
|
|
39
|
-
if (dot > -0.5)
|
|
40
|
-
continue;
|
|
41
|
-
const uv = cad.uvFromPoint(faces[j], source.point);
|
|
42
|
-
if (cad.classifyPointOnFace(faces[j], uv.u, uv.v) === "out")
|
|
43
|
-
continue;
|
|
44
|
-
const projected = cad.pointOnSurface(faces[j], uv.u, uv.v);
|
|
45
|
-
const distance = Math.hypot(source.point.x - projected.x, source.point.y - projected.y, source.point.z - projected.z);
|
|
46
|
-
if (distance > 1e-7 && distance < minimum)
|
|
47
|
-
minimum = distance;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return { minimum: Number.isFinite(minimum) ? minimum : null, unit: "mm", method: "sampled-opposing-surface-distance", samplesPerFace: GRID * GRID, facesChecked: faces.length };
|
|
17
|
+
try {
|
|
18
|
+
return sampledWallThickness(cad, placedSolidForShape(cad, shape));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof WallThicknessRefusal)
|
|
22
|
+
throw new ServerCadError(error.message, error.reason === "too_many_faces" ? 413 : 422);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
52
25
|
});
|
|
53
26
|
}
|
|
54
27
|
//# sourceMappingURL=serverCadThickness.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverCadThickness.js","sourceRoot":"","sources":["../src/serverCadThickness.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serverCadThickness.js","sourceRoot":"","sources":["../src/serverCadThickness.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAA4B,MAAM,kBAAkB,CAAC;AAKxG;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAqB;IACjD,OAAO,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,OAAO,oBAAoB,CAAC,GAAG,EAAE,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB;gBAAE,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,KAAK,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAClI,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** Split a retained STEP source into positioned, independently editable solids. Node-only. */
|
|
2
2
|
import type { ProjectAsset, WorkplaneShape } from "./domain/sketchforge.js";
|
|
3
|
-
export
|
|
4
|
-
export declare const MAX_EXPLODE_SOURCE_BYTES: number;
|
|
3
|
+
export { MAX_EXPLODE_SOLIDS, MAX_EXPLODE_SOURCE_BYTES } from "./stepAssemblyExplode.js";
|
|
5
4
|
/** Turn a STEP-imported mesh shape into one exact mesh/B-rep-backed shape per solid. */
|
|
6
5
|
export declare function explodeStepAssembly(shape: WorkplaneShape, source: ProjectAsset): Promise<WorkplaneShape[]>;
|
|
7
6
|
//# sourceMappingURL=serverStepExplode.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverStepExplode.d.ts","sourceRoot":"","sources":["../src/serverStepExplode.ts"],"names":[],"mappings":"AAAA,8FAA8F;
|
|
1
|
+
{"version":3,"file":"serverStepExplode.d.ts","sourceRoot":"","sources":["../src/serverStepExplode.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAK9F,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAMxF,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAc1G"}
|