@constructorfleet/sketchforge-engine 1.19.0 → 1.21.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 +43 -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 +482 -29
- package/dist/engine.js.map +1 -1
- package/dist/faceDraftSegmentLineage.d.ts +45 -0
- package/dist/faceDraftSegmentLineage.d.ts.map +1 -0
- package/dist/faceDraftSegmentLineage.js +200 -0
- package/dist/faceDraftSegmentLineage.js.map +1 -0
- 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 +152 -0
- package/dist/parametricRecompute.js.map +1 -1
- package/dist/revolveRequests.d.ts +12 -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 +210 -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,9 @@
|
|
|
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
|
+
import { draftFaceForSketchSegment, legacyDraftSegmentForShape, sketchSegmentForDraftFace } from "./faceDraftSegmentLineage.js";
|
|
19
|
+
import { cadSketchRegions, cadSketchSweepPaths } from "./sketchCadProfile.js";
|
|
18
20
|
import { applyShell, shapeFaces, } from "./serverCadShell.js";
|
|
19
21
|
import { measureBetween as measureBetweenEntities, shapeMeasurements, } from "./serverCadMeasure.js";
|
|
20
22
|
import { shapeProperties } from "./serverCadProperties.js";
|
|
@@ -32,11 +34,69 @@ import { serverManifoldRuntime } from "./serverManifoldRuntime.js";
|
|
|
32
34
|
import { createLocalId } from "./localIds.js";
|
|
33
35
|
import { invalidSketchSweepProfile, sketchSweepShapeFromRequest } from "./sketchSweepRequest.js";
|
|
34
36
|
import { sketchExtrusionShapeFromRequest } from "./sketchExtrusionRequest.js";
|
|
35
|
-
import { findSketchOutput, recomputeSketchOutput } from "./parametricRecompute.js";
|
|
37
|
+
import { findSketchOutput, recomputeSketchOutput, replaySingleAllEdgeTreatment } from "./parametricRecompute.js";
|
|
36
38
|
import { boundsForShapes, footprintCenter, shapeBounds } from "./shapeBounds.js";
|
|
37
39
|
import { REQUESTABLE_SHAPE_KINDS, shapeFromRequest, shapeWithRequestedChanges, SHAPE_PARAMETERS, } from "./shapeRequests.js";
|
|
38
40
|
import { canonicalizeShape, cleanNearZero, cleanRotationDegrees, cloneWorkplaneShapeTreeWithFreshIds, withHoleMode, } from "./workplaneShapes.js";
|
|
39
41
|
const UNIQUE_CURVED_FACE_SURFACES = new Set(["cone", "sphere", "torus", "cylinder"]);
|
|
42
|
+
const FACE_DRAFT_AXES = new Set(["x", "y", "z", "-x", "-y", "-z"]);
|
|
43
|
+
function faceDraftInput(value) {
|
|
44
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
45
|
+
return null;
|
|
46
|
+
const draft = value;
|
|
47
|
+
if (typeof draft.sourceFaceId !== "string" || !draft.sourceFaceId.trim()
|
|
48
|
+
|| typeof draft.angle !== "number" || !Number.isFinite(draft.angle) || draft.angle === 0 || Math.abs(draft.angle) >= 45
|
|
49
|
+
|| typeof draft.pullDirection !== "string" || !FACE_DRAFT_AXES.has(draft.pullDirection))
|
|
50
|
+
return null;
|
|
51
|
+
return {
|
|
52
|
+
sourceFaceId: draft.sourceFaceId,
|
|
53
|
+
angle: draft.angle,
|
|
54
|
+
pullDirection: draft.pullDirection,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function isSingleLoopSketchExtrusion(shape) {
|
|
58
|
+
if (shape.sketchOperation !== "extrude" || !shape.sketchProfile)
|
|
59
|
+
return false;
|
|
60
|
+
const regions = cadSketchRegions(shape.sketchProfile);
|
|
61
|
+
return regions.length === 1 && regions[0]?.holes.length === 0;
|
|
62
|
+
}
|
|
63
|
+
function isSingleSectionSketchSweep(shape) {
|
|
64
|
+
if (shape.sketchOperation !== "sweep" || !shape.sketchProfile)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
const { sections, spine } = cadSketchSweepPaths(shape.sketchProfile);
|
|
68
|
+
return sections.length === 1 && sections[0]?.holes.length === 0 && spine.steps.length > 0;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function revolveSourceScale(shape) {
|
|
75
|
+
const mesh = shape.importedMesh;
|
|
76
|
+
const ratio = (drawn, natural) => natural && natural > 0 ? drawn / natural : 1;
|
|
77
|
+
return {
|
|
78
|
+
width: ratio(shape.width, mesh?.baseWidth),
|
|
79
|
+
depth: ratio(shape.depth, mesh?.baseDepth),
|
|
80
|
+
height: ratio(shape.height, mesh?.baseHeight),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function resizeRebuiltRevolve(shape, scale) {
|
|
84
|
+
const mesh = shape.importedMesh;
|
|
85
|
+
if (!mesh)
|
|
86
|
+
return shape;
|
|
87
|
+
const width = Math.max(0.01, (mesh.baseWidth ?? shape.width) * scale.width);
|
|
88
|
+
const depth = Math.max(0.01, (mesh.baseDepth ?? shape.depth) * scale.depth);
|
|
89
|
+
const height = Math.max(0.01, (mesh.baseHeight ?? shape.height) * scale.height);
|
|
90
|
+
return { ...shape, width, depth, height, size: Math.max(width, depth) };
|
|
91
|
+
}
|
|
92
|
+
function faceNormalMatches(left, right) {
|
|
93
|
+
return Math.hypot(left.x - right.x, left.y - right.y, left.z - right.z) <= 1e-5;
|
|
94
|
+
}
|
|
95
|
+
async function findUniqueDraftFace(shape, normal) {
|
|
96
|
+
const matches = (await shapeFaces(shape)).filter((face) => face.surfaceType === "plane" && face.normal
|
|
97
|
+
&& faceNormalMatches(face.normal, normal));
|
|
98
|
+
return matches.length === 1 ? matches[0] : null;
|
|
99
|
+
}
|
|
40
100
|
/**
|
|
41
101
|
* Carry face metadata across a parameter edit only when a face has a unique
|
|
42
102
|
* counterpart. Curved faces may change their sampled normal as their defining
|
|
@@ -331,13 +391,42 @@ export function createEngine(store) {
|
|
|
331
391
|
});
|
|
332
392
|
}
|
|
333
393
|
/** The shared tail of `treat` and `shell`: run the kernel, store the result on the shape. */
|
|
334
|
-
async function cadTreatment(projectId, shapeId, run) {
|
|
394
|
+
async function cadTreatment(projectId, shapeId, operation, run) {
|
|
335
395
|
return mutating(projectId, async (document) => {
|
|
336
396
|
const found = oneShape(document, shapeId);
|
|
337
397
|
if (!found.ok)
|
|
338
398
|
return found;
|
|
339
399
|
if (found.value.locked)
|
|
340
400
|
return failed({ reason: "shape_locked", ids: [shapeId] });
|
|
401
|
+
const hasEdgeTreatments = (found.value.edgeTreatments?.length ?? 0) > 0
|
|
402
|
+
|| (found.value.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
403
|
+
const hasDraftPrism = found.value.draftPrismAngle !== undefined;
|
|
404
|
+
const replayablePrismTreatment = (operation === "all-edge-treatment" || operation === "all-edge-chamfer-treatment")
|
|
405
|
+
&& !hasEdgeTreatments
|
|
406
|
+
&& found.value.sketchOperation === "extrude"
|
|
407
|
+
&& Boolean(found.value.sketchProfile)
|
|
408
|
+
&& hasDraftPrism;
|
|
409
|
+
const replayableFaceDraftTreatment = (operation === "all-edge-treatment" || operation === "all-edge-chamfer-treatment")
|
|
410
|
+
&& !hasEdgeTreatments
|
|
411
|
+
&& found.value.faceDraft !== undefined
|
|
412
|
+
&& (isPlainFullTurnSketchRevolve(found.value)
|
|
413
|
+
|| isSingleLoopSketchExtrusion(found.value)
|
|
414
|
+
&& (operation === "all-edge-treatment" || operation === "all-edge-chamfer-treatment")
|
|
415
|
+
|| operation === "all-edge-treatment" && isSingleSectionSketchSweep(found.value)
|
|
416
|
+
|| operation === "all-edge-chamfer-treatment" && isSingleSectionSketchSweep(found.value));
|
|
417
|
+
const incompatible = found.value.faceDraft !== undefined && !replayableFaceDraftTreatment
|
|
418
|
+
|| operation === "edge-treatment" && hasDraftPrism
|
|
419
|
+
|| (operation === "all-edge-treatment" || operation === "all-edge-chamfer-treatment")
|
|
420
|
+
&& hasDraftPrism && !replayablePrismTreatment
|
|
421
|
+
|| operation === "prism-draft" && hasEdgeTreatments
|
|
422
|
+
|| operation === "shell" && (found.value.draftPrismAngle !== undefined || hasEdgeTreatments);
|
|
423
|
+
if (incompatible) {
|
|
424
|
+
return failed({
|
|
425
|
+
reason: "cad_refused",
|
|
426
|
+
message: "Face drafts support one symmetric 45-degree all-edge chamfer or all-edge fillet on a single-loop sketch extrusion, one symmetric 45-degree all-edge chamfer or all-edge fillet on a single-section sketch sweep, or one symmetric all-edge chamfer or all-edge fillet on a plain full-turn sketch revolve; prism drafts support one all-edge treatment only on a sketch extrusion. Other draft and treatment combinations, and shelling with persisted features, are refused.",
|
|
427
|
+
status: 422,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
341
430
|
let changed;
|
|
342
431
|
try {
|
|
343
432
|
changed = await run(found.value);
|
|
@@ -413,11 +502,15 @@ export function createEngine(store) {
|
|
|
413
502
|
changeShape: (projectId, shapeId, changes) => mutating(projectId, async (document) => {
|
|
414
503
|
const byOutputId = oneShape(document, shapeId);
|
|
415
504
|
// 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
|
|
505
|
+
// When a caller edits that node's profile or revolve settings, resolve
|
|
506
|
+
// its dependent output and rebuild it before the single project write. The
|
|
418
507
|
// output keeps its own stable object ID; SKF export derives the source
|
|
419
508
|
// node from the synchronized profile on that output.
|
|
420
|
-
const
|
|
509
|
+
const sourceFeatureInputChanged = changes.sketchProfile !== undefined
|
|
510
|
+
|| changes.draftPrismAngle !== undefined
|
|
511
|
+
|| Object.hasOwn(changes, "faceDraft")
|
|
512
|
+
|| REVOLVE_CHANGE_FIELDS.some((field) => changes[field] !== undefined);
|
|
513
|
+
const sourceOutputs = !byOutputId.ok && sourceFeatureInputChanged
|
|
421
514
|
? findSketchOutput(document.shapes, shapeId)
|
|
422
515
|
: [];
|
|
423
516
|
const sourceSketchTarget = !byOutputId.ok && sourceOutputs.length === 1;
|
|
@@ -430,7 +523,9 @@ export function createEngine(store) {
|
|
|
430
523
|
return found;
|
|
431
524
|
const existing = found.value;
|
|
432
525
|
if (sourceSketchTarget) {
|
|
433
|
-
const
|
|
526
|
+
const isRevolveSource = isRevolveShape(existing);
|
|
527
|
+
const outputFields = Object.keys(changes).filter((field) => field !== "sketchProfile" && field !== "draftPrismAngle" && field !== "faceDraft"
|
|
528
|
+
&& !(isRevolveSource && REVOLVE_CHANGE_FIELDS.includes(field)));
|
|
434
529
|
if (outputFields.length > 0) {
|
|
435
530
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: outputFields, because: "not_this_kind" });
|
|
436
531
|
}
|
|
@@ -441,9 +536,80 @@ export function createEngine(store) {
|
|
|
441
536
|
if (existing.locked && changes.locked !== false)
|
|
442
537
|
return failed({ reason: "shape_locked", ids: [shapeId] });
|
|
443
538
|
const replacingSketchProfile = changes.sketchProfile !== undefined;
|
|
539
|
+
const faceDraftChanged = Object.hasOwn(changes, "faceDraft");
|
|
540
|
+
const clearingFaceDraft = faceDraftChanged && changes.faceDraft === null && existing.faceDraft !== undefined;
|
|
541
|
+
const requestedFaceDraft = faceDraftChanged
|
|
542
|
+
? changes.faceDraft === null ? undefined : faceDraftInput(changes.faceDraft)
|
|
543
|
+
: existing.faceDraft;
|
|
444
544
|
const isSketchSweep = existing.sketchOperation === "sweep" && Boolean(existing.sketchProfile);
|
|
445
545
|
const isSketchExtrusion = existing.sketchOperation === "extrude" && Boolean(existing.sketchProfile);
|
|
446
|
-
|
|
546
|
+
const isSketchRevolve = isRevolveShape(existing);
|
|
547
|
+
if (faceDraftChanged && changes.faceDraft !== null && !requestedFaceDraft) {
|
|
548
|
+
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." });
|
|
549
|
+
}
|
|
550
|
+
const isExactSketchRevolve = isPlainFullTurnSketchRevolve(existing);
|
|
551
|
+
if ((faceDraftChanged || (replacingSketchProfile && existing.faceDraft))
|
|
552
|
+
&& !isSketchExtrusion && !isSketchSweep && !isExactSketchRevolve) {
|
|
553
|
+
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: ["faceDraft"], because: "not_this_kind" });
|
|
554
|
+
}
|
|
555
|
+
const hasExistingTreatment = (existing.edgeTreatments?.length ?? 0) > 0
|
|
556
|
+
|| (existing.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
557
|
+
const canReplayFaceDraftTreatment = existing.faceDraft !== undefined
|
|
558
|
+
&& existing.draftPrismAngle === undefined
|
|
559
|
+
&& (!faceDraftChanged || requestedFaceDraft !== undefined && requestedFaceDraft !== null
|
|
560
|
+
&& requestedFaceDraft.sourceFaceId === existing.faceDraft.sourceFaceId)
|
|
561
|
+
&& (isPlainFullTurnSketchRevolve(existing)
|
|
562
|
+
|| isSingleLoopSketchExtrusion(existing)
|
|
563
|
+
&& (existing.edgeTreatments?.[0]?.kind === "fillet" || existing.edgeTreatments?.[0]?.kind === "chamfer")
|
|
564
|
+
|| isSingleSectionSketchSweep(existing)
|
|
565
|
+
&& (existing.edgeTreatments?.[0]?.kind === "fillet" || existing.edgeTreatments?.[0]?.kind === "chamfer"))
|
|
566
|
+
&& existing.edgeTreatments?.length === 1
|
|
567
|
+
&& (existing.edgeTreatments[0]?.kind === "chamfer" || existing.edgeTreatments[0]?.kind === "fillet")
|
|
568
|
+
&& existing.edgeTreatments[0]?.allEdges === true
|
|
569
|
+
&& existing.edgeTreatmentHistory?.length === 1
|
|
570
|
+
&& existing.edgeTreatmentHistory[0]?.feature.kind === existing.edgeTreatments[0]?.kind
|
|
571
|
+
&& existing.edgeTreatmentHistory[0]?.feature.allEdges === true
|
|
572
|
+
&& (existing.edgeTreatments[0]?.kind !== "chamfer"
|
|
573
|
+
|| Math.abs((existing.edgeTreatments[0].chamferAngle ?? 45) - 45) < 0.001
|
|
574
|
+
&& Math.abs((existing.edgeTreatmentHistory[0]?.feature.chamferAngle ?? 45) - 45) < 0.001);
|
|
575
|
+
if (requestedFaceDraft
|
|
576
|
+
&& (existing.draftPrismAngle !== undefined || hasExistingTreatment && !canReplayFaceDraftTreatment)) {
|
|
577
|
+
return failed({ reason: "invalid_shape_request", message: "This face draft cannot be combined with the requested persisted draft or treatment history." });
|
|
578
|
+
}
|
|
579
|
+
const canReselectUntreatedExtrusionFace = isSingleLoopSketchExtrusion(existing)
|
|
580
|
+
&& existing.draftPrismAngle === undefined
|
|
581
|
+
&& !hasExistingTreatment;
|
|
582
|
+
if (faceDraftChanged && existing.faceDraft && requestedFaceDraft
|
|
583
|
+
&& requestedFaceDraft.sourceFaceId !== existing.faceDraft.sourceFaceId
|
|
584
|
+
&& replacingSketchProfile) {
|
|
585
|
+
return failed({ reason: "invalid_shape_request", message: "Change the sketch profile and face draft selection in separate edits." });
|
|
586
|
+
}
|
|
587
|
+
if (faceDraftChanged && existing.faceDraft && requestedFaceDraft
|
|
588
|
+
&& requestedFaceDraft.sourceFaceId !== existing.faceDraft.sourceFaceId) {
|
|
589
|
+
const currentOutputFaces = await shapeFaces(existing);
|
|
590
|
+
if (!canReselectUntreatedExtrusionFace
|
|
591
|
+
|| !existing.faceDraftSourceFaceIds?.includes(requestedFaceDraft.sourceFaceId)
|
|
592
|
+
|| currentOutputFaces.filter((face) => face.id === requestedFaceDraft.sourceFaceId && face.surfaceType === "plane").length !== 1) {
|
|
593
|
+
return failed({ reason: "invalid_shape_request", message: "Clear the existing face draft before selecting a different source face." });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const draftPrismChanged = changes.draftPrismAngle !== undefined;
|
|
597
|
+
const requestedDraftPrismAngle = typeof changes.draftPrismAngle === "number" ? changes.draftPrismAngle : undefined;
|
|
598
|
+
if (draftPrismChanged && (!isSketchExtrusion || requestedDraftPrismAngle === undefined || !Number.isFinite(requestedDraftPrismAngle)
|
|
599
|
+
|| requestedDraftPrismAngle === 0 || Math.abs(requestedDraftPrismAngle) >= 45)) {
|
|
600
|
+
return failed({
|
|
601
|
+
reason: "invalid_shape_request",
|
|
602
|
+
message: "A draft prism angle must be non-zero and between -45 and 45 degrees on a sketch extrusion.",
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (draftPrismChanged && requestedFaceDraft) {
|
|
606
|
+
return failed({ reason: "invalid_shape_request", message: "Face drafts cannot be combined with prism drafts yet." });
|
|
607
|
+
}
|
|
608
|
+
if (draftPrismChanged && existing.draftPrismAngle === undefined
|
|
609
|
+
&& ((existing.edgeTreatments?.length ?? 0) > 0 || (existing.edgeTreatmentHistory?.length ?? 0) > 0)) {
|
|
610
|
+
return failed({ reason: "invalid_shape_request", message: "A prism draft cannot be added after an edge treatment." });
|
|
611
|
+
}
|
|
612
|
+
if (replacingSketchProfile && !isSketchSweep && !isSketchExtrusion && !isSketchRevolve) {
|
|
447
613
|
return failed({ reason: "unapplicable_fields", kind: existing.kind, fields: ["sketchProfile"], because: "no_sweep" });
|
|
448
614
|
}
|
|
449
615
|
const sweptShape = isRevolveShape(existing);
|
|
@@ -456,8 +622,10 @@ export function createEngine(store) {
|
|
|
456
622
|
// leave a thread's pitch readable but not writable.
|
|
457
623
|
const revolveChanges = sweptShape ? REVOLVE_CHANGE_FIELDS.filter((field) => changes[field] !== undefined) : [];
|
|
458
624
|
let swept = existing;
|
|
459
|
-
if (replacingSketchProfile && isSketchExtrusion)
|
|
460
|
-
|
|
625
|
+
if ((replacingSketchProfile && isSketchExtrusion) || (draftPrismChanged && isSketchExtrusion)
|
|
626
|
+
|| (faceDraftChanged && isSketchExtrusion)) {
|
|
627
|
+
const sourceFrame = existing.faceDraftSourceFrame ?? existing.draftPrismSourceFrame ?? existing.cadBrepFrame;
|
|
628
|
+
const sourceHeight = existing.draftPrismSourceHeight ?? existing.faceDraftSourceFrame?.height ?? existing.height;
|
|
461
629
|
try {
|
|
462
630
|
const rebuilt = await sketchExtrusionShapeFromRequest({
|
|
463
631
|
id: existing.id,
|
|
@@ -465,34 +633,148 @@ export function createEngine(store) {
|
|
|
465
633
|
name: existing.name,
|
|
466
634
|
color: existing.color,
|
|
467
635
|
hole: existing.hole,
|
|
468
|
-
profile: changes.sketchProfile,
|
|
469
|
-
height:
|
|
470
|
-
x: existing.x - (
|
|
471
|
-
z: existing.z - (
|
|
472
|
-
elevation: (existing.elevation ?? 0) - (
|
|
636
|
+
profile: replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile,
|
|
637
|
+
height: sourceHeight,
|
|
638
|
+
x: existing.x - (sourceFrame?.x ?? 0),
|
|
639
|
+
z: existing.z - (sourceFrame?.z ?? 0),
|
|
640
|
+
elevation: (existing.elevation ?? 0) - (sourceFrame?.elevation ?? 0),
|
|
473
641
|
rotation: existing.rotation,
|
|
474
642
|
rotationX: existing.rotationX,
|
|
475
643
|
rotationZ: existing.rotationZ,
|
|
476
644
|
});
|
|
477
645
|
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, ...sourceMetadata } = existing;
|
|
478
|
-
|
|
646
|
+
const draftPrismAngle = draftPrismChanged ? requestedDraftPrismAngle : existing.draftPrismAngle;
|
|
647
|
+
const rebuiltSource = {
|
|
648
|
+
...sourceMetadata,
|
|
649
|
+
...rebuilt,
|
|
650
|
+
id: existing.id,
|
|
651
|
+
sketchSourceId: existing.sketchSourceId ?? rebuilt.sketchSourceId,
|
|
652
|
+
faceDraft: undefined,
|
|
653
|
+
faceDraftSourceFrame: undefined,
|
|
654
|
+
faceDraftSourceFaceIds: undefined,
|
|
655
|
+
faceDraftSourceSegmentId: undefined,
|
|
656
|
+
...(faceDraftChanged && !replacingSketchProfile && !existing.faceDraft && !hasExistingTreatment
|
|
657
|
+
&& existing.draftPrismAngle === undefined && existing.faceIds?.length === rebuilt.faceIds?.length
|
|
658
|
+
? { faceIds: existing.faceIds }
|
|
659
|
+
: {}),
|
|
660
|
+
...(!replacingSketchProfile && existing.faceDraft && existing.faceDraftSourceFaceIds?.length === rebuilt.faceIds?.length
|
|
661
|
+
? { faceIds: existing.faceDraftSourceFaceIds }
|
|
662
|
+
: {}),
|
|
663
|
+
...(draftPrismAngle === undefined ? {} : {
|
|
664
|
+
draftPrismSourceHeight: sourceHeight,
|
|
665
|
+
...(sourceFrame ? { draftPrismSourceFrame: sourceFrame } : {}),
|
|
666
|
+
}),
|
|
667
|
+
};
|
|
668
|
+
const hasRetainedTreatments = (existing.edgeTreatments?.length ?? 0) > 0
|
|
669
|
+
|| (existing.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
670
|
+
const replayDraftAndTreatment = hasRetainedTreatments
|
|
671
|
+
&& draftPrismAngle !== undefined
|
|
672
|
+
&& (replacingSketchProfile || clearingFaceDraft || draftPrismChanged);
|
|
673
|
+
const replayFaceDraftAndTreatment = hasRetainedTreatments && faceDraftChanged && requestedFaceDraft !== undefined;
|
|
674
|
+
const replayOriginal = clearingFaceDraft
|
|
675
|
+
? { ...existing, faceDraft: undefined }
|
|
676
|
+
: replayFaceDraftAndTreatment && existing.faceDraft
|
|
677
|
+
? { ...existing, faceDraft: { ...existing.faceDraft, ...requestedFaceDraft } }
|
|
678
|
+
: existing;
|
|
679
|
+
const replayedSource = (replacingSketchProfile || clearingFaceDraft || draftPrismChanged || replayFaceDraftAndTreatment)
|
|
680
|
+
&& hasRetainedTreatments
|
|
681
|
+
? await replaySingleAllEdgeTreatment(replayOriginal, rebuiltSource, draftPrismChanged ? requestedDraftPrismAngle : undefined)
|
|
682
|
+
: rebuiltSource;
|
|
683
|
+
if (requestedFaceDraft && replayedSource.faceDraft) {
|
|
684
|
+
swept = replayedSource;
|
|
685
|
+
}
|
|
686
|
+
else if (requestedFaceDraft) {
|
|
687
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
688
|
+
? existing.faceDraft.sourceFaceNormal
|
|
689
|
+
: undefined;
|
|
690
|
+
const selectedOnPrior = priorReference ? undefined
|
|
691
|
+
: (await shapeFaces(existing, isExactSketchRevolve ? { topology: "exact" } : undefined))
|
|
692
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
693
|
+
const isReselection = existing.faceDraft !== undefined
|
|
694
|
+
&& requestedFaceDraft.sourceFaceId !== existing.faceDraft.sourceFaceId;
|
|
695
|
+
const exactReselectedSourceFace = isReselection
|
|
696
|
+
? (await shapeFaces(replayedSource)).find((face) => face.id === requestedFaceDraft.sourceFaceId)
|
|
697
|
+
: undefined;
|
|
698
|
+
const exactSelectedSourceFace = !replacingSketchProfile && !existing.faceDraft
|
|
699
|
+
? (await shapeFaces(replayedSource)).find((face) => face.id === requestedFaceDraft.sourceFaceId)
|
|
700
|
+
: undefined;
|
|
701
|
+
const legacySegmentMatch = replacingSketchProfile && existing.faceDraft && !existing.faceDraftSourceSegmentId
|
|
702
|
+
? await legacyDraftSegmentForShape(existing)
|
|
703
|
+
: undefined;
|
|
704
|
+
if (legacySegmentMatch?.status === "ambiguous") {
|
|
705
|
+
throw new Error("The saved face draft does not identify one source sketch segment.");
|
|
706
|
+
}
|
|
707
|
+
const retainedSourceSegmentId = existing.faceDraftSourceSegmentId
|
|
708
|
+
?? (legacySegmentMatch?.status === "matched" ? legacySegmentMatch.segmentId : undefined);
|
|
709
|
+
const retainedSegmentFace = replacingSketchProfile && retainedSourceSegmentId
|
|
710
|
+
? await draftFaceForSketchSegment(replayedSource, retainedSourceSegmentId)
|
|
711
|
+
: undefined;
|
|
712
|
+
if (retainedSegmentFace && retainedSegmentFace.status !== "matched") {
|
|
713
|
+
throw new Error("The selected sketch segment no longer maps to exactly one extrusion side face.");
|
|
714
|
+
}
|
|
715
|
+
let currentFaceId = retainedSegmentFace?.status === "matched"
|
|
716
|
+
? retainedSegmentFace.faceId
|
|
717
|
+
: exactReselectedSourceFace?.surfaceType === "plane" ? exactReselectedSourceFace.id
|
|
718
|
+
: exactSelectedSourceFace?.surfaceType === "plane" ? exactSelectedSourceFace.id : undefined;
|
|
719
|
+
const sourceFaceNormal = retainedSegmentFace?.status === "matched"
|
|
720
|
+
? retainedSegmentFace.normal
|
|
721
|
+
: exactReselectedSourceFace?.normal ?? exactSelectedSourceFace?.normal ?? priorReference ?? selectedOnPrior?.normal;
|
|
722
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
723
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
724
|
+
}
|
|
725
|
+
if (!currentFaceId)
|
|
726
|
+
currentFaceId = (await findUniqueDraftFace(replayedSource, sourceFaceNormal))?.id;
|
|
727
|
+
if (!currentFaceId)
|
|
728
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
729
|
+
const segmentMatch = retainedSegmentFace?.status === "matched"
|
|
730
|
+
? retainedSegmentFace
|
|
731
|
+
: await sketchSegmentForDraftFace(replayedSource, currentFaceId);
|
|
732
|
+
if (segmentMatch.status === "ambiguous") {
|
|
733
|
+
throw new Error("The selected lateral face matches multiple sketch segments.");
|
|
734
|
+
}
|
|
735
|
+
if (segmentMatch.status === "missing") {
|
|
736
|
+
throw new Error("The selected extrusion side face has no exact sketch-segment boundary match.");
|
|
737
|
+
}
|
|
738
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
739
|
+
const result = await applySketchFaceDraft(replayedSource, {
|
|
740
|
+
...requestedFaceDraft,
|
|
741
|
+
sourceFaceId: currentFaceId,
|
|
742
|
+
});
|
|
743
|
+
swept = {
|
|
744
|
+
...shapeWithCadResult(replayedSource, result),
|
|
745
|
+
faceDraft: { ...faceDraft, sourceFaceId: currentFaceId },
|
|
746
|
+
faceDraftSourceFaceIds: replayedSource.faceIds,
|
|
747
|
+
faceDraftSourceSegmentId: segmentMatch.status === "matched" ? segmentMatch.segmentId : undefined,
|
|
748
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
else if (draftPrismAngle !== undefined && !replayDraftAndTreatment) {
|
|
752
|
+
swept = {
|
|
753
|
+
...shapeWithCadResult(replayedSource, await applyDraftPrism(replayedSource, { angle: draftPrismAngle })),
|
|
754
|
+
draftPrismAngle,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
swept = replayedSource;
|
|
759
|
+
}
|
|
479
760
|
}
|
|
480
761
|
catch (error) {
|
|
481
762
|
return failed({ reason: "sweep_failed", message: error instanceof Error ? error.message : "The sketch extrusion could not be rebuilt." });
|
|
482
763
|
}
|
|
483
764
|
}
|
|
484
|
-
if (replacingSketchProfile && isSketchSweep) {
|
|
485
|
-
const
|
|
765
|
+
if ((replacingSketchProfile || faceDraftChanged) && isSketchSweep) {
|
|
766
|
+
const profileToBuild = replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile;
|
|
767
|
+
const invalidProfile = replacingSketchProfile ? invalidSketchSweepProfile(profileToBuild) : undefined;
|
|
486
768
|
if (invalidProfile)
|
|
487
769
|
return failed({ reason: "invalid_shape_request", message: invalidProfile });
|
|
488
|
-
const frame = existing.cadBrepFrame;
|
|
770
|
+
const frame = existing.faceDraftSourceFrame ?? existing.cadBrepFrame;
|
|
489
771
|
try {
|
|
490
772
|
const rebuilt = await sketchSweepShapeFromRequest({
|
|
491
773
|
id: existing.id,
|
|
492
774
|
name: existing.name,
|
|
493
775
|
color: existing.color,
|
|
494
776
|
hole: existing.hole,
|
|
495
|
-
profile:
|
|
777
|
+
profile: profileToBuild,
|
|
496
778
|
x: existing.x - (frame?.x ?? 0),
|
|
497
779
|
z: existing.z - (frame?.z ?? 0),
|
|
498
780
|
elevation: (existing.elevation ?? 0) - (frame?.elevation ?? 0),
|
|
@@ -514,14 +796,61 @@ export function createEngine(store) {
|
|
|
514
796
|
resized.size = Math.max(resized.width, resized.depth);
|
|
515
797
|
const refreshedFaceIds = (await shapeFaces(rebuilt))
|
|
516
798
|
.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
|
-
|
|
799
|
+
const { faceIds: _oldFaceIds, faceColors: _faceColors, edgeTreatments: _edgeTreatments, edgeTreatmentHistory: _edgeTreatmentHistory, cadDisplayEdges: _cadDisplayEdges, cadDisplayEdgesVersion: _cadDisplayEdgesVersion, faceDraft: _faceDraft, faceDraftSourceFrame: _faceDraftSourceFrame, ...sourceMetadata } = existing;
|
|
800
|
+
const rebuiltSweep = {
|
|
519
801
|
...sourceMetadata,
|
|
520
802
|
...resized,
|
|
521
803
|
id: existing.id,
|
|
522
804
|
sketchSourceId: existing.sketchSourceId ?? rebuilt.sketchSourceId,
|
|
523
805
|
faceIds: refreshedFaceIds,
|
|
806
|
+
faceDraft: undefined,
|
|
807
|
+
faceDraftSourceFrame: undefined,
|
|
524
808
|
};
|
|
809
|
+
const hasRetainedTreatments = (existing.edgeTreatments?.length ?? 0) > 0
|
|
810
|
+
|| (existing.edgeTreatmentHistory?.length ?? 0) > 0;
|
|
811
|
+
const replayFaceDraftAndTreatment = hasRetainedTreatments && faceDraftChanged && requestedFaceDraft !== undefined;
|
|
812
|
+
const replayOriginal = clearingFaceDraft
|
|
813
|
+
? { ...existing, faceDraft: undefined }
|
|
814
|
+
: replayFaceDraftAndTreatment && existing.faceDraft
|
|
815
|
+
? { ...existing, faceDraft: { ...existing.faceDraft, ...requestedFaceDraft } }
|
|
816
|
+
: existing;
|
|
817
|
+
const replayedSource = (replacingSketchProfile || clearingFaceDraft || replayFaceDraftAndTreatment) && hasRetainedTreatments
|
|
818
|
+
? await replaySingleAllEdgeTreatment(replayOriginal, rebuiltSweep)
|
|
819
|
+
: rebuiltSweep;
|
|
820
|
+
if (requestedFaceDraft && replayedSource.faceDraft) {
|
|
821
|
+
// `replaySingleAllEdgeTreatment` has already rebuilt the retained
|
|
822
|
+
// draft before its treatment; applying the draft again would try
|
|
823
|
+
// to operate on post-fillet topology and make source edits fail.
|
|
824
|
+
swept = replayedSource;
|
|
825
|
+
}
|
|
826
|
+
else if (requestedFaceDraft) {
|
|
827
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
828
|
+
? existing.faceDraft.sourceFaceNormal
|
|
829
|
+
: undefined;
|
|
830
|
+
const selectedOnPrior = priorReference ? undefined
|
|
831
|
+
: (await shapeFaces(existing, isExactSketchRevolve ? { topology: "exact" } : undefined))
|
|
832
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
833
|
+
const sourceFaceNormal = priorReference ?? selectedOnPrior?.normal;
|
|
834
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
835
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
836
|
+
}
|
|
837
|
+
const currentFace = await findUniqueDraftFace(replayedSource, sourceFaceNormal);
|
|
838
|
+
if (!currentFace)
|
|
839
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
840
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
841
|
+
const result = await applySketchFaceDraft(replayedSource, {
|
|
842
|
+
...requestedFaceDraft,
|
|
843
|
+
sourceFaceId: currentFace.id,
|
|
844
|
+
});
|
|
845
|
+
swept = {
|
|
846
|
+
...shapeWithCadResult(replayedSource, result),
|
|
847
|
+
faceDraft,
|
|
848
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
swept = replayedSource;
|
|
853
|
+
}
|
|
525
854
|
}
|
|
526
855
|
catch (error) {
|
|
527
856
|
return failed({
|
|
@@ -530,7 +859,89 @@ export function createEngine(store) {
|
|
|
530
859
|
});
|
|
531
860
|
}
|
|
532
861
|
}
|
|
533
|
-
if (
|
|
862
|
+
if ((faceDraftChanged || (replacingSketchProfile && existing.faceDraft)
|
|
863
|
+
|| (revolveChanges.length > 0 && existing.faceDraft)) && isSketchRevolve) {
|
|
864
|
+
try {
|
|
865
|
+
const runtime = await serverManifoldRuntime();
|
|
866
|
+
const sourceFrame = existing.faceDraftSourceFrame ?? existing.cadBrepFrame;
|
|
867
|
+
const rebuildSeed = sourceFrame ? {
|
|
868
|
+
...existing,
|
|
869
|
+
x: sourceFrame.x,
|
|
870
|
+
z: sourceFrame.z,
|
|
871
|
+
elevation: sourceFrame.elevation,
|
|
872
|
+
} : existing;
|
|
873
|
+
const rebuiltAtNaturalScale = rebuiltRevolveShape(runtime, rebuildSeed, {
|
|
874
|
+
profile: (replacingSketchProfile ? changes.sketchProfile : existing.sketchProfile),
|
|
875
|
+
settings: revolveChanges.length > 0
|
|
876
|
+
? requestedRevolveSettings(existing.sketchRevolve, changes)
|
|
877
|
+
: existing.sketchRevolve,
|
|
878
|
+
});
|
|
879
|
+
const sourceScale = existing.faceDraftSourceScale ?? revolveSourceScale(existing);
|
|
880
|
+
const rebuilt = resizeRebuiltRevolve(rebuiltAtNaturalScale, sourceScale);
|
|
881
|
+
if (requestedFaceDraft) {
|
|
882
|
+
const exactBase = shapeWithCadResult(rebuilt, await exactPlainSketchRevolveResult(rebuilt));
|
|
883
|
+
const priorReference = existing.faceDraft?.sourceFaceId === requestedFaceDraft.sourceFaceId
|
|
884
|
+
? existing.faceDraft.sourceFaceNormal
|
|
885
|
+
: undefined;
|
|
886
|
+
const selectedOnPrior = priorReference ? undefined
|
|
887
|
+
: (await shapeFaces(existing, { topology: "exact" }))
|
|
888
|
+
.find((face) => face.id === requestedFaceDraft.sourceFaceId);
|
|
889
|
+
const sourceFaceNormal = priorReference ?? selectedOnPrior?.normal;
|
|
890
|
+
if (!sourceFaceNormal || selectedOnPrior && selectedOnPrior.surfaceType !== "plane") {
|
|
891
|
+
throw new Error("The selected source face has no stable planar reference.");
|
|
892
|
+
}
|
|
893
|
+
const currentFace = await findUniqueDraftFace(exactBase, sourceFaceNormal);
|
|
894
|
+
if (!currentFace)
|
|
895
|
+
throw new Error("The selected source face is missing or ambiguous after the sketch edit.");
|
|
896
|
+
const faceDraft = { ...requestedFaceDraft, sourceFaceNormal };
|
|
897
|
+
const result = await applySketchFaceDraft(exactBase, {
|
|
898
|
+
...requestedFaceDraft,
|
|
899
|
+
sourceFaceId: currentFace.id,
|
|
900
|
+
});
|
|
901
|
+
swept = {
|
|
902
|
+
...shapeWithCadResult(exactBase, result),
|
|
903
|
+
faceDraft,
|
|
904
|
+
faceDraftSourceFrame: existing.faceDraftSourceFrame ?? existing.cadBrepFrame ?? exactBase.cadBrepFrame,
|
|
905
|
+
faceDraftSourceScale: sourceScale,
|
|
906
|
+
};
|
|
907
|
+
swept = await replaySingleAllEdgeTreatment(existing, swept);
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
const clearedDraft = {
|
|
911
|
+
...rebuilt,
|
|
912
|
+
faceDraft: undefined,
|
|
913
|
+
faceDraftSourceFrame: undefined,
|
|
914
|
+
faceDraftSourceScale: undefined,
|
|
915
|
+
};
|
|
916
|
+
swept = clearingFaceDraft
|
|
917
|
+
? await replaySingleAllEdgeTreatment({ ...existing, faceDraft: undefined }, clearedDraft)
|
|
918
|
+
: clearedDraft;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
catch (error) {
|
|
922
|
+
return failed({
|
|
923
|
+
reason: "sweep_failed",
|
|
924
|
+
message: error instanceof Error ? error.message : "The sketch revolve could not be rebuilt.",
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
else if (replacingSketchProfile && isSketchRevolve) {
|
|
929
|
+
try {
|
|
930
|
+
const runtime = await serverManifoldRuntime();
|
|
931
|
+
const rebuilt = rebuiltRevolveShape(runtime, existing, {
|
|
932
|
+
profile: changes.sketchProfile,
|
|
933
|
+
settings: existing.sketchRevolve,
|
|
934
|
+
});
|
|
935
|
+
swept = await replaySingleAllEdgeTreatment(existing, rebuilt);
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
return failed({
|
|
939
|
+
reason: "sweep_failed",
|
|
940
|
+
message: error instanceof Error ? error.message : "The sketch revolve could not be rebuilt.",
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (revolveChanges.length > 0 && sweptShape && !existing.faceDraft) {
|
|
534
945
|
const outline = changes.profile ?? changes.outline ?? outlineFromProfile(existing.sketchProfile);
|
|
535
946
|
if (!outline) {
|
|
536
947
|
return failed({
|
|
@@ -545,10 +956,11 @@ export function createEngine(store) {
|
|
|
545
956
|
return failed({ reason: "invalid_shape_request", message: parsed.error });
|
|
546
957
|
try {
|
|
547
958
|
const runtime = await serverManifoldRuntime();
|
|
548
|
-
|
|
959
|
+
const rebuilt = rebuiltRevolveShape(runtime, existing, {
|
|
549
960
|
profile: parsed.profile,
|
|
550
961
|
settings: requestedRevolveSettings(existing.sketchRevolve, changes),
|
|
551
962
|
});
|
|
963
|
+
swept = await replaySingleAllEdgeTreatment(existing, rebuilt);
|
|
552
964
|
}
|
|
553
965
|
catch (error) {
|
|
554
966
|
return failed({
|
|
@@ -562,6 +974,8 @@ export function createEngine(store) {
|
|
|
562
974
|
const sweepFields = [
|
|
563
975
|
...(sweptShape ? REVOLVE_CHANGE_FIELDS : REVOLVE_ONLY_FIELDS),
|
|
564
976
|
"sketchProfile",
|
|
977
|
+
"draftPrismAngle",
|
|
978
|
+
"faceDraft",
|
|
565
979
|
];
|
|
566
980
|
// Shape summaries expose the source ID for identity; echoing a full
|
|
567
981
|
// summary into PATCH must not let callers replace that identity.
|
|
@@ -806,12 +1220,12 @@ export function createEngine(store) {
|
|
|
806
1220
|
return failed(cadFailure(error));
|
|
807
1221
|
}
|
|
808
1222
|
}),
|
|
809
|
-
faces: (projectId, shapeId) => reading(projectId, async (document) => {
|
|
1223
|
+
faces: (projectId, shapeId, topology) => reading(projectId, async (document) => {
|
|
810
1224
|
const found = oneShape(document, shapeId);
|
|
811
1225
|
if (!found.ok)
|
|
812
1226
|
return found;
|
|
813
1227
|
try {
|
|
814
|
-
return ok(await shapeFaces(found.value));
|
|
1228
|
+
return ok(await shapeFaces(found.value, topology === "exact" ? { topology } : undefined));
|
|
815
1229
|
}
|
|
816
1230
|
catch (error) {
|
|
817
1231
|
return failed(cadFailure(error));
|
|
@@ -868,8 +1282,47 @@ export function createEngine(store) {
|
|
|
868
1282
|
return failed(cadFailure(error));
|
|
869
1283
|
}
|
|
870
1284
|
}),
|
|
871
|
-
treat: (projectId, shapeId, treatment) => cadTreatment(projectId, shapeId,
|
|
872
|
-
|
|
1285
|
+
treat: (projectId, shapeId, treatment) => cadTreatment(projectId, shapeId, (treatment.edges === undefined || treatment.edges.length === 0)
|
|
1286
|
+
&& treatment.kind === "chamfer" && (treatment.angle === undefined || Math.abs(treatment.angle - 45) < 0.001)
|
|
1287
|
+
? "all-edge-chamfer-treatment"
|
|
1288
|
+
: (treatment.edges === undefined || treatment.edges.length === 0)
|
|
1289
|
+
&& treatment.kind === "fillet"
|
|
1290
|
+
? "all-edge-treatment"
|
|
1291
|
+
: "edge-treatment", async (shape) => {
|
|
1292
|
+
const treated = shapeWithCadResult(shape, await applyEdgeTreatment(shape, treatment));
|
|
1293
|
+
if (shape.draftPrismAngle === undefined && shape.faceDraft === undefined)
|
|
1294
|
+
return treated;
|
|
1295
|
+
const feature = {
|
|
1296
|
+
kind: treatment.kind,
|
|
1297
|
+
amount: treatment.amount,
|
|
1298
|
+
edgeCount: await allEdgeTreatmentEdgeCount(shape, treatment.kind),
|
|
1299
|
+
allEdges: true,
|
|
1300
|
+
...(treatment.kind === "chamfer" ? { chamferAngle: treatment.angle ?? 45 } : {}),
|
|
1301
|
+
};
|
|
1302
|
+
const history = shape.edgeTreatmentHistory ?? [];
|
|
1303
|
+
return {
|
|
1304
|
+
...treated,
|
|
1305
|
+
edgeTreatments: [...(shape.edgeTreatments ?? []), feature],
|
|
1306
|
+
edgeTreatmentHistory: [
|
|
1307
|
+
...history,
|
|
1308
|
+
{
|
|
1309
|
+
id: createLocalId("edge-history"),
|
|
1310
|
+
createdAt: Date.now(),
|
|
1311
|
+
feature,
|
|
1312
|
+
before: structuredClone(shape),
|
|
1313
|
+
},
|
|
1314
|
+
],
|
|
1315
|
+
};
|
|
1316
|
+
}),
|
|
1317
|
+
draftPrism: (projectId, shapeId, angle) => cadTreatment(projectId, shapeId, "prism-draft", async (shape) => {
|
|
1318
|
+
const sourceFrame = shape.draftPrismSourceFrame ?? shape.cadBrepFrame;
|
|
1319
|
+
return {
|
|
1320
|
+
...shapeWithCadResult(shape, await applyDraftPrism(shape, { angle })),
|
|
1321
|
+
draftPrismAngle: angle,
|
|
1322
|
+
draftPrismSourceHeight: shape.draftPrismSourceHeight ?? shape.height,
|
|
1323
|
+
...(sourceFrame ? { draftPrismSourceFrame: sourceFrame } : {}),
|
|
1324
|
+
};
|
|
1325
|
+
}),
|
|
873
1326
|
mirrorBaked: (projectId, ids, axis) => mutating(projectId, async (document) => {
|
|
874
1327
|
if (ids.length < 1)
|
|
875
1328
|
return failed({ reason: "selection_too_small", needed: 1, given: 0 });
|
|
@@ -899,7 +1352,7 @@ export function createEngine(store) {
|
|
|
899
1352
|
revision: await writeShapes(projectId, document, shapes),
|
|
900
1353
|
});
|
|
901
1354
|
}),
|
|
902
|
-
shell: (projectId, shapeId, request) => cadTreatment(projectId, shapeId, async (shape) => (shapeWithCadResult(shape, await applyShell(shape, request)))),
|
|
1355
|
+
shell: (projectId, shapeId, request) => cadTreatment(projectId, shapeId, "shell", async (shape) => (shapeWithCadResult(shape, await applyShell(shape, request)))),
|
|
903
1356
|
};
|
|
904
1357
|
}
|
|
905
1358
|
//# sourceMappingURL=engine.js.map
|