@constructorfleet/sketchforge-engine 1.18.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 +364 -21
- 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 +85 -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 +4 -0
- package/dist/sketchExtrusionRequest.d.ts.map +1 -0
- package/dist/sketchExtrusionRequest.js +92 -0
- package/dist/sketchExtrusionRequest.js.map +1 -0
- 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 +78 -2
- package/dist/support/lib/operatorConfiguration.d.ts +26 -2
- package/dist/support/lib/operatorConfiguration.d.ts.map +1 -1
- 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";
|
|
@@ -31,11 +31,53 @@ import { boundsForShapes as boundsForMirroring, restoreGroupedChildren, separate
|
|
|
31
31
|
import { serverManifoldRuntime } from "./serverManifoldRuntime.js";
|
|
32
32
|
import { createLocalId } from "./localIds.js";
|
|
33
33
|
import { invalidSketchSweepProfile, sketchSweepShapeFromRequest } from "./sketchSweepRequest.js";
|
|
34
|
-
import {
|
|
34
|
+
import { sketchExtrusionShapeFromRequest } from "./sketchExtrusionRequest.js";
|
|
35
|
+
import { findSketchOutput, recomputeSketchOutput, replaySingleAllEdgeTreatment } from "./parametricRecompute.js";
|
|
35
36
|
import { boundsForShapes, footprintCenter, shapeBounds } from "./shapeBounds.js";
|
|
36
37
|
import { REQUESTABLE_SHAPE_KINDS, shapeFromRequest, shapeWithRequestedChanges, SHAPE_PARAMETERS, } from "./shapeRequests.js";
|
|
37
38
|
import { canonicalizeShape, cleanNearZero, cleanRotationDegrees, cloneWorkplaneShapeTreeWithFreshIds, withHoleMode, } from "./workplaneShapes.js";
|
|
38
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
|
+
}
|
|
39
81
|
/**
|
|
40
82
|
* Carry face metadata across a parameter edit only when a face has a unique
|
|
41
83
|
* counterpart. Curved faces may change their sampled normal as their defining
|
|
@@ -330,13 +372,33 @@ export function createEngine(store) {
|
|
|
330
372
|
});
|
|
331
373
|
}
|
|
332
374
|
/** The shared tail of `treat` and `shell`: run the kernel, store the result on the shape. */
|
|
333
|
-
async function cadTreatment(projectId, shapeId, run) {
|
|
375
|
+
async function cadTreatment(projectId, shapeId, operation, run) {
|
|
334
376
|
return mutating(projectId, async (document) => {
|
|
335
377
|
const found = oneShape(document, shapeId);
|
|
336
378
|
if (!found.ok)
|
|
337
379
|
return found;
|
|
338
380
|
if (found.value.locked)
|
|
339
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
|
+
}
|
|
340
402
|
let changed;
|
|
341
403
|
try {
|
|
342
404
|
changed = await run(found.value);
|
|
@@ -412,11 +474,15 @@ export function createEngine(store) {
|
|
|
412
474
|
changeShape: (projectId, shapeId, changes) => mutating(projectId, async (document) => {
|
|
413
475
|
const byOutputId = oneShape(document, shapeId);
|
|
414
476
|
// A persisted sketch source is an input node, not a renderable shape.
|
|
415
|
-
// When a caller edits that node's profile
|
|
416
|
-
// 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
|
|
417
479
|
// output keeps its own stable object ID; SKF export derives the source
|
|
418
480
|
// node from the synchronized profile on that output.
|
|
419
|
-
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
|
|
420
486
|
? findSketchOutput(document.shapes, shapeId)
|
|
421
487
|
: [];
|
|
422
488
|
const sourceSketchTarget = !byOutputId.ok && sourceOutputs.length === 1;
|
|
@@ -429,7 +495,9 @@ export function createEngine(store) {
|
|
|
429
495
|
return found;
|
|
430
496
|
const existing = found.value;
|
|
431
497
|
if (sourceSketchTarget) {
|
|
432
|
-
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)));
|
|
433
501
|
if (outputFields.length > 0) {
|
|
434
502
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: outputFields, because: "not_this_kind" });
|
|
435
503
|
}
|
|
@@ -440,8 +508,48 @@ export function createEngine(store) {
|
|
|
440
508
|
if (existing.locked && changes.locked !== false)
|
|
441
509
|
return failed({ reason: "shape_locked", ids: [shapeId] });
|
|
442
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;
|
|
443
516
|
const isSketchSweep = existing.sketchOperation === "sweep" && Boolean(existing.sketchProfile);
|
|
444
|
-
|
|
517
|
+
const isSketchExtrusion = existing.sketchOperation === "extrude" && Boolean(existing.sketchProfile);
|
|
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) {
|
|
445
553
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: ["sketchProfile"], because: "no_sweep" });
|
|
446
554
|
}
|
|
447
555
|
const sweptShape = isRevolveShape(existing);
|
|
@@ -454,18 +562,100 @@ export function createEngine(store) {
|
|
|
454
562
|
// leave a thread's pitch readable but not writable.
|
|
455
563
|
const revolveChanges = sweptShape ? REVOLVE_CHANGE_FIELDS.filter((field) => changes[field] !== undefined) : [];
|
|
456
564
|
let swept = existing;
|
|
457
|
-
if (replacingSketchProfile &&
|
|
458
|
-
|
|
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;
|
|
569
|
+
try {
|
|
570
|
+
const rebuilt = await sketchExtrusionShapeFromRequest({
|
|
571
|
+
id: existing.id,
|
|
572
|
+
sketchSourceId: existing.sketchSourceId ?? `sketch/${existing.id}`,
|
|
573
|
+
name: existing.name,
|
|
574
|
+
color: existing.color,
|
|
575
|
+
hole: existing.hole,
|
|
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),
|
|
581
|
+
rotation: existing.rotation,
|
|
582
|
+
rotationX: existing.rotationX,
|
|
583
|
+
rotationZ: existing.rotationZ,
|
|
584
|
+
});
|
|
585
|
+
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, ...sourceMetadata } = existing;
|
|
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
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
return failed({ reason: "sweep_failed", message: error instanceof Error ? error.message : "The sketch extrusion could not be rebuilt." });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if ((replacingSketchProfile || faceDraftChanged) && isSketchSweep) {
|
|
647
|
+
const profileToBuild = replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile;
|
|
648
|
+
const invalidProfile = replacingSketchProfile ? invalidSketchSweepProfile(profileToBuild) : undefined;
|
|
459
649
|
if (invalidProfile)
|
|
460
650
|
return failed({ reason: "invalid_shape_request", message: invalidProfile });
|
|
461
|
-
const frame = existing.cadBrepFrame;
|
|
651
|
+
const frame = existing.faceDraftSourceFrame ?? existing.cadBrepFrame;
|
|
462
652
|
try {
|
|
463
653
|
const rebuilt = await sketchSweepShapeFromRequest({
|
|
464
654
|
id: existing.id,
|
|
465
655
|
name: existing.name,
|
|
466
656
|
color: existing.color,
|
|
467
657
|
hole: existing.hole,
|
|
468
|
-
profile:
|
|
658
|
+
profile: profileToBuild,
|
|
469
659
|
x: existing.x - (frame?.x ?? 0),
|
|
470
660
|
z: existing.z - (frame?.z ?? 0),
|
|
471
661
|
elevation: (existing.elevation ?? 0) - (frame?.elevation ?? 0),
|
|
@@ -487,14 +677,47 @@ export function createEngine(store) {
|
|
|
487
677
|
resized.size = Math.max(resized.width, resized.depth);
|
|
488
678
|
const refreshedFaceIds = (await shapeFaces(rebuilt))
|
|
489
679
|
.map((face) => `face_${createLocalId(`${existing.id}-${face.index}`)}`);
|
|
490
|
-
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, ...sourceMetadata } = existing;
|
|
491
|
-
|
|
680
|
+
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, faceDraft: _faceDraft, faceDraftSourceFrame: _faceDraftSourceFrame, ...sourceMetadata } = existing;
|
|
681
|
+
const rebuiltSweep = {
|
|
492
682
|
...sourceMetadata,
|
|
493
683
|
...resized,
|
|
494
684
|
id: existing.id,
|
|
495
685
|
sketchSourceId: existing.sketchSourceId ?? rebuilt.sketchSourceId,
|
|
496
686
|
faceIds: refreshedFaceIds,
|
|
687
|
+
faceDraft: undefined,
|
|
688
|
+
faceDraftSourceFrame: undefined,
|
|
497
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
|
+
}
|
|
498
721
|
}
|
|
499
722
|
catch (error) {
|
|
500
723
|
return failed({
|
|
@@ -503,7 +726,88 @@ export function createEngine(store) {
|
|
|
503
726
|
});
|
|
504
727
|
}
|
|
505
728
|
}
|
|
506
|
-
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) {
|
|
507
811
|
const outline = changes.profile ?? changes.outline ?? outlineFromProfile(existing.sketchProfile);
|
|
508
812
|
if (!outline) {
|
|
509
813
|
return failed({
|
|
@@ -518,10 +822,11 @@ export function createEngine(store) {
|
|
|
518
822
|
return failed({ reason: "invalid_shape_request", message: parsed.error });
|
|
519
823
|
try {
|
|
520
824
|
const runtime = await serverManifoldRuntime();
|
|
521
|
-
|
|
825
|
+
const rebuilt = rebuiltRevolveShape(runtime, existing, {
|
|
522
826
|
profile: parsed.profile,
|
|
523
827
|
settings: requestedRevolveSettings(existing.sketchRevolve, changes),
|
|
524
828
|
});
|
|
829
|
+
swept = await replaySingleAllEdgeTreatment(existing, rebuilt);
|
|
525
830
|
}
|
|
526
831
|
catch (error) {
|
|
527
832
|
return failed({
|
|
@@ -535,6 +840,8 @@ export function createEngine(store) {
|
|
|
535
840
|
const sweepFields = [
|
|
536
841
|
...(sweptShape ? REVOLVE_CHANGE_FIELDS : REVOLVE_ONLY_FIELDS),
|
|
537
842
|
"sketchProfile",
|
|
843
|
+
"draftPrismAngle",
|
|
844
|
+
"faceDraft",
|
|
538
845
|
];
|
|
539
846
|
// Shape summaries expose the source ID for identity; echoing a full
|
|
540
847
|
// summary into PATCH must not let callers replace that identity.
|
|
@@ -779,12 +1086,12 @@ export function createEngine(store) {
|
|
|
779
1086
|
return failed(cadFailure(error));
|
|
780
1087
|
}
|
|
781
1088
|
}),
|
|
782
|
-
faces: (projectId, shapeId) => reading(projectId, async (document) => {
|
|
1089
|
+
faces: (projectId, shapeId, topology) => reading(projectId, async (document) => {
|
|
783
1090
|
const found = oneShape(document, shapeId);
|
|
784
1091
|
if (!found.ok)
|
|
785
1092
|
return found;
|
|
786
1093
|
try {
|
|
787
|
-
return ok(await shapeFaces(found.value));
|
|
1094
|
+
return ok(await shapeFaces(found.value, topology === "exact" ? { topology } : undefined));
|
|
788
1095
|
}
|
|
789
1096
|
catch (error) {
|
|
790
1097
|
return failed(cadFailure(error));
|
|
@@ -841,8 +1148,44 @@ export function createEngine(store) {
|
|
|
841
1148
|
return failed(cadFailure(error));
|
|
842
1149
|
}
|
|
843
1150
|
}),
|
|
844
|
-
treat: (projectId, shapeId, treatment) => cadTreatment(projectId, shapeId,
|
|
845
|
-
|
|
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
|
+
}),
|
|
846
1189
|
mirrorBaked: (projectId, ids, axis) => mutating(projectId, async (document) => {
|
|
847
1190
|
if (ids.length < 1)
|
|
848
1191
|
return failed({ reason: "selection_too_small", needed: 1, given: 0 });
|
|
@@ -872,7 +1215,7 @@ export function createEngine(store) {
|
|
|
872
1215
|
revision: await writeShapes(projectId, document, shapes),
|
|
873
1216
|
});
|
|
874
1217
|
}),
|
|
875
|
-
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)))),
|
|
876
1219
|
};
|
|
877
1220
|
}
|
|
878
1221
|
//# sourceMappingURL=engine.js.map
|