@reekon-tools/boldr-cad 1.4.0 → 1.6.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 +13 -3
- package/dist/app/edit.d.ts +3 -3
- package/dist/app/edit.js +26 -1
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +6 -8
- package/dist/app/state.js +0 -1
- package/dist/app/state.js.map +1 -1
- package/dist/data/sync.d.ts +1 -1
- package/dist/data/sync.js +7 -2
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +8 -0
- package/dist/editor/actions.js +255 -181
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/elevation.d.ts +4 -1
- package/dist/editor/elevation.js +16 -17
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/gestures.d.ts +21 -0
- package/dist/editor/gestures.js +92 -14
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keys.js +4 -3
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/laser.d.ts +4 -2
- package/dist/editor/laser.js +4 -2
- package/dist/editor/laser.js.map +1 -1
- package/dist/editor/linkActions.d.ts +49 -12
- package/dist/editor/linkActions.js +164 -35
- package/dist/editor/linkActions.js.map +1 -1
- package/dist/editor/links.d.ts +13 -0
- package/dist/editor/links.js +7 -1
- package/dist/editor/links.js.map +1 -1
- package/dist/editor/mode.d.ts +2 -1
- package/dist/editor/mode.js +8 -5
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/project.js +1 -2
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/scaleGate.d.ts +13 -0
- package/dist/editor/scaleGate.js +27 -0
- package/dist/editor/scaleGate.js.map +1 -0
- package/dist/editor/selection.js +13 -9
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/tooltips.js +10 -0
- package/dist/editor/tooltips.js.map +1 -1
- package/dist/editor/view3d.d.ts +4 -1
- package/dist/editor/view3d.js +6 -2
- package/dist/editor/view3d.js.map +1 -1
- package/dist/export/pdf.d.ts +5 -2
- package/dist/export/pdf.js +12 -8
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +1 -1
- package/dist/export/plan.js +14 -9
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/model/constraints.js +2 -0
- package/dist/model/constraints.js.map +1 -1
- package/dist/model/demo.d.ts +1 -0
- package/dist/model/demo.js +2 -1
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +1 -1
- package/dist/model/elevation.js +3 -3
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/geom.d.ts +2 -0
- package/dist/model/geom.js +11 -0
- package/dist/model/geom.js.map +1 -1
- package/dist/model/history.js +4 -1
- package/dist/model/history.js.map +1 -1
- package/dist/model/links.d.ts +10 -2
- package/dist/model/links.js +15 -3
- package/dist/model/links.js.map +1 -1
- package/dist/model/measurement.d.ts +2 -2
- package/dist/model/measurement.js +3 -3
- package/dist/model/measurement.js.map +1 -1
- package/dist/model/openings.d.ts +10 -0
- package/dist/model/openings.js +18 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +37 -2
- package/dist/model/planGeom.js +72 -9
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +5 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/rooms.d.ts +18 -5
- package/dist/model/rooms.js +178 -73
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/scale.d.ts +30 -0
- package/dist/model/scale.js +123 -0
- package/dist/model/scale.js.map +1 -0
- package/dist/model/settings.d.ts +0 -1
- package/dist/model/settings.js +1 -1
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +26 -2
- package/dist/model/sketch.js +77 -22
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/stroke.js +2 -2
- package/dist/model/trim.d.ts +54 -0
- package/dist/model/trim.js +219 -0
- package/dist/model/trim.js.map +1 -0
- package/dist/model/types.d.ts +62 -9
- package/dist/react/BottomBar.d.ts +2 -1
- package/dist/react/BottomBar.js +144 -46
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/CadEditor.js +18 -9
- package/dist/react/CadEditor.js.map +1 -1
- package/dist/react/Drawer.js +6 -4
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.js.map +1 -1
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/MarkupPanel.js +1 -1
- package/dist/react/PlanCanvas.native.js +54 -7
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/PlanCanvas.web.js +17 -5
- package/dist/react/PlanCanvas.web.js.map +1 -1
- package/dist/react/Popovers.js +14 -3
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.js +1 -1
- package/dist/react/ShapeMenu.js +3 -2
- package/dist/react/ShapeMenu.js.map +1 -1
- package/dist/react/Sheet.d.ts +2 -2
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/ToolTip.js +3 -2
- package/dist/react/ToolTip.js.map +1 -1
- package/dist/react/TopBar.js +4 -3
- package/dist/react/TopBar.js.map +1 -1
- package/dist/react/barScroll.d.ts +23 -0
- package/dist/react/barScroll.js +47 -0
- package/dist/react/barScroll.js.map +1 -0
- package/dist/react/corners.d.ts +6 -2
- package/dist/react/corners.js.map +1 -1
- package/dist/react/editorBox.d.ts +8 -0
- package/dist/react/editorBox.js +10 -0
- package/dist/react/editorBox.js.map +1 -0
- package/dist/react/host.d.ts +1 -1
- package/dist/react/host.js +1 -1
- package/dist/react/host.js.map +1 -1
- package/dist/react/motion.d.ts +1 -1
- package/dist/react/motion.js +1 -1
- package/dist/react/motion.js.map +1 -1
- package/dist/react/pageLock.native.d.ts +2 -0
- package/dist/react/pageLock.native.js +2 -0
- package/dist/react/pageLock.native.js.map +1 -1
- package/dist/react/pageLock.web.d.ts +6 -0
- package/dist/react/pageLock.web.js +12 -3
- package/dist/react/pageLock.web.js.map +1 -1
- package/dist/react/ports.js +8 -3
- package/dist/react/ports.js.map +1 -1
- package/dist/react/saving.d.ts +7 -1
- package/dist/react/saving.js +10 -2
- package/dist/react/saving.js.map +1 -1
- package/dist/react/store.d.ts +5 -2
- package/dist/react/store.js +4 -1
- package/dist/react/store.js.map +1 -1
- package/dist/render/draw.d.ts +21 -4
- package/dist/render/draw.js +203 -151
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.js +3 -3
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +6 -0
- package/dist/render/figmaIcons.js +16 -1
- package/dist/render/figmaIcons.js.map +1 -1
- package/dist/render/hit.d.ts +8 -1
- package/dist/render/hit.js +23 -4
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +27 -20
- package/dist/render/icons.js.map +1 -1
- package/dist/render/lens.d.ts +27 -0
- package/dist/render/lens.js +41 -0
- package/dist/render/lens.js.map +1 -0
- package/dist/render/linkTile.d.ts +20 -2
- package/dist/render/linkTile.js +476 -246
- package/dist/render/linkTile.js.map +1 -1
- package/dist/render/snap.d.ts +10 -1
- package/dist/render/snap.js +87 -15
- package/dist/render/snap.js.map +1 -1
- package/dist/render/svgPath.d.ts +29 -0
- package/dist/render/svgPath.js +114 -0
- package/dist/render/svgPath.js.map +1 -0
- package/dist/render/tileIcons.d.ts +18 -0
- package/dist/render/tileIcons.js +67 -0
- package/dist/render/tileIcons.js.map +1 -0
- package/dist/render/view.d.ts +18 -1
- package/dist/render/view.js +38 -6
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +8 -0
- package/dist/skia/SkiaDrawCtx.js +27 -5
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/solver/equations.d.ts +6 -0
- package/dist/solver/equations.js +53 -22
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.js +6 -1
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/tools/annotate.d.ts +0 -2
- package/dist/tools/annotate.js +1 -22
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.js +4 -0
- package/dist/tools/chamfer.js.map +1 -1
- package/dist/tools/dim.d.ts +3 -3
- package/dist/tools/dim.js +26 -65
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/fillet.js +4 -0
- package/dist/tools/fillet.js.map +1 -1
- package/dist/tools/freehand.js +1 -4
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/line.js +15 -11
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.d.ts +32 -4
- package/dist/tools/modify.js +253 -98
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/registry.d.ts +7 -0
- package/dist/tools/registry.js +14 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +30 -8
- package/dist/tools/select.js +165 -34
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +1 -1
- package/dist/tools/shapes.js +12 -5
- package/dist/tools/shapes.js.map +1 -1
- package/dist/view3d/build.js +9 -8
- package/dist/view3d/build.js.map +1 -1
- package/package.json +9 -9
- package/src/app/edit.ts +25 -1
- package/src/app/state.ts +6 -8
- package/src/data/sync.ts +7 -2
- package/src/editor/actions.ts +213 -122
- package/src/editor/elevation.ts +13 -16
- package/src/editor/gestures.ts +83 -13
- package/src/editor/keys.ts +3 -2
- package/src/editor/laser.ts +7 -4
- package/src/editor/linkActions.ts +169 -31
- package/src/editor/links.ts +17 -1
- package/src/editor/mode.ts +8 -5
- package/src/editor/project.ts +1 -2
- package/src/editor/scaleGate.ts +32 -0
- package/src/editor/selection.ts +13 -7
- package/src/editor/tooltips.ts +9 -0
- package/src/editor/view3d.ts +6 -2
- package/src/export/pdf.ts +7 -4
- package/src/export/plan.ts +14 -10
- package/src/index.ts +1 -0
- package/src/model/constraints.ts +1 -0
- package/src/model/demo.ts +2 -1
- package/src/model/elevation.ts +4 -4
- package/src/model/geom.ts +10 -0
- package/src/model/history.ts +4 -1
- package/src/model/links.ts +14 -3
- package/src/model/measurement.ts +3 -3
- package/src/model/openings.ts +18 -0
- package/src/model/planGeom.ts +70 -10
- package/src/model/project.ts +6 -2
- package/src/model/rooms.ts +151 -63
- package/src/model/scale.ts +107 -0
- package/src/model/settings.ts +1 -2
- package/src/model/sketch.ts +62 -11
- package/src/model/stroke.ts +2 -2
- package/src/model/trim.ts +188 -0
- package/src/model/types.ts +57 -9
- package/src/react/BottomBar.tsx +177 -52
- package/src/react/CadEditor.tsx +19 -9
- package/src/react/Drawer.tsx +17 -12
- package/src/react/Duck.tsx +3 -3
- package/src/react/KeypadSheet.tsx +2 -2
- package/src/react/MarkupPanel.tsx +1 -1
- package/src/react/PlanCanvas.native.tsx +60 -6
- package/src/react/PlanCanvas.web.tsx +17 -5
- package/src/react/Popovers.tsx +24 -9
- package/src/react/Rail.tsx +1 -1
- package/src/react/ShapeMenu.tsx +3 -2
- package/src/react/Sheet.tsx +3 -3
- package/src/react/ToolTip.tsx +3 -2
- package/src/react/TopBar.tsx +4 -3
- package/src/react/barScroll.ts +57 -0
- package/src/react/corners.ts +6 -3
- package/src/react/editorBox.ts +20 -0
- package/src/react/host.ts +1 -1
- package/src/react/motion.tsx +6 -6
- package/src/react/pageLock.native.ts +2 -0
- package/src/react/pageLock.web.ts +12 -3
- package/src/react/ports.ts +7 -2
- package/src/react/saving.ts +10 -2
- package/src/react/store.ts +5 -2
- package/src/render/draw.ts +169 -118
- package/src/render/elevation.ts +5 -4
- package/src/render/figmaIcons.ts +17 -1
- package/src/render/hit.ts +21 -4
- package/src/render/icons.ts +27 -20
- package/src/render/lens.ts +52 -0
- package/src/render/linkTile.ts +436 -190
- package/src/render/snap.ts +87 -17
- package/src/render/svgPath.ts +95 -0
- package/src/render/tileIcons.ts +75 -0
- package/src/render/view.ts +43 -7
- package/src/skia/SkiaDrawCtx.ts +26 -4
- package/src/solver/equations.ts +52 -22
- package/src/solver/gauss-newton.ts +4 -1
- package/src/styles.css +3 -2
- package/src/tools/annotate.ts +1 -18
- package/src/tools/chamfer.ts +3 -0
- package/src/tools/dim.ts +26 -52
- package/src/tools/fillet.ts +3 -0
- package/src/tools/freehand.ts +1 -3
- package/src/tools/line.ts +12 -11
- package/src/tools/modify.ts +210 -70
- package/src/tools/registry.ts +18 -1
- package/src/tools/select.ts +144 -36
- package/src/tools/shapes.ts +9 -4
- package/src/ui/ctx.ts +4 -3
- package/src/ui/drawer.ts +8 -2
- package/src/view3d/build.ts +11 -10
- package/dist/react/LaserTray.d.ts +0 -4
- package/dist/react/LaserTray.js +0 -26
- package/dist/react/LaserTray.js.map +0 -1
- package/src/react/LaserTray.tsx +0 -42
package/src/tools/modify.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import { afterEdit, holdFaces,
|
|
2
|
-
import { ed } from '../app/state';
|
|
3
|
-
import { splitFaces } from '../model/faces';
|
|
4
|
-
import { clamp, hyp,
|
|
1
|
+
import { afterEdit, holdFaces, solveSketch, tryAddConstraint, undo } from '../app/edit';
|
|
2
|
+
import { ed, type Preview } from '../app/state';
|
|
3
|
+
import { faceKey, splitFaces } from '../model/faces';
|
|
4
|
+
import { clamp, hyp, norm2, projT } from '../model/geom';
|
|
5
5
|
import { dropHistory, pushHistory } from '../model/history';
|
|
6
6
|
import { uid } from '../model/ids';
|
|
7
7
|
import { findFace, maybeRoom, roomGeom } from '../model/rooms';
|
|
8
|
-
import { addLine, addPoint } from '../model/sketch';
|
|
9
|
-
import { SK } from '../model/store';
|
|
10
|
-
import type
|
|
8
|
+
import { addCons, addLine, addPoint } from '../model/sketch';
|
|
9
|
+
import { replaceSK, SK } from '../model/store';
|
|
10
|
+
import { distTo, nearestOn, pieceAt, pieceKey, pieceShape, trimItemAt, type Cut, type TrimItem, type TrimKind, type TrimPiece } from '../model/trim';
|
|
11
|
+
import type { Constraint, Sketch, XY } from '../model/types';
|
|
11
12
|
import { hitAny } from '../render/hit';
|
|
12
13
|
import { outsideSide } from '../model/planGeom';
|
|
13
|
-
import {
|
|
14
|
+
import { TH } from '../render/theme';
|
|
15
|
+
import { mainT, requestDraw, requestOverlayDraw, S, s2w } from '../render/view';
|
|
14
16
|
import { placeTargets, snapSlide } from '../editor/elevation';
|
|
15
17
|
import { fmtArea, fmtLen } from '../units';
|
|
16
18
|
import type { PEv } from './registry';
|
|
17
19
|
import { openKeypad, toast } from '../editor/ports';
|
|
20
|
+
import { noScaleYet, waitForScale } from '../editor/scaleGate';
|
|
18
21
|
|
|
19
22
|
/** Hover helper: highlight only geometry of the given types. */
|
|
20
23
|
const hoverOnly = (types: string[]) => (ev: PEv) => {
|
|
@@ -23,82 +26,216 @@ const hoverOnly = (types: string[]) => (ev: PEv) => {
|
|
|
23
26
|
requestDraw();
|
|
24
27
|
};
|
|
25
28
|
|
|
29
|
+
/*
|
|
30
|
+
Trim (T; the rail, after Arc — on a phone, under Draw to shape): tap a piece of a line, wall, arc or circle and it
|
|
31
|
+
goes, cut back to where something crosses it or meets it on either side (model/trim.ts); drag across several and
|
|
32
|
+
each one the stroke crosses goes when it lifts. What's about to go is shown in red. One undo step a tap or a stroke.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** A piece a tap or a stroke took: where on what it's of (in the drawing), and the piece then. */
|
|
36
|
+
interface TrimMark { at: XY; pc: TrimPiece; key: string }
|
|
37
|
+
|
|
38
|
+
/** How near (inches) a point is to count as on what's trimmed, and cuts to count as one: a couple of pixels. */
|
|
39
|
+
const cutTol = (): number => Math.min(0.5, 2 / mainT().s);
|
|
40
|
+
|
|
41
|
+
/** The piece under screen point (sx, sy). */
|
|
42
|
+
function trimMarkAt(sx: number, sy: number, touch: boolean): TrimMark | null {
|
|
43
|
+
const [x, y] = s2w(sx, sy), sk = SK();
|
|
44
|
+
const it = trimItemAt(sk, { x, y }, (touch ? 20 : 10) / mainT().s);
|
|
45
|
+
if (!it) return null;
|
|
46
|
+
// (where on it: found again there as it's cut, on it or on a piece an earlier cut split off)
|
|
47
|
+
const at = nearestOn(sk, it, { x, y }), pc = pieceAt(sk, it, at, cutTol());
|
|
48
|
+
return { at, pc, key: pieceKey(pc) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** What's about to be cut away, drawn over the drawing in red. */
|
|
52
|
+
const trimPreview = (pcs: TrimPiece[]): Preview => (c, T) => {
|
|
53
|
+
const sk = SK();
|
|
54
|
+
c.save();
|
|
55
|
+
c.strokeStyle = TH.red; c.lineWidth = 4; c.lineCap = 'round'; c.globalAlpha = 0.85;
|
|
56
|
+
for (const pc of pcs) {
|
|
57
|
+
const g = pieceShape(sk, pc);
|
|
58
|
+
if (!g) continue;
|
|
59
|
+
c.beginPath();
|
|
60
|
+
if ('a' in g) { const [x1, y1] = S(T, g.a.x, g.a.y), [x2, y2] = S(T, g.b.x, g.b.y); c.moveTo(x1, y1); c.lineTo(x2, y2); }
|
|
61
|
+
else { const [x, y] = S(T, g.o.x, g.o.y); c.arc(x, y, g.r * T.s, -g.t0, -g.t1, true); }
|
|
62
|
+
c.stroke();
|
|
63
|
+
}
|
|
64
|
+
c.restore();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const freshTrim = () => ({ down: null as XY | null, last: null as XY | null, stroke: false, marks: [] as TrimMark[] });
|
|
68
|
+
|
|
26
69
|
export const trimTool = {
|
|
27
|
-
|
|
28
|
-
hover
|
|
29
|
-
|
|
30
|
-
|
|
70
|
+
st: freshTrim(),
|
|
71
|
+
reset() { this.st = freshTrim(); ed.hover = null; ed.preview = null; requestOverlayDraw(); },
|
|
72
|
+
show(marks: TrimMark[]) { ed.preview = marks.length ? trimPreview(marks.map(m => m.pc)) : null; requestOverlayDraw(); },
|
|
73
|
+
hover(ev: PEv) { const m = trimMarkAt(ev.sx, ev.sy, ev.touch); this.show(m ? [m] : []); },
|
|
74
|
+
down(ev: PEv) {
|
|
75
|
+
const m = trimMarkAt(ev.sx, ev.sy, ev.touch);
|
|
76
|
+
this.st = { down: { x: ev.sx, y: ev.sy }, last: { x: ev.sx, y: ev.sy }, stroke: false, marks: m ? [m] : [] };
|
|
77
|
+
this.show(this.st.marks);
|
|
78
|
+
},
|
|
79
|
+
move(ev: PEv) {
|
|
80
|
+
const st = this.st;
|
|
81
|
+
if (!st.down) { this.hover(ev); return; }
|
|
82
|
+
if (!st.stroke && hyp(ev.sx - st.down.x, ev.sy - st.down.y) < 12) return;
|
|
83
|
+
st.stroke = true;
|
|
84
|
+
// every few pixels of the way since the last move, so a quick stroke doesn't skip a short piece
|
|
85
|
+
const from = st.last!, n = Math.max(1, Math.ceil(hyp(ev.sx - from.x, ev.sy - from.y) / 6));
|
|
86
|
+
for (let i = 1; i <= n; i++) {
|
|
87
|
+
const m = trimMarkAt(from.x + ((ev.sx - from.x) * i) / n, from.y + ((ev.sy - from.y) * i) / n, ev.touch);
|
|
88
|
+
if (m && !st.marks.some(k => k.key === m.key)) st.marks.push(m);
|
|
89
|
+
}
|
|
90
|
+
st.last = { x: ev.sx, y: ev.sy };
|
|
91
|
+
this.show(st.marks);
|
|
92
|
+
},
|
|
31
93
|
up(ev: PEv) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
else if (h.type === 'circle') { pushHistory(); delete SK().circles[h.id]; afterEdit(); }
|
|
94
|
+
const st = this.st, tap = trimMarkAt(ev.sx, ev.sy, ev.touch);
|
|
95
|
+
const marks = st.stroke ? st.marks : tap ? [tap] : [];
|
|
96
|
+
this.reset();
|
|
97
|
+
if (marks.length) trimMarks(marks);
|
|
37
98
|
},
|
|
38
|
-
hint() { return 'Tap the
|
|
99
|
+
hint() { return 'Tap the piece to cut away, or drag across several. Each cuts back to where something crosses or meets it.'; },
|
|
39
100
|
};
|
|
40
101
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (id === lid) continue;
|
|
49
|
-
const o = sk.lines[id];
|
|
50
|
-
const r = segInter(A, B, sk.points[o.a], sk.points[o.b]);
|
|
51
|
-
if (r && r.t > 1e-6 && r.t < 1 - 1e-6) ts.push({ t: r.t, line: id });
|
|
52
|
-
}
|
|
53
|
-
for (const id in sk.circles) { const ci = sk.circles[id]; for (const t of lineCircleT(A, B, sk.points[ci.c], ci.r)) ts.push({ t, circle: id }); }
|
|
102
|
+
/**
|
|
103
|
+
* Cut away a tap's or a stroke's pieces, as one undo step. Each is found again as it's cut — on what it was of, or on a
|
|
104
|
+
* piece an earlier cut split off — so cutting one doesn't upset the next. If the drawing can't solve after, it's put
|
|
105
|
+
* back as it was and says so.
|
|
106
|
+
*/
|
|
107
|
+
export function trimMarks(marks: TrimMark[]): void {
|
|
108
|
+
const sk = SK(), was = JSON.stringify(sk), tol = cutTol(), near = 3 * tol;
|
|
54
109
|
pushHistory();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
110
|
+
const made: TrimItem[] = [];
|
|
111
|
+
let cut = 0, whole: TrimKind | null = null;
|
|
112
|
+
for (const m of marks) {
|
|
113
|
+
const it = [m.pc.item, ...made].find(c => distTo(sk, c, m.at) <= near);
|
|
114
|
+
if (!it) continue;
|
|
115
|
+
const pc = pieceAt(sk, it, m.at, tol);
|
|
116
|
+
if (!pc.lo && !pc.hi) whole = it.kind;
|
|
117
|
+
made.push(...trimPiece(sk, pc));
|
|
118
|
+
cut++;
|
|
119
|
+
}
|
|
120
|
+
if (!cut) { dropHistory(); return; }
|
|
121
|
+
if (!solveSketch().ok) {
|
|
122
|
+
replaceSK(JSON.parse(was) as Sketch);
|
|
123
|
+
dropHistory();
|
|
124
|
+
afterEdit();
|
|
125
|
+
toast('Can’t trim that — it conflicts with other constraints.', { warn: true });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
afterEdit();
|
|
129
|
+
// (a tap that took a whole thing: nothing crossed it, so there was nothing to cut back to)
|
|
130
|
+
if (marks.length === 1 && whole) toast((whole === 'line' ? 'Line' : whole === 'arc' ? 'Arc' : 'Circle') + ' removed — ' + (whole === 'circle' ? 'it takes two crossings to cut a piece out.' : 'nothing crossed it.'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cut piece `pc` away. A line or wall is shortened from an end, or split in two around a middle piece that goes (the
|
|
135
|
+
* two kept in line); an arc the same (the two one circle still); a circle opens into an arc round the rest; with no cut
|
|
136
|
+
* either side, the whole thing goes. Where a cut is a point already there, the new end is that point, so what meets
|
|
137
|
+
* there stays joined; a crossing makes a new point, held on what it crosses. Constraints carry over (product rule 9):
|
|
138
|
+
* a length, a midpoint or Equal on a cut line no longer say what they did and go. Edits the sketch only: the caller
|
|
139
|
+
* pushes history, solves and runs afterEdit. Returns what it made (a split's second piece).
|
|
140
|
+
*/
|
|
141
|
+
export function trimPiece(sk: Sketch, pc: TrimPiece): TrimItem[] {
|
|
142
|
+
const { item, lo, hi } = pc, made: TrimItem[] = [];
|
|
143
|
+
/** A cut's point: the one there, or a new one, held on what crosses there. */
|
|
144
|
+
const at = (c: Cut): string => {
|
|
145
|
+
if (c.pt) return c.pt;
|
|
146
|
+
const id = addPoint(sk, c.x, c.y);
|
|
147
|
+
if (c.line) addCons(sk, { type: 'online', p: id, l: c.line });
|
|
148
|
+
if (c.curve) addCons(sk, { type: 'oncircle', p: id, c: c.curve });
|
|
63
149
|
return id;
|
|
64
150
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
151
|
+
if (item.kind === 'line') trimLinePiece(sk, item.id, lo, hi, at, made);
|
|
152
|
+
else if (item.kind === 'arc') {
|
|
153
|
+
const a = sk.arcs[item.id];
|
|
154
|
+
if (!lo && !hi) delete sk.arcs[item.id];
|
|
155
|
+
else if (!lo) a.a = at(hi!);
|
|
156
|
+
else if (!hi) a.b = at(lo);
|
|
157
|
+
else {
|
|
158
|
+
const p1 = at(lo), p2 = at(hi), oldB = a.b, nid = uid('ar');
|
|
159
|
+
a.b = p1;
|
|
160
|
+
sk.arcs[nid] = { ...a, a: p2, b: oldB };
|
|
161
|
+
// one circle still; a tangent at its old end goes with the piece that ends there now
|
|
162
|
+
addCons(sk, { type: 'eqr', c1: item.id, c2: nid });
|
|
163
|
+
for (const c of Object.values(sk.cons)) if (c.type === 'tan' && c.c === item.id) { const l = sk.lines[c.l!]; if (l && (l.a === oldB || l.b === oldB)) c.c = nid; }
|
|
164
|
+
made.push({ kind: 'arc', id: nid });
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const ci = sk.circles[item.id];
|
|
168
|
+
if (!lo || !hi) delete sk.circles[item.id];
|
|
169
|
+
else {
|
|
170
|
+
// what's left runs from the cut after the piece round to the one before: an arc, with the circle's id, so what
|
|
171
|
+
// refers to it (its size, a tangent, Concentric) refers to the arc
|
|
172
|
+
const { c, constr, kind, thick } = ci, a = at(hi), b = at(lo);
|
|
173
|
+
delete sk.circles[item.id];
|
|
174
|
+
sk.arcs[item.id] = { c, a, b, ...(constr ? { constr } : {}), ...(kind ? { kind } : {}), ...(thick ? { thick } : {}) };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// a point held on a line or an arc that's that line's or arc's end now: an end is on it already
|
|
178
|
+
const ids = new Set([item.id, ...made.map(m => m.id)]);
|
|
179
|
+
for (const id in sk.cons) {
|
|
180
|
+
const c = sk.cons[id], l = c.l ? sk.lines[c.l] : undefined, a = c.c ? sk.arcs[c.c] : undefined;
|
|
181
|
+
if (c.type === 'online' && !c.face && l && ids.has(c.l!) && (l.a === c.p || l.b === c.p)) delete sk.cons[id];
|
|
182
|
+
else if (c.type === 'oncircle' && a && ids.has(c.c!) && (a.a === c.p || a.b === c.p)) delete sk.cons[id];
|
|
183
|
+
}
|
|
184
|
+
return made;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function trimLinePiece(sk: Sketch, lid: string, lo: Cut | null, hi: Cut | null, at: (c: Cut) => string, made: TrimItem[]): void {
|
|
188
|
+
const l = sk.lines[lid];
|
|
189
|
+
if (!lo && !hi) { delete sk.lines[lid]; return; }
|
|
190
|
+
const A = sk.points[l.a], B = sk.points[l.b], L = hyp(B.x - A.x, B.y - A.y), oldA = l.a, oldB = l.b;
|
|
191
|
+
// its length, its middle, Equal to another: they no longer say what they did
|
|
192
|
+
for (const id in sk.cons) {
|
|
193
|
+
const c = sk.cons[id];
|
|
194
|
+
if ((c.type === 'dist' && c.line === lid) || (c.type === 'mid' && c.l === lid) || (c.type === 'eq' && (c.l1 === lid || c.l2 === lid))) delete sk.cons[id];
|
|
195
|
+
}
|
|
196
|
+
if (lo && hi) {
|
|
197
|
+
// a middle piece: split at both cuts (splitLineAt carries constraints, spaces, doors and windows, and what's on its
|
|
198
|
+
// faces) and the piece between goes; the two left stay in line
|
|
199
|
+
const p1 = at(lo), p2 = at(hi), mid = splitLineAt(sk, lid, p1), rest = mid && splitLineAt(sk, mid, p2);
|
|
200
|
+
if (!mid || !rest) return;
|
|
201
|
+
delete sk.lines[mid];
|
|
68
202
|
for (const id in sk.cons) {
|
|
69
203
|
const c = sk.cons[id];
|
|
70
|
-
|
|
71
|
-
if (c.type === '
|
|
72
|
-
if (c.type === 'mid' && c.l === lid) delete sk.cons[id];
|
|
204
|
+
const across = (c.type === 'h' || c.type === 'v') && ((c.a === p1 && c.b === p2) || (c.a === p2 && c.b === p1));
|
|
205
|
+
if (across || (c.type === 'online' && c.p === p2 && c.l === lid)) delete sk.cons[id];
|
|
73
206
|
}
|
|
74
|
-
|
|
207
|
+
addCons(sk, { type: 'col', l1: lid, l2: rest });
|
|
208
|
+
made.push({ kind: 'line', id: rest });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const hv = (c: Constraint) => (c.type === 'h' || c.type === 'v') && ((c.a === oldA && c.b === oldB) || (c.a === oldB && c.b === oldA));
|
|
75
212
|
if (!lo) {
|
|
76
|
-
|
|
213
|
+
// from its start: it starts at the cut now, and what's measured from its start is measured from there
|
|
214
|
+
const p = at(hi!), s = hi!.at * L;
|
|
77
215
|
l.a = p;
|
|
78
|
-
|
|
79
|
-
for (const id in sk.openings) { const o = sk.openings[id]; if (o.line
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
l.b = p;
|
|
83
|
-
retarget(oldA, p);
|
|
216
|
+
for (const c of Object.values(sk.cons)) if (hv(c)) { c.a = p; c.b = oldB; }
|
|
217
|
+
for (const id in sk.openings) { const o = sk.openings[id]; if (o.line !== lid) continue; if (o.at < s) delete sk.openings[id]; else o.at -= s; }
|
|
218
|
+
shiftWallFace(sk, lid, -1, -s);
|
|
219
|
+
for (const side of [1, -1]) for (const e of sk.elev?.[lid + '|' + side] ?? []) { e.a[0] -= s; e.b[0] -= s; }
|
|
84
220
|
} else {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
for (const id in sk.
|
|
90
|
-
|
|
91
|
-
if ((c.type === 'par' || c.type === 'perp' || c.type === 'col') && (c.l1 === lid || c.l2 === lid)) copies.push(Object.assign({}, c, c.l1 === lid ? { l1: nid } : { l2: nid }));
|
|
92
|
-
if (isHV(c)) copies.push({ type: c.type, a: p2, b: oldB });
|
|
93
|
-
}
|
|
94
|
-
retarget(oldA, p1);
|
|
95
|
-
for (const c of copies) tryAddConstraint(c, { silent: true });
|
|
96
|
-
tryAddConstraint({ type: 'online', p: p2, l: lid }, { silent: true });
|
|
97
|
-
tryAddConstraint({ type: 'par', l1: lid, l2: nid }, { silent: true });
|
|
98
|
-
for (const id in sk.openings) { const o = sk.openings[id]; if (o.line === lid && o.at > hi.t * L) { o.line = nid; o.at -= hi.t * L; } }
|
|
221
|
+
// to its end: it ends at the cut now
|
|
222
|
+
const p = at(lo), s = lo.at * L;
|
|
223
|
+
l.b = p;
|
|
224
|
+
for (const c of Object.values(sk.cons)) if (hv(c)) { c.a = oldA; c.b = p; }
|
|
225
|
+
for (const id in sk.openings) { const o = sk.openings[id]; if (o.line === lid && o.at > s) delete sk.openings[id]; }
|
|
226
|
+
shiftWallFace(sk, lid, 1, s - L);
|
|
99
227
|
}
|
|
100
|
-
|
|
101
|
-
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* What's drawn on one face of wall `lid`, moved across it by dx: the face's x runs from the wall's start seen from side
|
|
232
|
+
* −1, and from its end seen from side 1 (see splitFaces), so a trim moving that end moves it.
|
|
233
|
+
*/
|
|
234
|
+
function shiftWallFace(sk: Sketch, lid: string, side: 1 | -1, dx: number): void {
|
|
235
|
+
const face = sk.faces?.[faceKey(lid, side)];
|
|
236
|
+
if (!face || !dx) return;
|
|
237
|
+
delete face.wall; // (its corners are recounted at its next placing)
|
|
238
|
+
for (const p of Object.values(face.points)) if (!p.anchor) p.x += dx;
|
|
102
239
|
}
|
|
103
240
|
|
|
104
241
|
export const offsetTool = {
|
|
@@ -118,6 +255,8 @@ export const offsetTool = {
|
|
|
118
255
|
};
|
|
119
256
|
|
|
120
257
|
export function offsetPrompt(lid: string, side: number): void {
|
|
258
|
+
// (a distance typed is real: nothing drawn says what it is in a sketch without its scale yet — editor/scaleGate.ts)
|
|
259
|
+
if (waitForScale()) return;
|
|
121
260
|
ed.sel = [{ type: 'line', id: lid }];
|
|
122
261
|
requestDraw();
|
|
123
262
|
openKeypad({ title: 'Offset distance', sub: 'Copies the line, kept parallel', initial: '', onDone: v => { if (!v) return; offsetLine(lid, v * side); } });
|
|
@@ -185,7 +324,8 @@ export const roomTool = {
|
|
|
185
324
|
if (!id) { dropHistory(); toast('That area is too small for a space.', { warn: true }); return; }
|
|
186
325
|
ed.sel = [];
|
|
187
326
|
afterEdit();
|
|
188
|
-
|
|
327
|
+
// (its area once the drawing has its scale: before, only a placeholder)
|
|
328
|
+
toast(sk.rooms[id].name + (noScaleYet() ? '' : ' — ' + fmtArea(roomGeom(sk, sk.rooms[id]).area)));
|
|
189
329
|
},
|
|
190
330
|
hint() { return 'Tap inside a closed shape to make a space. Area is measured to the inside face of walls.'; },
|
|
191
331
|
};
|
package/src/tools/registry.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { tryAddConstraint } from '../app/edit';
|
|
2
2
|
import { ed, type Snap } from '../app/state';
|
|
3
3
|
import { hyp } from '../model/geom';
|
|
4
|
+
import { edgeOf } from '../model/planGeom';
|
|
4
5
|
import { settings } from '../model/settings';
|
|
5
6
|
import { addPoint, WALL_THICK } from '../model/sketch';
|
|
6
7
|
import { SK } from '../model/store';
|
|
@@ -10,6 +11,7 @@ import { requestDraw, S, type Xform } from '../render/view';
|
|
|
10
11
|
import { fmtLen } from '../units';
|
|
11
12
|
import { onSessionReset } from '../model/session';
|
|
12
13
|
import { updateCtx, updateHint, updateRail } from '../editor/ports';
|
|
14
|
+
import { noScaleYet, toolWaitsForScale, waitForScale } from '../editor/scaleGate';
|
|
13
15
|
import type { KeyIn } from '../editor/keys';
|
|
14
16
|
import type { DrawCtx } from '../render/ctx';
|
|
15
17
|
import { TH } from '../render/theme';
|
|
@@ -36,6 +38,11 @@ export interface Tool {
|
|
|
36
38
|
hint?(): string;
|
|
37
39
|
/** something half-done that Esc should cancel first (a line in progress, a picked first point…) */
|
|
38
40
|
busy?(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* whether a finger or pencil pressed and moving is placing or dragging something, for a device's magnifier
|
|
43
|
+
* (render/lens.ts). Unset: whenever one is (a drawing tool places at it all the time).
|
|
44
|
+
*/
|
|
45
|
+
magnify?(): boolean;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export const tools: Record<string, Tool> = {};
|
|
@@ -53,6 +60,8 @@ export function cancelTool(): void {
|
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
export function setTool(t: string): void {
|
|
63
|
+
// (doors, windows, cabinets and offsets have real sizes: they wait for the page's first length — editor/scaleGate.ts)
|
|
64
|
+
if (toolWaitsForScale(t) && waitForScale()) return;
|
|
56
65
|
if (t === ed.tool && t !== 'select' && t !== 'aselect') { cancelTool(); updateCtx(); updateHint(); return; }
|
|
57
66
|
cancelTool();
|
|
58
67
|
ed.tool = t;
|
|
@@ -73,9 +82,16 @@ export function commitPoint(s: Snap): { id: string; cons: Constraint[] } {
|
|
|
73
82
|
else if (s.kind === 'online') cons.push({ type: 'online', p: id, l: s.ref! });
|
|
74
83
|
else if (s.kind === 'oncircle') cons.push({ type: 'oncircle', p: id, c: s.ref! });
|
|
75
84
|
else if (s.kind === 'inter' && s.refs) cons.push({ type: 'online', p: id, l: s.refs[0] }, { type: 'online', p: id, l: s.refs[1] });
|
|
85
|
+
else cons.push(...faceSnapCons(s, id));
|
|
76
86
|
return { id, cons };
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
/** A snap onto a wall's face, or where two faces meet: point `p` on that face (on each), not on the wall's centreline. */
|
|
90
|
+
export function faceSnapCons(s: Snap, p: string): Constraint[] {
|
|
91
|
+
const edges = s.kind === 'onface' && s.ref ? [s.ref] : s.kind === 'facecorner' && s.refs ? s.refs : [];
|
|
92
|
+
return edges.map(e => { const [l, face] = edgeOf(e); return { type: 'online', p, l, face }; });
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
export function applyPending(list: Constraint[]): void {
|
|
80
96
|
if (!settings.autoCons) return;
|
|
81
97
|
for (const c of list) tryAddConstraint(c, { silent: true });
|
|
@@ -88,7 +104,8 @@ export function rememberHover(s: Snap | null): void {
|
|
|
88
104
|
export function lengthPreview(c: DrawCtx, T: Xform, a: XY, b: XY, col?: string): void {
|
|
89
105
|
const [x1, y1] = S(T, a.x, a.y), [x2, y2] = S(T, b.x, b.y);
|
|
90
106
|
const L = hyp(b.x - a.x, b.y - a.y);
|
|
91
|
-
|
|
107
|
+
// (not on a page without its scale: the length is only a placeholder)
|
|
108
|
+
if (L * T.s < 6 || noScaleYet()) return;
|
|
92
109
|
chip(c, fmtLen(L), (x1 + x2) / 2, (y1 + y2) / 2 - 16, { bg: '#fff', fg: '#0066FF', border: [col || '#0066FF', 1.5] });
|
|
93
110
|
}
|
|
94
111
|
|