@constructorfleet/sketchforge-engine 1.19.0 → 1.20.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/README.md +3 -3
- package/dist/domain/sketchforge.d.ts +39 -0
- package/dist/domain/sketchforge.d.ts.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +345 -29
- package/dist/engine.js.map +1 -1
- package/dist/faceLineage.d.ts +10 -0
- package/dist/faceLineage.d.ts.map +1 -0
- package/dist/faceLineage.js +84 -0
- package/dist/faceLineage.js.map +1 -0
- package/dist/parametricRecompute.d.ts +2 -0
- package/dist/parametricRecompute.d.ts.map +1 -1
- package/dist/parametricRecompute.js +69 -0
- package/dist/parametricRecompute.js.map +1 -1
- package/dist/revolveRequests.d.ts +10 -0
- package/dist/revolveRequests.d.ts.map +1 -1
- package/dist/serverCadOperations.d.ts +54 -3
- package/dist/serverCadOperations.d.ts.map +1 -1
- package/dist/serverCadOperations.js +224 -6
- package/dist/serverCadOperations.js.map +1 -1
- package/dist/serverCadShell.d.ts +3 -1
- package/dist/serverCadShell.d.ts.map +1 -1
- package/dist/serverCadShell.js +15 -5
- package/dist/serverCadShell.js.map +1 -1
- package/dist/sketchExtrusionRequest.d.ts.map +1 -1
- package/dist/sketchExtrusionRequest.js +1 -0
- package/dist/sketchExtrusionRequest.js.map +1 -1
- package/dist/skfProject.d.ts.map +1 -1
- package/dist/skfProject.js +85 -8
- package/dist/skfProject.js.map +1 -1
- package/dist/stepComponentMetadata.d.ts.map +1 -1
- package/dist/stepComponentMetadata.js +55 -2
- package/dist/stepComponentMetadata.js.map +1 -1
- package/dist/stepExport.d.ts.map +1 -1
- package/dist/stepExport.js +54 -8
- package/dist/stepExport.js.map +1 -1
- package/dist/stepGlbColors.d.ts.map +1 -1
- package/dist/stepGlbColors.js +10 -2
- package/dist/stepGlbColors.js.map +1 -1
- package/dist/support/lib/operator-configuration.json +10 -0
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* -- including the project lock, which a macro writing a directory needs and
|
|
15
15
|
* does not have today -- without pulling `next/server` in with it.
|
|
16
16
|
*/
|
|
17
|
-
import { applyEdgeTreatment, applyBakedMirror, applyDraftPrism, shapeEdges, shapeWithCadResult, ServerCadError, } from "./serverCadOperations.js";
|
|
17
|
+
import { allEdgeTreatmentEdgeCount, applyEdgeTreatment, applyBakedMirror, applyDraftPrism, applySketchFaceDraft, exactPlainSketchRevolveResult, isPlainFullTurnSketchRevolve, shapeEdges, shapeWithCadResult, ServerCadError, } from "./serverCadOperations.js";
|
|
18
18
|
import { applyShell, shapeFaces, } from "./serverCadShell.js";
|
|
19
19
|
import { measureBetween as measureBetweenEntities, shapeMeasurements, } from "./serverCadMeasure.js";
|
|
20
20
|
import { shapeProperties } from "./serverCadProperties.js";
|
|
@@ -32,11 +32,52 @@ import { serverManifoldRuntime } from "./serverManifoldRuntime.js";
|
|
|
32
32
|
import { createLocalId } from "./localIds.js";
|
|
33
33
|
import { invalidSketchSweepProfile, sketchSweepShapeFromRequest } from "./sketchSweepRequest.js";
|
|
34
34
|
import { sketchExtrusionShapeFromRequest } from "./sketchExtrusionRequest.js";
|
|
35
|
-
import { findSketchOutput, recomputeSketchOutput } from "./parametricRecompute.js";
|
|
35
|
+
import { findSketchOutput, recomputeSketchOutput, replaySingleAllEdgeTreatment } from "./parametricRecompute.js";
|
|
36
36
|
import { boundsForShapes, footprintCenter, shapeBounds } from "./shapeBounds.js";
|
|
37
37
|
import { REQUESTABLE_SHAPE_KINDS, shapeFromRequest, shapeWithRequestedChanges, SHAPE_PARAMETERS, } from "./shapeRequests.js";
|
|
38
38
|
import { canonicalizeShape, cleanNearZero, cleanRotationDegrees, cloneWorkplaneShapeTreeWithFreshIds, withHoleMode, } from "./workplaneShapes.js";
|
|
39
39
|
const UNIQUE_CURVED_FACE_SURFACES = new Set(["cone", "sphere", "torus", "cylinder"]);
|
|
40
|
+
const FACE_DRAFT_AXES = new Set(["x", "y", "z", "-x", "-y", "-z"]);
|
|
41
|
+
function faceDraftInput(value) {
|
|
42
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
43
|
+
return null;
|
|
44
|
+
const draft = value;
|
|
45
|
+
if (typeof draft.sourceFaceId !== "string" || !draft.sourceFaceId.trim()
|
|
46
|
+
|| typeof draft.angle !== "number" || !Number.isFinite(draft.angle) || draft.angle === 0 || Math.abs(draft.angle) >= 45
|
|
47
|
+
|| typeof draft.pullDirection !== "string" || !FACE_DRAFT_AXES.has(draft.pullDirection))
|
|
48
|
+
return null;
|
|
49
|
+
return {
|
|
50
|
+
sourceFaceId: draft.sourceFaceId,
|
|
51
|
+
angle: draft.angle,
|
|
52
|
+
pullDirection: draft.pullDirection,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function revolveSourceScale(shape) {
|
|
56
|
+
const mesh = shape.importedMesh;
|
|
57
|
+
const ratio = (drawn, natural) => natural && natural > 0 ? drawn / natural : 1;
|
|
58
|
+
return {
|
|
59
|
+
width: ratio(shape.width, mesh?.baseWidth),
|
|
60
|
+
depth: ratio(shape.depth, mesh?.baseDepth),
|
|
61
|
+
height: ratio(shape.height, mesh?.baseHeight),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function resizeRebuiltRevolve(shape, scale) {
|
|
65
|
+
const mesh = shape.importedMesh;
|
|
66
|
+
if (!mesh)
|
|
67
|
+
return shape;
|
|
68
|
+
const width = Math.max(0.01, (mesh.baseWidth ?? shape.width) * scale.width);
|
|
69
|
+
const depth = Math.max(0.01, (mesh.baseDepth ?? shape.depth) * scale.depth);
|
|
70
|
+
const height = Math.max(0.01, (mesh.baseHeight ?? shape.height) * scale.height);
|
|
71
|
+
return { ...shape, width, depth, height, size: Math.max(width, depth) };
|
|
72
|
+
}
|
|
73
|
+
function faceNormalMatches(left, right) {
|
|
74
|
+
return Math.hypot(left.x - right.x, left.y - right.y, left.z - right.z) <= 1e-5;
|
|
75
|
+
}
|
|
76
|
+
async function findUniqueDraftFace(shape, normal) {
|
|
77
|
+
const matches = (await shapeFaces(shape)).filter((face) => face.surfaceType === "plane" && face.normal
|
|
78
|
+
&& faceNormalMatches(face.normal, normal));
|
|
79
|
+
return matches.length === 1 ? matches[0] : null;
|
|
80
|
+
}
|
|
40
81
|
/**
|
|
41
82
|
* Carry face metadata across a parameter edit only when a face has a unique
|
|
42
83
|
* counterpart. Curved faces may change their sampled normal as their defining
|
|
@@ -331,13 +372,33 @@ export function createEngine(store) {
|
|
|
331
372
|
});
|
|
332
373
|
}
|
|
333
374
|
/** The shared tail of `treat` and `shell`: run the kernel, store the result on the shape. */
|
|
334
|
-
async function cadTreatment(projectId, shapeId, run) {
|
|
375
|
+
async function cadTreatment(projectId, shapeId, operation, run) {
|
|
335
376
|
return mutating(projectId, async (document) => {
|
|
336
377
|
const found = oneShape(document, shapeId);
|
|
337
378
|
if (!found.ok)
|
|
338
379
|
return found;
|
|
339
380
|
if (found.value.locked)
|
|
340
381
|
return failed({ reason: "shape_locked", ids: [shapeId] });
|
|
382
|
+
const hasEdgeTreatments = (found.value.edgeTreatments?.length ?? 0) > 0
|
|
383
|
+
|| (found.value.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
384
|
+
const hasDraftPrism = found.value.draftPrismAngle !== undefined;
|
|
385
|
+
const replayableDraftTreatment = operation === "all-edge-treatment"
|
|
386
|
+
&& !hasEdgeTreatments
|
|
387
|
+
&& found.value.sketchOperation === "extrude"
|
|
388
|
+
&& Boolean(found.value.sketchProfile)
|
|
389
|
+
&& hasDraftPrism;
|
|
390
|
+
const incompatible = found.value.faceDraft !== undefined
|
|
391
|
+
|| operation === "edge-treatment" && hasDraftPrism
|
|
392
|
+
|| operation === "all-edge-treatment" && hasDraftPrism && !replayableDraftTreatment
|
|
393
|
+
|| operation === "prism-draft" && hasEdgeTreatments
|
|
394
|
+
|| operation === "shell" && (found.value.draftPrismAngle !== undefined || hasEdgeTreatments);
|
|
395
|
+
if (incompatible) {
|
|
396
|
+
return failed({
|
|
397
|
+
reason: "cad_refused",
|
|
398
|
+
message: "Face drafts cannot be combined with edge treatments, prism drafts, or shelling. Draft prisms only support one all-edge treatment on a retained sketch extrusion; shelling cannot be combined with persisted draft or treatment features.",
|
|
399
|
+
status: 422,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
341
402
|
let changed;
|
|
342
403
|
try {
|
|
343
404
|
changed = await run(found.value);
|
|
@@ -413,11 +474,15 @@ export function createEngine(store) {
|
|
|
413
474
|
changeShape: (projectId, shapeId, changes) => mutating(projectId, async (document) => {
|
|
414
475
|
const byOutputId = oneShape(document, shapeId);
|
|
415
476
|
// A persisted sketch source is an input node, not a renderable shape.
|
|
416
|
-
// When a caller edits that node's profile
|
|
417
|
-
// output and rebuild it before the single project write
|
|
477
|
+
// When a caller edits that node's profile or revolve settings, resolve
|
|
478
|
+
// its dependent output and rebuild it before the single project write. The
|
|
418
479
|
// output keeps its own stable object ID; SKF export derives the source
|
|
419
480
|
// node from the synchronized profile on that output.
|
|
420
|
-
const
|
|
481
|
+
const sourceFeatureInputChanged = changes.sketchProfile !== undefined
|
|
482
|
+
|| changes.draftPrismAngle !== undefined
|
|
483
|
+
|| Object.hasOwn(changes, "faceDraft")
|
|
484
|
+
|| REVOLVE_CHANGE_FIELDS.some((field) => changes[field] !== undefined);
|
|
485
|
+
const sourceOutputs = !byOutputId.ok && sourceFeatureInputChanged
|
|
421
486
|
? findSketchOutput(document.shapes, shapeId)
|
|
422
487
|
: [];
|
|
423
488
|
const sourceSketchTarget = !byOutputId.ok && sourceOutputs.length === 1;
|
|
@@ -430,7 +495,9 @@ export function createEngine(store) {
|
|
|
430
495
|
return found;
|
|
431
496
|
const existing = found.value;
|
|
432
497
|
if (sourceSketchTarget) {
|
|
433
|
-
const
|
|
498
|
+
const isRevolveSource = isRevolveShape(existing);
|
|
499
|
+
const outputFields = Object.keys(changes).filter((field) => field !== "sketchProfile" && field !== "draftPrismAngle" && field !== "faceDraft"
|
|
500
|
+
&& !(isRevolveSource && REVOLVE_CHANGE_FIELDS.includes(field)));
|
|
434
501
|
if (outputFields.length > 0) {
|
|
435
502
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: outputFields, because: "not_this_kind" });
|
|
436
503
|
}
|
|
@@ -441,9 +508,48 @@ export function createEngine(store) {
|
|
|
441
508
|
if (existing.locked && changes.locked !== false)
|
|
442
509
|
return failed({ reason: "shape_locked", ids: [shapeId] });
|
|
443
510
|
const replacingSketchProfile = changes.sketchProfile !== undefined;
|
|
511
|
+
const faceDraftChanged = Object.hasOwn(changes, "faceDraft");
|
|
512
|
+
const clearingFaceDraft = faceDraftChanged && changes.faceDraft === null && existing.faceDraft !== undefined;
|
|
513
|
+
const requestedFaceDraft = faceDraftChanged
|
|
514
|
+
? changes.faceDraft === null ? undefined : faceDraftInput(changes.faceDraft)
|
|
515
|
+
: existing.faceDraft;
|
|
444
516
|
const isSketchSweep = existing.sketchOperation === "sweep" && Boolean(existing.sketchProfile);
|
|
445
517
|
const isSketchExtrusion = existing.sketchOperation === "extrude" && Boolean(existing.sketchProfile);
|
|
446
|
-
|
|
518
|
+
const isSketchRevolve = isRevolveShape(existing);
|
|
519
|
+
if (faceDraftChanged && changes.faceDraft !== null && !requestedFaceDraft) {
|
|
520
|
+
return failed({ reason: "invalid_shape_request", message: "A face draft requires a stable source face ID, a non-zero angle below 45 degrees, and a signed local pull axis." });
|
|
521
|
+
}
|
|
522
|
+
const isExactSketchRevolve = isPlainFullTurnSketchRevolve(existing);
|
|
523
|
+
if ((faceDraftChanged || (replacingSketchProfile && existing.faceDraft))
|
|
524
|
+
&& !isSketchExtrusion && !isSketchSweep && !isExactSketchRevolve) {
|
|
525
|
+
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: ["faceDraft"], because: "not_this_kind" });
|
|
526
|
+
}
|
|
527
|
+
if (requestedFaceDraft
|
|
528
|
+
&& ((existing.edgeTreatments?.length ?? 0) > 0 || (existing.edgeTreatmentHistory?.length ?? 0) > 0
|
|
529
|
+
|| existing.draftPrismAngle !== undefined)) {
|
|
530
|
+
return failed({ reason: "invalid_shape_request", message: "Face drafts cannot be combined with prism drafts or edge treatments yet." });
|
|
531
|
+
}
|
|
532
|
+
if (faceDraftChanged && existing.faceDraft && requestedFaceDraft
|
|
533
|
+
&& requestedFaceDraft.sourceFaceId !== existing.faceDraft.sourceFaceId) {
|
|
534
|
+
return failed({ reason: "invalid_shape_request", message: "Clear the existing face draft before selecting a different source face." });
|
|
535
|
+
}
|
|
536
|
+
const draftPrismChanged = changes.draftPrismAngle !== undefined;
|
|
537
|
+
const requestedDraftPrismAngle = typeof changes.draftPrismAngle === "number" ? changes.draftPrismAngle : undefined;
|
|
538
|
+
if (draftPrismChanged && (!isSketchExtrusion || requestedDraftPrismAngle === undefined || !Number.isFinite(requestedDraftPrismAngle)
|
|
539
|
+
|| requestedDraftPrismAngle === 0 || Math.abs(requestedDraftPrismAngle) >= 45)) {
|
|
540
|
+
return failed({
|
|
541
|
+
reason: "invalid_shape_request",
|
|
542
|
+
message: "A draft prism angle must be non-zero and between -45 and 45 degrees on a sketch extrusion.",
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (draftPrismChanged && requestedFaceDraft) {
|
|
546
|
+
return failed({ reason: "invalid_shape_request", message: "Face drafts cannot be combined with prism drafts yet." });
|
|
547
|
+
}
|
|
548
|
+
if (draftPrismChanged && existing.draftPrismAngle === undefined
|
|
549
|
+
&& ((existing.edgeTreatments?.length ?? 0) > 0 || (existing.edgeTreatmentHistory?.length ?? 0) > 0)) {
|
|
550
|
+
return failed({ reason: "invalid_shape_request", message: "A prism draft cannot be added after an edge treatment." });
|
|
551
|
+
}
|
|
552
|
+
if (replacingSketchProfile && !isSketchSweep && !isSketchExtrusion && !isSketchRevolve) {
|
|
447
553
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: ["sketchProfile"], because: "no_sweep" });
|
|
448
554
|
}
|
|
449
555
|
const sweptShape = isRevolveShape(existing);
|
|
@@ -456,8 +562,10 @@ export function createEngine(store) {
|
|
|
456
562
|
// leave a thread's pitch readable but not writable.
|
|
457
563
|
const revolveChanges = sweptShape ? REVOLVE_CHANGE_FIELDS.filter((field) => changes[field] !== undefined) : [];
|
|
458
564
|
let swept = existing;
|
|
459
|
-
if (replacingSketchProfile && isSketchExtrusion)
|
|
460
|
-
|
|
565
|
+
if ((replacingSketchProfile && isSketchExtrusion) || (draftPrismChanged && isSketchExtrusion)
|
|
566
|
+
|| (faceDraftChanged && isSketchExtrusion)) {
|
|
567
|
+
const sourceFrame = existing.faceDraftSourceFrame ?? existing.draftPrismSourceFrame ?? existing.cadBrepFrame;
|
|
568
|
+
const sourceHeight = existing.draftPrismSourceHeight ?? existing.faceDraftSourceFrame?.height ?? existing.height;
|
|
461
569
|
try {
|
|
462
570
|
const rebuilt = await sketchExtrusionShapeFromRequest({
|
|
463
571
|
id: existing.id,
|
|
@@ -465,34 +573,89 @@ export function createEngine(store) {
|
|
|
465
573
|
name: existing.name,
|
|
466
574
|
color: existing.color,
|
|
467
575
|
hole: existing.hole,
|
|
468
|
-
profile: changes.sketchProfile,
|
|
469
|
-
height:
|
|
470
|
-
x: existing.x - (
|
|
471
|
-
z: existing.z - (
|
|
472
|
-
elevation: (existing.elevation ?? 0) - (
|
|
576
|
+
profile: replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile,
|
|
577
|
+
height: sourceHeight,
|
|
578
|
+
x: existing.x - (sourceFrame?.x ?? 0),
|
|
579
|
+
z: existing.z - (sourceFrame?.z ?? 0),
|
|
580
|
+
elevation: (existing.elevation ?? 0) - (sourceFrame?.elevation ?? 0),
|
|
473
581
|
rotation: existing.rotation,
|
|
474
582
|
rotationX: existing.rotationX,
|
|
475
583
|
rotationZ: existing.rotationZ,
|
|
476
584
|
});
|
|
477
585
|
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, ...sourceMetadata } = existing;
|
|
478
|
-
|
|
586
|
+
const draftPrismAngle = draftPrismChanged ? requestedDraftPrismAngle : existing.draftPrismAngle;
|
|
587
|
+
const rebuiltSource = {
|
|
588
|
+
...sourceMetadata,
|
|
589
|
+
...rebuilt,
|
|
590
|
+
id: existing.id,
|
|
591
|
+
sketchSourceId: existing.sketchSourceId ?? rebuilt.sketchSourceId,
|
|
592
|
+
faceDraft: undefined,
|
|
593
|
+
faceDraftSourceFrame: undefined,
|
|
594
|
+
...(draftPrismAngle === undefined ? {} : {
|
|
595
|
+
draftPrismSourceHeight: sourceHeight,
|
|
596
|
+
...(sourceFrame ? { draftPrismSourceFrame: sourceFrame } : {}),
|
|
597
|
+
}),
|
|
598
|
+
};
|
|
599
|
+
const hasRetainedTreatments = (existing.edgeTreatments?.length ?? 0) > 0
|
|
600
|
+
|| (existing.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
601
|
+
const replayDraftAndTreatment = hasRetainedTreatments
|
|
602
|
+
&& draftPrismAngle !== undefined
|
|
603
|
+
&& (replacingSketchProfile || clearingFaceDraft || draftPrismChanged);
|
|
604
|
+
const replayedSource = (replacingSketchProfile || clearingFaceDraft || draftPrismChanged) && hasRetainedTreatments
|
|
605
|
+
? await replaySingleAllEdgeTreatment(clearingFaceDraft ? { ...existing, faceDraft: undefined } : existing, rebuiltSource, draftPrismChanged ? requestedDraftPrismAngle : undefined)
|
|
606
|
+
: rebuiltSource;
|
|
607
|
+
if (requestedFaceDraft) {
|
|
608
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
609
|
+
? existing.faceDraft.sourceFaceNormal
|
|
610
|
+
: undefined;
|
|
611
|
+
const selectedOnPrior = priorReference ? undefined
|
|
612
|
+
: (await shapeFaces(existing, isExactSketchRevolve ? { topology: "exact" } : undefined))
|
|
613
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
614
|
+
const sourceFaceNormal = priorReference ?? selectedOnPrior?.normal;
|
|
615
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
616
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
617
|
+
}
|
|
618
|
+
const currentFace = await findUniqueDraftFace(replayedSource, sourceFaceNormal);
|
|
619
|
+
if (!currentFace)
|
|
620
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
621
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
622
|
+
const result = await applySketchFaceDraft(replayedSource, {
|
|
623
|
+
...requestedFaceDraft,
|
|
624
|
+
sourceFaceId: currentFace.id,
|
|
625
|
+
});
|
|
626
|
+
swept = {
|
|
627
|
+
...shapeWithCadResult(replayedSource, result),
|
|
628
|
+
faceDraft,
|
|
629
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
else if (draftPrismAngle !== undefined && !replayDraftAndTreatment) {
|
|
633
|
+
swept = {
|
|
634
|
+
...shapeWithCadResult(replayedSource, await applyDraftPrism(replayedSource, { angle: draftPrismAngle })),
|
|
635
|
+
draftPrismAngle,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
swept = replayedSource;
|
|
640
|
+
}
|
|
479
641
|
}
|
|
480
642
|
catch (error) {
|
|
481
643
|
return failed({ reason: "sweep_failed", message: error instanceof Error ? error.message : "The sketch extrusion could not be rebuilt." });
|
|
482
644
|
}
|
|
483
645
|
}
|
|
484
|
-
if (replacingSketchProfile && isSketchSweep) {
|
|
485
|
-
const
|
|
646
|
+
if ((replacingSketchProfile || faceDraftChanged) && isSketchSweep) {
|
|
647
|
+
const profileToBuild = replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile;
|
|
648
|
+
const invalidProfile = replacingSketchProfile ? invalidSketchSweepProfile(profileToBuild) : undefined;
|
|
486
649
|
if (invalidProfile)
|
|
487
650
|
return failed({ reason: "invalid_shape_request", message: invalidProfile });
|
|
488
|
-
const frame = existing.cadBrepFrame;
|
|
651
|
+
const frame = existing.faceDraftSourceFrame ?? existing.cadBrepFrame;
|
|
489
652
|
try {
|
|
490
653
|
const rebuilt = await sketchSweepShapeFromRequest({
|
|
491
654
|
id: existing.id,
|
|
492
655
|
name: existing.name,
|
|
493
656
|
color: existing.color,
|
|
494
657
|
hole: existing.hole,
|
|
495
|
-
profile:
|
|
658
|
+
profile: profileToBuild,
|
|
496
659
|
x: existing.x - (frame?.x ?? 0),
|
|
497
660
|
z: existing.z - (frame?.z ?? 0),
|
|
498
661
|
elevation: (existing.elevation ?? 0) - (frame?.elevation ?? 0),
|
|
@@ -514,14 +677,47 @@ export function createEngine(store) {
|
|
|
514
677
|
resized.size = Math.max(resized.width, resized.depth);
|
|
515
678
|
const refreshedFaceIds = (await shapeFaces(rebuilt))
|
|
516
679
|
.map((face) => `face_${createLocalId(`${existing.id}-${face.index}`)}`);
|
|
517
|
-
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, ...sourceMetadata } = existing;
|
|
518
|
-
|
|
680
|
+
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, faceDraft: _faceDraft, faceDraftSourceFrame: _faceDraftSourceFrame, ...sourceMetadata } = existing;
|
|
681
|
+
const rebuiltSweep = {
|
|
519
682
|
...sourceMetadata,
|
|
520
683
|
...resized,
|
|
521
684
|
id: existing.id,
|
|
522
685
|
sketchSourceId: existing.sketchSourceId ?? rebuilt.sketchSourceId,
|
|
523
686
|
faceIds: refreshedFaceIds,
|
|
687
|
+
faceDraft: undefined,
|
|
688
|
+
faceDraftSourceFrame: undefined,
|
|
524
689
|
};
|
|
690
|
+
const replayedSource = replacingSketchProfile || clearingFaceDraft
|
|
691
|
+
? await replaySingleAllEdgeTreatment(clearingFaceDraft ? { ...existing, faceDraft: undefined } : existing, rebuiltSweep)
|
|
692
|
+
: rebuiltSweep;
|
|
693
|
+
if (requestedFaceDraft) {
|
|
694
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
695
|
+
? existing.faceDraft.sourceFaceNormal
|
|
696
|
+
: undefined;
|
|
697
|
+
const selectedOnPrior = priorReference ? undefined
|
|
698
|
+
: (await shapeFaces(existing, isExactSketchRevolve ? { topology: "exact" } : undefined))
|
|
699
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
700
|
+
const sourceFaceNormal = priorReference ?? selectedOnPrior?.normal;
|
|
701
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
702
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
703
|
+
}
|
|
704
|
+
const currentFace = await findUniqueDraftFace(replayedSource, sourceFaceNormal);
|
|
705
|
+
if (!currentFace)
|
|
706
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
707
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
708
|
+
const result = await applySketchFaceDraft(replayedSource, {
|
|
709
|
+
...requestedFaceDraft,
|
|
710
|
+
sourceFaceId: currentFace.id,
|
|
711
|
+
});
|
|
712
|
+
swept = {
|
|
713
|
+
...shapeWithCadResult(replayedSource, result),
|
|
714
|
+
faceDraft,
|
|
715
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
swept = replayedSource;
|
|
720
|
+
}
|
|
525
721
|
}
|
|
526
722
|
catch (error) {
|
|
527
723
|
return failed({
|
|
@@ -530,7 +726,88 @@ export function createEngine(store) {
|
|
|
530
726
|
});
|
|
531
727
|
}
|
|
532
728
|
}
|
|
533
|
-
if (
|
|
729
|
+
if ((faceDraftChanged || (replacingSketchProfile && existing.faceDraft)
|
|
730
|
+
|| (revolveChanges.length > 0 && existing.faceDraft)) && isSketchRevolve) {
|
|
731
|
+
try {
|
|
732
|
+
const runtime = await serverManifoldRuntime();
|
|
733
|
+
const sourceFrame = existing.faceDraftSourceFrame ?? existing.cadBrepFrame;
|
|
734
|
+
const rebuildSeed = sourceFrame ? {
|
|
735
|
+
...existing,
|
|
736
|
+
x: sourceFrame.x,
|
|
737
|
+
z: sourceFrame.z,
|
|
738
|
+
elevation: sourceFrame.elevation,
|
|
739
|
+
} : existing;
|
|
740
|
+
const rebuiltAtNaturalScale = rebuiltRevolveShape(runtime, rebuildSeed, {
|
|
741
|
+
profile: (replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile),
|
|
742
|
+
settings: revolveChanges.length > 0
|
|
743
|
+
? requestedRevolveSettings(existing.sketchRevolve, changes)
|
|
744
|
+
: existing.sketchRevolve,
|
|
745
|
+
});
|
|
746
|
+
const sourceScale = existing.faceDraftSourceScale ?? revolveSourceScale(existing);
|
|
747
|
+
const rebuilt = resizeRebuiltRevolve(rebuiltAtNaturalScale, sourceScale);
|
|
748
|
+
if (requestedFaceDraft) {
|
|
749
|
+
const exactBase = shapeWithCadResult(rebuilt, await exactPlainSketchRevolveResult(rebuilt));
|
|
750
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
751
|
+
? existing.faceDraft.sourceFaceNormal
|
|
752
|
+
: undefined;
|
|
753
|
+
const selectedOnPrior = priorReference ? undefined
|
|
754
|
+
: (await shapeFaces(existing, { topology: "exact" }))
|
|
755
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
756
|
+
const sourceFaceNormal = priorReference ?? selectedOnPrior?.normal;
|
|
757
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
758
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
759
|
+
}
|
|
760
|
+
const currentFace = await findUniqueDraftFace(exactBase, sourceFaceNormal);
|
|
761
|
+
if (!currentFace)
|
|
762
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
763
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
764
|
+
const result = await applySketchFaceDraft(exactBase, {
|
|
765
|
+
...requestedFaceDraft,
|
|
766
|
+
sourceFaceId: currentFace.id,
|
|
767
|
+
});
|
|
768
|
+
swept = {
|
|
769
|
+
...shapeWithCadResult(exactBase, result),
|
|
770
|
+
faceDraft,
|
|
771
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame ?? exactBase.cadBrepFrame,
|
|
772
|
+
faceDraftSourceScale: sourceScale,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
const clearedDraft = {
|
|
777
|
+
...rebuilt,
|
|
778
|
+
faceDraft: undefined,
|
|
779
|
+
faceDraftSourceFrame: undefined,
|
|
780
|
+
faceDraftSourceScale: undefined,
|
|
781
|
+
};
|
|
782
|
+
swept = clearingFaceDraft
|
|
783
|
+
? await replaySingleAllEdgeTreatment({ ...existing, faceDraft: undefined }, clearedDraft)
|
|
784
|
+
: clearedDraft;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
return failed({
|
|
789
|
+
reason: "sweep_failed",
|
|
790
|
+
message: error instanceof Error ? error.message : "The sketch revolve could not be rebuilt.",
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else if (replacingSketchProfile && isSketchRevolve) {
|
|
795
|
+
try {
|
|
796
|
+
const runtime = await serverManifoldRuntime();
|
|
797
|
+
const rebuilt = rebuiltRevolveShape(runtime, existing, {
|
|
798
|
+
profile: changes.sketchProfile,
|
|
799
|
+
settings: existing.sketchRevolve,
|
|
800
|
+
});
|
|
801
|
+
swept = await replaySingleAllEdgeTreatment(existing, rebuilt);
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
return failed({
|
|
805
|
+
reason: "sweep_failed",
|
|
806
|
+
message: error instanceof Error ? error.message : "The sketch revolve could not be rebuilt.",
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (revolveChanges.length > 0 && sweptShape && !existing.faceDraft) {
|
|
534
811
|
const outline = changes.profile ?? changes.outline ?? outlineFromProfile(existing.sketchProfile);
|
|
535
812
|
if (!outline) {
|
|
536
813
|
return failed({
|
|
@@ -545,10 +822,11 @@ export function createEngine(store) {
|
|
|
545
822
|
return failed({ reason: "invalid_shape_request", message: parsed.error });
|
|
546
823
|
try {
|
|
547
824
|
const runtime = await serverManifoldRuntime();
|
|
548
|
-
|
|
825
|
+
const rebuilt = rebuiltRevolveShape(runtime, existing, {
|
|
549
826
|
profile: parsed.profile,
|
|
550
827
|
settings: requestedRevolveSettings(existing.sketchRevolve, changes),
|
|
551
828
|
});
|
|
829
|
+
swept = await replaySingleAllEdgeTreatment(existing, rebuilt);
|
|
552
830
|
}
|
|
553
831
|
catch (error) {
|
|
554
832
|
return failed({
|
|
@@ -562,6 +840,8 @@ export function createEngine(store) {
|
|
|
562
840
|
const sweepFields = [
|
|
563
841
|
...(sweptShape ? REVOLVE_CHANGE_FIELDS : REVOLVE_ONLY_FIELDS),
|
|
564
842
|
"sketchProfile",
|
|
843
|
+
"draftPrismAngle",
|
|
844
|
+
"faceDraft",
|
|
565
845
|
];
|
|
566
846
|
// Shape summaries expose the source ID for identity; echoing a full
|
|
567
847
|
// summary into PATCH must not let callers replace that identity.
|
|
@@ -806,12 +1086,12 @@ export function createEngine(store) {
|
|
|
806
1086
|
return failed(cadFailure(error));
|
|
807
1087
|
}
|
|
808
1088
|
}),
|
|
809
|
-
faces: (projectId, shapeId) => reading(projectId, async (document) => {
|
|
1089
|
+
faces: (projectId, shapeId, topology) => reading(projectId, async (document) => {
|
|
810
1090
|
const found = oneShape(document, shapeId);
|
|
811
1091
|
if (!found.ok)
|
|
812
1092
|
return found;
|
|
813
1093
|
try {
|
|
814
|
-
return ok(await shapeFaces(found.value));
|
|
1094
|
+
return ok(await shapeFaces(found.value, topology === "exact" ? { topology } : undefined));
|
|
815
1095
|
}
|
|
816
1096
|
catch (error) {
|
|
817
1097
|
return failed(cadFailure(error));
|
|
@@ -868,8 +1148,44 @@ export function createEngine(store) {
|
|
|
868
1148
|
return failed(cadFailure(error));
|
|
869
1149
|
}
|
|
870
1150
|
}),
|
|
871
|
-
treat: (projectId, shapeId, treatment) => cadTreatment(projectId, shapeId,
|
|
872
|
-
|
|
1151
|
+
treat: (projectId, shapeId, treatment) => cadTreatment(projectId, shapeId, (treatment.edges === undefined || treatment.edges.length === 0)
|
|
1152
|
+
&& (treatment.kind === "fillet" || treatment.angle === undefined || Math.abs(treatment.angle - 45) < 0.001)
|
|
1153
|
+
? "all-edge-treatment"
|
|
1154
|
+
: "edge-treatment", async (shape) => {
|
|
1155
|
+
const treated = shapeWithCadResult(shape, await applyEdgeTreatment(shape, treatment));
|
|
1156
|
+
if (shape.draftPrismAngle === undefined)
|
|
1157
|
+
return treated;
|
|
1158
|
+
const feature = {
|
|
1159
|
+
kind: treatment.kind,
|
|
1160
|
+
amount: treatment.amount,
|
|
1161
|
+
edgeCount: await allEdgeTreatmentEdgeCount(shape, treatment.kind),
|
|
1162
|
+
allEdges: true,
|
|
1163
|
+
...(treatment.kind === "chamfer" ? { chamferAngle: treatment.angle ?? 45 } : {}),
|
|
1164
|
+
};
|
|
1165
|
+
const history = shape.edgeTreatmentHistory ?? [];
|
|
1166
|
+
return {
|
|
1167
|
+
...treated,
|
|
1168
|
+
edgeTreatments: [...(shape.edgeTreatments ?? []), feature],
|
|
1169
|
+
edgeTreatmentHistory: [
|
|
1170
|
+
...history,
|
|
1171
|
+
{
|
|
1172
|
+
id: createLocalId("edge-history"),
|
|
1173
|
+
createdAt: Date.now(),
|
|
1174
|
+
feature,
|
|
1175
|
+
before: structuredClone(shape),
|
|
1176
|
+
},
|
|
1177
|
+
],
|
|
1178
|
+
};
|
|
1179
|
+
}),
|
|
1180
|
+
draftPrism: (projectId, shapeId, angle) => cadTreatment(projectId, shapeId, "prism-draft", async (shape) => {
|
|
1181
|
+
const sourceFrame = shape.draftPrismSourceFrame ?? shape.cadBrepFrame;
|
|
1182
|
+
return {
|
|
1183
|
+
...shapeWithCadResult(shape, await applyDraftPrism(shape, { angle })),
|
|
1184
|
+
draftPrismAngle: angle,
|
|
1185
|
+
draftPrismSourceHeight: shape.draftPrismSourceHeight ?? shape.height,
|
|
1186
|
+
...(sourceFrame ? { draftPrismSourceFrame: sourceFrame } : {}),
|
|
1187
|
+
};
|
|
1188
|
+
}),
|
|
873
1189
|
mirrorBaked: (projectId, ids, axis) => mutating(projectId, async (document) => {
|
|
874
1190
|
if (ids.length < 1)
|
|
875
1191
|
return failed({ reason: "selection_too_small", needed: 1, given: 0 });
|
|
@@ -899,7 +1215,7 @@ export function createEngine(store) {
|
|
|
899
1215
|
revision: await writeShapes(projectId, document, shapes),
|
|
900
1216
|
});
|
|
901
1217
|
}),
|
|
902
|
-
shell: (projectId, shapeId, request) => cadTreatment(projectId, shapeId, async (shape) => (shapeWithCadResult(shape, await applyShell(shape, request)))),
|
|
1218
|
+
shell: (projectId, shapeId, request) => cadTreatment(projectId, shapeId, "shell", async (shape) => (shapeWithCadResult(shape, await applyShell(shape, request)))),
|
|
903
1219
|
};
|
|
904
1220
|
}
|
|
905
1221
|
//# sourceMappingURL=engine.js.map
|