@reekon-tools/boldr-cad 0.1.0 → 1.0.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 +81 -9
- package/dist/app/edit.d.ts +12 -1
- package/dist/app/edit.js +96 -11
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +31 -4
- package/dist/app/state.js +31 -2
- package/dist/app/state.js.map +1 -1
- package/dist/data/memory.d.ts +5 -0
- package/dist/data/memory.js +2 -2
- package/dist/data/memory.js.map +1 -1
- package/dist/data/provider.d.ts +19 -7
- package/dist/data/sync.d.ts +7 -2
- package/dist/data/sync.js +115 -64
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +1 -2
- package/dist/editor/actions.js +391 -113
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/confirm.d.ts +23 -0
- package/dist/editor/confirm.js +35 -0
- package/dist/editor/confirm.js.map +1 -0
- package/dist/editor/elevation.d.ts +191 -90
- package/dist/editor/elevation.js +812 -616
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/face.d.ts +73 -0
- package/dist/editor/face.js +269 -0
- package/dist/editor/face.js.map +1 -0
- package/dist/editor/files.d.ts +6 -0
- package/dist/editor/files.js +50 -4
- package/dist/editor/files.js.map +1 -1
- package/dist/editor/gestures.d.ts +18 -3
- package/dist/editor/gestures.js +145 -30
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keypad.d.ts +19 -4
- package/dist/editor/keypad.js +39 -9
- package/dist/editor/keypad.js.map +1 -1
- package/dist/editor/keys.js +36 -4
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/laser.d.ts +20 -4
- package/dist/editor/laser.js +104 -21
- package/dist/editor/laser.js.map +1 -1
- package/dist/editor/linkActions.d.ts +49 -0
- package/dist/editor/linkActions.js +210 -0
- package/dist/editor/linkActions.js.map +1 -0
- package/dist/editor/linkFollow.d.ts +2 -0
- package/dist/editor/linkFollow.js +53 -0
- package/dist/editor/linkFollow.js.map +1 -0
- package/dist/editor/linkWrite.d.ts +13 -0
- package/dist/editor/linkWrite.js +84 -0
- package/dist/editor/linkWrite.js.map +1 -0
- package/dist/editor/links.d.ts +84 -0
- package/dist/editor/links.js +130 -0
- package/dist/editor/links.js.map +1 -0
- package/dist/editor/markup.d.ts +29 -3
- package/dist/editor/markup.js +95 -10
- package/dist/editor/markup.js.map +1 -1
- package/dist/editor/mode.d.ts +40 -2
- package/dist/editor/mode.js +71 -4
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/ports.d.ts +36 -6
- package/dist/editor/ports.js +10 -1
- package/dist/editor/ports.js.map +1 -1
- package/dist/editor/project.d.ts +38 -6
- package/dist/editor/project.js +167 -27
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/selection.d.ts +10 -0
- package/dist/editor/selection.js +62 -8
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/status.js +3 -0
- package/dist/editor/status.js.map +1 -1
- package/dist/editor/tooltips.d.ts +5 -0
- package/dist/editor/tooltips.js +48 -0
- package/dist/editor/tooltips.js.map +1 -0
- package/dist/editor/view3d.d.ts +17 -0
- package/dist/editor/view3d.js +135 -0
- package/dist/editor/view3d.js.map +1 -0
- package/dist/editor/view3dState.d.ts +39 -0
- package/dist/editor/view3dState.js +44 -0
- package/dist/editor/view3dState.js.map +1 -0
- package/dist/export/dxf.js +1 -1
- package/dist/export/dxf.js.map +1 -1
- package/dist/export/pdf.js +17 -12
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +23 -6
- package/dist/export/plan.js +127 -44
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/constraints.js +15 -4
- package/dist/model/constraints.js.map +1 -1
- package/dist/model/demo.js +3 -3
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +6 -9
- package/dist/model/elevation.js +5 -13
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/faces.d.ts +58 -0
- package/dist/model/faces.js +197 -0
- package/dist/model/faces.js.map +1 -0
- package/dist/model/facesolve.d.ts +72 -0
- package/dist/model/facesolve.js +369 -0
- package/dist/model/facesolve.js.map +1 -0
- package/dist/model/history.d.ts +3 -2
- package/dist/model/history.js +17 -11
- package/dist/model/history.js.map +1 -1
- package/dist/model/images.d.ts +18 -0
- package/dist/model/images.js +26 -0
- package/dist/model/images.js.map +1 -0
- package/dist/model/links.d.ts +46 -0
- package/dist/model/links.js +73 -0
- package/dist/model/links.js.map +1 -0
- package/dist/model/markup.d.ts +46 -0
- package/dist/model/markup.js +71 -0
- package/dist/model/markup.js.map +1 -0
- package/dist/model/measurement.d.ts +11 -0
- package/dist/model/measurement.js +24 -0
- package/dist/model/measurement.js.map +1 -0
- package/dist/model/openings.d.ts +4 -0
- package/dist/model/openings.js +4 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +72 -2
- package/dist/model/planGeom.js +184 -10
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +3 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/projectfile.js +2 -1
- package/dist/model/projectfile.js.map +1 -1
- package/dist/model/rooms.js +2 -2
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/settings.d.ts +6 -0
- package/dist/model/settings.js +2 -2
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +14 -0
- package/dist/model/sketch.js +143 -24
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/smooth.d.ts +6 -0
- package/dist/model/smooth.js +52 -0
- package/dist/model/smooth.js.map +1 -0
- package/dist/model/store.d.ts +12 -0
- package/dist/model/store.js +35 -3
- package/dist/model/store.js.map +1 -1
- package/dist/model/types.d.ts +173 -8
- package/dist/model/wallfaces.d.ts +32 -0
- package/dist/model/wallfaces.js +133 -20
- package/dist/model/wallfaces.js.map +1 -1
- package/dist/model/wallshape.d.ts +15 -0
- package/dist/model/wallshape.js +89 -0
- package/dist/model/wallshape.js.map +1 -0
- package/dist/react/BottomBar.d.ts +9 -5
- package/dist/react/BottomBar.js +213 -24
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Buttons.d.ts +13 -2
- package/dist/react/Buttons.js +37 -5
- package/dist/react/Buttons.js.map +1 -1
- package/dist/react/CadEditor.d.ts +46 -3
- package/dist/react/CadEditor.js +77 -20
- package/dist/react/CadEditor.js.map +1 -1
- package/dist/react/ConfirmSheet.d.ts +4 -0
- package/dist/react/ConfirmSheet.js +31 -0
- package/dist/react/ConfirmSheet.js.map +1 -0
- package/dist/react/ConflictSheet.d.ts +2 -1
- package/dist/react/ConflictSheet.js +16 -8
- package/dist/react/ConflictSheet.js.map +1 -1
- package/dist/react/Drawer.d.ts +8 -2
- package/dist/react/Drawer.js +167 -38
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.d.ts +13 -0
- package/dist/react/Duck.js +45 -0
- package/dist/react/Duck.js.map +1 -0
- package/dist/react/Empty3D.d.ts +2 -0
- package/dist/react/Empty3D.js +39 -0
- package/dist/react/Empty3D.js.map +1 -0
- package/dist/react/FitScroll.d.ts +6 -0
- package/dist/react/FitScroll.js +14 -0
- package/dist/react/FitScroll.js.map +1 -0
- package/dist/react/Icon.native.d.ts +6 -0
- package/dist/react/Icon.native.js +4 -0
- package/dist/react/Icon.native.js.map +1 -1
- package/dist/react/Icon.web.d.ts +6 -0
- package/dist/react/Icon.web.js +4 -0
- package/dist/react/Icon.web.js.map +1 -1
- package/dist/react/KeypadSheet.d.ts +9 -2
- package/dist/react/KeypadSheet.js +174 -42
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/LaserTray.js +8 -5
- package/dist/react/LaserTray.js.map +1 -1
- package/dist/react/MarkupPanel.d.ts +8 -0
- package/dist/react/MarkupPanel.js +67 -0
- package/dist/react/MarkupPanel.js.map +1 -0
- package/dist/react/PageThumb.native.d.ts +7 -2
- package/dist/react/PageThumb.native.js +19 -7
- package/dist/react/PageThumb.native.js.map +1 -1
- package/dist/react/PageThumb.web.d.ts +3 -2
- package/dist/react/PageThumb.web.js +4 -4
- package/dist/react/PageThumb.web.js.map +1 -1
- package/dist/react/PlanCanvas.native.d.ts +1 -1
- package/dist/react/PlanCanvas.native.js +134 -36
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/PlanCanvas.web.js +16 -5
- package/dist/react/PlanCanvas.web.js.map +1 -1
- package/dist/react/Popovers.d.ts +12 -6
- package/dist/react/Popovers.js +53 -26
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.d.ts +24 -0
- package/dist/react/Rail.js +324 -34
- package/dist/react/Rail.js.map +1 -1
- package/dist/react/ShapeMenu.d.ts +7 -0
- package/dist/react/ShapeMenu.js +58 -0
- package/dist/react/ShapeMenu.js.map +1 -0
- package/dist/react/Sheet.d.ts +14 -4
- package/dist/react/Sheet.js +54 -9
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/Slider.d.ts +18 -0
- package/dist/react/Slider.js +27 -0
- package/dist/react/Slider.js.map +1 -0
- package/dist/react/Toast.js +14 -6
- package/dist/react/Toast.js.map +1 -1
- package/dist/react/ToolTip.d.ts +7 -0
- package/dist/react/ToolTip.js +43 -0
- package/dist/react/ToolTip.js.map +1 -0
- package/dist/react/TopBar.d.ts +6 -3
- package/dist/react/TopBar.js +48 -17
- package/dist/react/TopBar.js.map +1 -1
- package/dist/react/Tween.d.ts +7 -0
- package/dist/react/Tween.js +26 -0
- package/dist/react/Tween.js.map +1 -0
- package/dist/react/Tween.native.d.ts +6 -0
- package/dist/react/Tween.native.js +43 -0
- package/dist/react/Tween.native.js.map +1 -0
- package/dist/react/View3D.native.d.ts +4 -0
- package/dist/react/View3D.native.js +103 -0
- package/dist/react/View3D.native.js.map +1 -0
- package/dist/react/View3D.web.d.ts +4 -0
- package/dist/react/View3D.web.js +151 -0
- package/dist/react/View3D.web.js.map +1 -0
- package/dist/react/View3DHost.d.ts +143 -0
- package/dist/react/View3DHost.js +377 -0
- package/dist/react/View3DHost.js.map +1 -0
- package/dist/react/corners.d.ts +7 -0
- package/dist/react/corners.js +51 -0
- package/dist/react/corners.js.map +1 -0
- package/dist/react/easing.d.ts +15 -0
- package/dist/react/easing.js +10 -0
- package/dist/react/easing.js.map +1 -0
- package/dist/react/host.d.ts +7 -2
- package/dist/react/host.js +8 -3
- package/dist/react/host.js.map +1 -1
- package/dist/react/hostKeypad.d.ts +56 -0
- package/dist/react/hostKeypad.js +5 -0
- package/dist/react/hostKeypad.js.map +1 -0
- package/dist/react/motion.d.ts +77 -0
- package/dist/react/motion.js +173 -0
- package/dist/react/motion.js.map +1 -0
- package/dist/react/ports.js +90 -8
- package/dist/react/ports.js.map +1 -1
- package/dist/react/saving.d.ts +15 -0
- package/dist/react/saving.js +76 -0
- package/dist/react/saving.js.map +1 -0
- package/dist/react/store.d.ts +38 -1
- package/dist/react/store.js +31 -1
- package/dist/react/store.js.map +1 -1
- package/dist/react/theme.d.ts +10 -0
- package/dist/react/theme.js +8 -0
- package/dist/react/theme.js.map +1 -1
- package/dist/react/topRow.d.ts +22 -0
- package/dist/react/topRow.js +61 -0
- package/dist/react/topRow.js.map +1 -0
- package/dist/react/touch.d.ts +6 -0
- package/dist/react/touch.js +9 -0
- package/dist/react/touch.js.map +1 -0
- package/dist/render/ctx.d.ts +8 -0
- package/dist/render/draw.d.ts +33 -1
- package/dist/render/draw.js +593 -215
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.d.ts +65 -43
- package/dist/render/elevation.js +414 -140
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +12 -0
- package/dist/render/figmaIcons.js +21 -0
- package/dist/render/figmaIcons.js.map +1 -0
- package/dist/render/hit.d.ts +22 -2
- package/dist/render/hit.js +168 -25
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +69 -10
- package/dist/render/icons.js.map +1 -1
- package/dist/render/linkTile.d.ts +15 -0
- package/dist/render/linkTile.js +369 -0
- package/dist/render/linkTile.js.map +1 -0
- package/dist/render/markup.d.ts +11 -0
- package/dist/render/markup.js +112 -0
- package/dist/render/markup.js.map +1 -0
- package/dist/render/navigate.d.ts +88 -0
- package/dist/render/navigate.js +100 -0
- package/dist/render/navigate.js.map +1 -0
- package/dist/render/snap.d.ts +20 -1
- package/dist/render/snap.js +118 -25
- package/dist/render/snap.js.map +1 -1
- package/dist/render/squircle.d.ts +8 -0
- package/dist/render/squircle.js +71 -0
- package/dist/render/squircle.js.map +1 -0
- package/dist/render/view.d.ts +115 -2
- package/dist/render/view.js +244 -19
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +30 -2
- package/dist/skia/SkiaDrawCtx.js +198 -43
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/skia/host.d.ts +18 -2
- package/dist/skia/host.js +46 -1
- package/dist/skia/host.js.map +1 -1
- package/dist/solver/equations.js +37 -2
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.d.ts +17 -0
- package/dist/solver/gauss-newton.js +144 -36
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/solver/linalg.d.ts +45 -2
- package/dist/solver/linalg.js +164 -10
- package/dist/solver/linalg.js.map +1 -1
- package/dist/solver/types.d.ts +5 -0
- package/dist/tools/annotate.d.ts +57 -1
- package/dist/tools/annotate.js +64 -43
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.d.ts +12 -0
- package/dist/tools/chamfer.js +114 -0
- package/dist/tools/chamfer.js.map +1 -0
- package/dist/tools/corner.d.ts +38 -0
- package/dist/tools/corner.js +99 -0
- package/dist/tools/corner.js.map +1 -0
- package/dist/tools/dim.d.ts +86 -9
- package/dist/tools/dim.js +581 -96
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/elevation.js +1 -1
- package/dist/tools/elevation.js.map +1 -1
- package/dist/tools/fillet.d.ts +9 -0
- package/dist/tools/fillet.js +86 -0
- package/dist/tools/fillet.js.map +1 -0
- package/dist/tools/freehand.d.ts +3 -2
- package/dist/tools/freehand.js +19 -12
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/index.js +14 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/line.d.ts +8 -3
- package/dist/tools/line.js +66 -30
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.d.ts +13 -0
- package/dist/tools/modify.js +105 -25
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/openings.js +22 -15
- package/dist/tools/openings.js.map +1 -1
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.js +42 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +77 -3
- package/dist/tools/select.js +560 -74
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +14 -0
- package/dist/tools/shapes.js +154 -75
- package/dist/tools/shapes.js.map +1 -1
- package/dist/units/format.d.ts +10 -2
- package/dist/units/format.js +18 -3
- package/dist/units/format.js.map +1 -1
- package/dist/view3d/build.d.ts +35 -0
- package/dist/view3d/build.js +630 -0
- package/dist/view3d/build.js.map +1 -0
- package/dist/view3d/camera.d.ts +60 -0
- package/dist/view3d/camera.js +84 -0
- package/dist/view3d/camera.js.map +1 -0
- package/dist/view3d/fatLines.d.ts +11 -0
- package/dist/view3d/fatLines.js +83 -0
- package/dist/view3d/fatLines.js.map +1 -0
- package/dist/view3d/lines.d.ts +17 -0
- package/dist/view3d/lines.js +271 -0
- package/dist/view3d/lines.js.map +1 -0
- package/dist/view3d/mesh.d.ts +51 -0
- package/dist/view3d/mesh.js +101 -0
- package/dist/view3d/mesh.js.map +1 -0
- package/dist/view3d/poly.d.ts +16 -0
- package/dist/view3d/poly.js +109 -0
- package/dist/view3d/poly.js.map +1 -0
- package/dist/view3d/viewer.d.ts +86 -0
- package/dist/view3d/viewer.js +350 -0
- package/dist/view3d/viewer.js.map +1 -0
- package/package.json +15 -2
- package/src/app/edit.ts +83 -12
- package/src/app/main.ts +11 -5
- package/src/app/state.ts +38 -5
- package/src/data/memory.ts +2 -2
- package/src/data/provider.ts +15 -7
- package/src/data/sync.ts +87 -50
- package/src/editor/actions.ts +292 -83
- package/src/editor/confirm.ts +58 -0
- package/src/editor/elevation.ts +698 -478
- package/src/editor/face.ts +227 -0
- package/src/editor/files.ts +40 -4
- package/src/editor/gestures.ts +147 -31
- package/src/editor/keypad.ts +43 -11
- package/src/editor/keys.ts +25 -5
- package/src/editor/laser.ts +98 -20
- package/src/editor/linkActions.ts +194 -0
- package/src/editor/linkFollow.ts +43 -0
- package/src/editor/linkWrite.ts +77 -0
- package/src/editor/links.ts +181 -0
- package/src/editor/markup.ts +92 -8
- package/src/editor/mode.ts +83 -7
- package/src/editor/ports.ts +37 -6
- package/src/editor/project.ts +155 -26
- package/src/editor/selection.ts +54 -8
- package/src/editor/status.ts +2 -0
- package/src/editor/tooltips.ts +47 -0
- package/src/editor/view3d.ts +119 -0
- package/src/editor/view3dState.ts +61 -0
- package/src/export/dxf.ts +1 -1
- package/src/export/pdf.ts +15 -7
- package/src/export/plan.ts +119 -35
- package/src/index.ts +5 -0
- package/src/input/keyboard.ts +2 -4
- package/src/model/constraints.ts +13 -4
- package/src/model/demo.ts +3 -3
- package/src/model/elevation.ts +6 -16
- package/src/model/faces.ts +153 -0
- package/src/model/facesolve.ts +322 -0
- package/src/model/history.ts +19 -11
- package/src/model/images.ts +35 -0
- package/src/model/links.ts +77 -0
- package/src/model/markup.ts +80 -0
- package/src/model/measurement.ts +26 -0
- package/src/model/openings.ts +6 -0
- package/src/model/planGeom.ts +174 -8
- package/src/model/project.ts +3 -2
- package/src/model/projectfile.ts +2 -1
- package/src/model/rooms.ts +1 -1
- package/src/model/settings.ts +8 -2
- package/src/model/sketch.ts +101 -14
- package/src/model/smooth.ts +51 -0
- package/src/model/store.ts +35 -4
- package/src/model/types.ts +170 -8
- package/src/model/wallfaces.ts +126 -17
- package/src/model/wallshape.ts +97 -0
- package/src/react/BottomBar.tsx +263 -46
- package/src/react/Buttons.tsx +50 -9
- package/src/react/CadEditor.tsx +133 -30
- package/src/react/ConfirmSheet.tsx +38 -0
- package/src/react/ConflictSheet.tsx +19 -12
- package/src/react/Drawer.tsx +213 -110
- package/src/react/Duck.tsx +50 -0
- package/src/react/Empty3D.tsx +32 -0
- package/src/react/FitScroll.tsx +19 -0
- package/src/react/Icon.native.tsx +5 -0
- package/src/react/Icon.web.tsx +5 -0
- package/src/react/KeypadSheet.tsx +253 -60
- package/src/react/LaserTray.tsx +12 -8
- package/src/react/MarkupPanel.tsx +150 -0
- package/src/react/PageThumb.native.tsx +18 -7
- package/src/react/PageThumb.web.tsx +4 -4
- package/src/react/PlanCanvas.native.tsx +151 -34
- package/src/react/PlanCanvas.web.tsx +15 -5
- package/src/react/Popovers.tsx +116 -51
- package/src/react/Rail.tsx +360 -27
- package/src/react/ShapeMenu.tsx +66 -0
- package/src/react/Sheet.tsx +56 -11
- package/src/react/Slider.tsx +48 -0
- package/src/react/Toast.tsx +16 -9
- package/src/react/ToolTip.tsx +49 -0
- package/src/react/TopBar.tsx +97 -39
- package/src/react/Tween.native.tsx +36 -0
- package/src/react/Tween.tsx +24 -0
- package/src/react/View3D.native.tsx +131 -0
- package/src/react/View3D.web.tsx +131 -0
- package/src/react/View3DHost.ts +367 -0
- package/src/react/corners.ts +52 -0
- package/src/react/easing.ts +14 -0
- package/src/react/host.ts +8 -3
- package/src/react/hostKeypad.ts +66 -0
- package/src/react/motion.tsx +239 -0
- package/src/react/ports.ts +78 -9
- package/src/react/saving.ts +80 -0
- package/src/react/store.ts +35 -3
- package/src/react/theme.ts +8 -0
- package/src/react/topRow.ts +71 -0
- package/src/react/touch.ts +10 -0
- package/src/render/ctx.ts +8 -0
- package/src/render/draw.ts +437 -175
- package/src/render/elevation.ts +349 -143
- package/src/render/figmaIcons.ts +22 -0
- package/src/render/hit.ts +140 -21
- package/src/render/icons.ts +69 -10
- package/src/render/linkTile.ts +297 -0
- package/src/render/markup.ts +91 -0
- package/src/render/navigate.ts +114 -0
- package/src/render/snap.ts +105 -22
- package/src/render/squircle.ts +73 -0
- package/src/render/view.ts +255 -19
- package/src/skia/SkiaDrawCtx.ts +163 -41
- package/src/skia/host.ts +46 -1
- package/src/solver/equations.ts +37 -2
- package/src/solver/gauss-newton.ts +125 -28
- package/src/solver/linalg.ts +141 -5
- package/src/solver/types.ts +5 -0
- package/src/styles.css +91 -42
- package/src/tools/annotate.ts +58 -29
- package/src/tools/chamfer.ts +103 -0
- package/src/tools/corner.ts +99 -0
- package/src/tools/dim.ts +524 -92
- package/src/tools/elevation.ts +1 -1
- package/src/tools/fillet.ts +82 -0
- package/src/tools/freehand.ts +20 -16
- package/src/tools/index.ts +17 -4
- package/src/tools/line.ts +59 -23
- package/src/tools/modify.ts +86 -24
- package/src/tools/openings.ts +8 -13
- package/src/tools/registry.ts +27 -2
- package/src/tools/select.ts +424 -75
- package/src/tools/shapes.ts +119 -58
- package/src/ui/ctx.ts +2 -1
- package/src/ui/domPorts.ts +23 -4
- package/src/ui/drawer.ts +6 -26
- package/src/ui/elevation.ts +10 -101
- package/src/ui/keypad.ts +8 -14
- package/src/ui/laser.ts +2 -2
- package/src/ui/menu.ts +63 -17
- package/src/ui/noteinput.ts +3 -3
- package/src/ui/opacity.ts +108 -0
- package/src/ui/palette.ts +67 -12
- package/src/ui/rail.ts +21 -7
- package/src/ui/topbar.ts +4 -14
- package/src/units/format.ts +18 -3
- package/src/view3d/build.ts +607 -0
- package/src/view3d/camera.ts +113 -0
- package/src/view3d/fatLines.ts +83 -0
- package/src/view3d/lines.ts +258 -0
- package/src/view3d/mesh.ts +111 -0
- package/src/view3d/poly.ts +104 -0
- package/src/view3d/viewer.ts +348 -0
- package/dist/react/ElevCanvas.native.d.ts +0 -3
- package/dist/react/ElevCanvas.native.js +0 -58
- package/dist/react/ElevCanvas.native.js.map +0 -1
- package/dist/react/ElevCanvas.web.d.ts +0 -4
- package/dist/react/ElevCanvas.web.js +0 -44
- package/dist/react/ElevCanvas.web.js.map +0 -1
- package/dist/react/ElevationSheet.d.ts +0 -2
- package/dist/react/ElevationSheet.js +0 -38
- package/dist/react/ElevationSheet.js.map +0 -1
- package/dist/render/elevationView.d.ts +0 -3
- package/dist/render/elevationView.js +0 -100
- package/dist/render/elevationView.js.map +0 -1
- package/src/react/ElevCanvas.native.tsx +0 -68
- package/src/react/ElevCanvas.web.tsx +0 -42
- package/src/react/ElevationSheet.tsx +0 -88
- package/src/render/elevationView.ts +0 -73
package/src/tools/dim.ts
CHANGED
|
@@ -1,33 +1,46 @@
|
|
|
1
|
-
import { afterEdit, tryAddConstraint } from '../app/edit';
|
|
1
|
+
import { afterEdit, holdGroups, tryAddConstraint } from '../app/edit';
|
|
2
2
|
import { ed } from '../app/state';
|
|
3
3
|
import { measure } from '../model/constraints';
|
|
4
4
|
import { hyp, norm2, pointInPoly, projT } from '../model/geom';
|
|
5
5
|
import { dropHistory, pushHistory } from '../model/history';
|
|
6
|
-
import { addCons, lengthDimOf, lineLen,
|
|
6
|
+
import { addCons, lengthDimOf, lineLen, roomValid } from '../model/sketch';
|
|
7
7
|
import { SK } from '../model/store';
|
|
8
|
-
import type { Constraint, OpeningEdge, XY } from '../model/types';
|
|
8
|
+
import type { Constraint, LinkTarget, OpeningEdge, Sketch, WallFace, XY } from '../model/types';
|
|
9
|
+
import { askLinkedChange, changesValue } from '../editor/linkWrite';
|
|
10
|
+
import { offersJobMeasurements, type MeasurementKind } from '../editor/links';
|
|
11
|
+
import { fromUm, linkInfoOf, linkSnapshot } from '../model/links';
|
|
12
|
+
import { defaultFace, dimEnds, edgeId, edgeLabel, edgeOf, faceName, sideOf, spaceBeside, wallOf } from '../model/planGeom';
|
|
13
|
+
import { faceLength } from '../model/wallfaces';
|
|
14
|
+
import { fitState, overflow, pastWallMsg, restoreFit } from '../editor/elevation';
|
|
15
|
+
import { pushedOut } from '../model/facesolve';
|
|
9
16
|
import { hitAny } from '../render/hit';
|
|
10
17
|
import { chooseEdges, edgePoint, nearerEdge, openingOnLineAt } from '../model/openings';
|
|
11
18
|
import { drawPendingDim, drawRubberBand } from '../render/draw';
|
|
12
19
|
import type { Hit } from '../app/state';
|
|
13
|
-
import { mainT, requestDraw, s2w, V } from '../render/view';
|
|
20
|
+
import { mainT, requestDraw, requestOverlayDraw, s2w, V } from '../render/view';
|
|
14
21
|
import { solver } from '../solver';
|
|
15
22
|
import { fmtAng, fmtLen } from '../units';
|
|
16
23
|
import { addOpeningDim } from './openings';
|
|
17
24
|
import type { PEv } from './registry';
|
|
18
|
-
import {
|
|
25
|
+
import { keypadFresh, keypadOpen, keypadSetReading } from '../editor/keypad';
|
|
26
|
+
import { cornerLines } from '../editor/selection';
|
|
27
|
+
import { closeKeypad, kpTyped, openKeypad, showConflict, toast, updateCtx, updateHint, type KeypadSpec } from '../editor/ports';
|
|
19
28
|
|
|
20
29
|
export const dimTool = {
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
/** angle: the first pick is a corner whose angle keypad is open (the next tap closes it) */
|
|
31
|
+
st: { first: null as string | null, angle: false },
|
|
32
|
+
reset() { if (this.st.angle && keypadOpen()) closeKeypad(); this.st = { first: null, angle: false }; ed.hover = null; ed.pickedAnchor = null; },
|
|
23
33
|
busy() { return !!this.st.first || !!ed.pickedAnchor; },
|
|
24
34
|
hover(ev: PEv) {
|
|
35
|
+
const was = ed.hover;
|
|
25
36
|
ed.hover = pickForDim(ev);
|
|
26
37
|
if (ed.hover && ed.hover.type === 'room') ed.hover = null;
|
|
27
38
|
// after the first pick: a rubber band from it to what's under the pointer
|
|
28
39
|
const band = this.band(ev);
|
|
29
40
|
ed.preview = band ? (c, T) => drawRubberBand(c, T, band.from, band.to, band.snapped) : null;
|
|
30
|
-
|
|
41
|
+
// what's under the pointer is highlighted in the drawing itself; otherwise only the band moved
|
|
42
|
+
const same = was?.type === ed.hover?.type && was?.id === ed.hover?.id && was?.part === ed.hover?.part;
|
|
43
|
+
if (same) requestOverlayDraw(); else requestDraw();
|
|
31
44
|
},
|
|
32
45
|
/** Ends of the rubber band: the first pick (an opening body resolves to the edge facing the target) and the target. */
|
|
33
46
|
band(ev: PEv): { from: XY; to: XY; snapped: boolean } | null {
|
|
@@ -54,12 +67,14 @@ export const dimTool = {
|
|
|
54
67
|
up(ev: PEv) {
|
|
55
68
|
const h = pickForDim(ev);
|
|
56
69
|
const print = ed.dimLayer === 'print', st = this.st;
|
|
70
|
+
// a corner's angle keypad is open: the next tap closes it and goes on (a second point measures from the corner)
|
|
71
|
+
if (st.angle) { st.angle = false; if (keypadOpen()) closeKeypad(); }
|
|
57
72
|
if (!h || h.type === 'room') { st.first = null; ed.pickedAnchor = null; ed.sel = []; ed.preview = null; updateHint(); requestDraw(); return; }
|
|
58
73
|
// door/window edge anchors: dimension them to a point (or another opening's edge) to lock them
|
|
59
74
|
const picked = ed.pickedAnchor;
|
|
60
75
|
if (h.type === 'oanchor') {
|
|
61
76
|
const edge = h.part as OpeningEdge;
|
|
62
|
-
if (print) { toast('Print dimensions can’t attach to doors or windows yet. Switch to Driving in the bar below.'); return; }
|
|
77
|
+
if (print) { toast('Print dimensions can’t attach to doors or windows yet. Switch to Driving in the bar below.', { warn: true }); return; }
|
|
63
78
|
if (st.first) {
|
|
64
79
|
const p = st.first; st.first = null; ed.sel = [];
|
|
65
80
|
addOpeningDim(h.id, h.weak ? nearerEdge(SK(), h.id, p) : edge, { pt: p });
|
|
@@ -79,21 +94,39 @@ export const dimTool = {
|
|
|
79
94
|
}
|
|
80
95
|
if (picked) {
|
|
81
96
|
if (h.type === 'point') { ed.pickedAnchor = null; addOpeningDim(picked.id, picked.body ? nearerEdge(SK(), picked.id, h.id) : picked.edge, { pt: h.id }); updateHint(); return; }
|
|
82
|
-
toast('Tap a corner, a point, or another door or window edge to measure to.');
|
|
97
|
+
toast('Tap a corner, a point, or another door or window edge to measure to.', { warn: true });
|
|
83
98
|
return;
|
|
84
99
|
}
|
|
85
100
|
if (h.type === 'point') {
|
|
86
|
-
if (!st.first) {
|
|
101
|
+
if (!st.first) {
|
|
102
|
+
st.first = h.id; ed.sel = [{ type: 'point', id: h.id }];
|
|
103
|
+
// a corner: its angle straight away (close the keypad to measure from the corner to another point instead)
|
|
104
|
+
if (!print) st.angle = cornerAngle(h.id, () => { this.st.first = null; this.st.angle = false; ed.sel = []; updateHint(); requestDraw(); }, true);
|
|
105
|
+
updateHint(); requestDraw(); return;
|
|
106
|
+
}
|
|
87
107
|
if (h.id !== st.first) dimPoints(st.first, h.id, print);
|
|
88
108
|
st.first = null; updateHint();
|
|
89
109
|
return;
|
|
90
110
|
}
|
|
111
|
+
// a wall's edge: its length on that face — or, after a point, the distance from the face to it; a curved wall's,
|
|
112
|
+
// its diameter across that face
|
|
113
|
+
if (h.type === 'wface') {
|
|
114
|
+
const [lid, side] = edgeOf(h.id);
|
|
115
|
+
if (!SK().lines[lid]) { dimCurve(lid, side); return; }
|
|
116
|
+
if (st.first) { const p = st.first; st.first = null; if (print) dimPointLine(p, lid); else dimEdgePoint(lid, side, p); updateHint(); return; }
|
|
117
|
+
dimLine(lid, print, side);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
91
120
|
if (h.type === 'line') {
|
|
121
|
+
if (h.side) ed.wallTap = { id: h.id, side: h.side };
|
|
92
122
|
if (st.first) { dimPointLine(st.first, h.id); st.first = null; updateHint(); return; }
|
|
93
123
|
dimLine(h.id, print);
|
|
94
124
|
return;
|
|
95
125
|
}
|
|
96
|
-
if (h.type === 'circle' || h.type === 'arc')
|
|
126
|
+
if (h.type === 'circle' || h.type === 'arc') {
|
|
127
|
+
if (h.side) ed.wallTap = { id: h.id, side: h.side };
|
|
128
|
+
dimCurve(h.id);
|
|
129
|
+
}
|
|
97
130
|
},
|
|
98
131
|
hint() {
|
|
99
132
|
if (ed.pickedAnchor) return 'Tap a corner or point to measure from. The door or window locks at that distance.';
|
|
@@ -107,12 +140,15 @@ export const dimTool = {
|
|
|
107
140
|
* counts as its nearer edge, so openings are as easy to dimension as points.
|
|
108
141
|
*/
|
|
109
142
|
function pickForDim(ev: PEv): Hit | null {
|
|
110
|
-
const h = hitAny(ev.sx, ev.sy, { touch: ev.touch, only: 'geom' });
|
|
111
|
-
if (!h || h.type !== 'line') return h;
|
|
112
|
-
const
|
|
143
|
+
const h = hitAny(ev.sx, ev.sy, { touch: ev.touch, only: 'geom', faces: true });
|
|
144
|
+
if (!h || (h.type !== 'line' && h.type !== 'wface')) return h;
|
|
145
|
+
const lid = h.type === 'wface' ? edgeOf(h.id)[0] : h.id;
|
|
146
|
+
const sk = SK(), l = sk.lines[lid];
|
|
147
|
+
if (!l) return h; // (a curved wall's edge)
|
|
148
|
+
const A = sk.points[l.a], B = sk.points[l.b];
|
|
113
149
|
const [wx, wy] = s2w(ev.sx, ev.sy);
|
|
114
150
|
const s = projT(A, B, { x: wx, y: wy }) * hyp(B.x - A.x, B.y - A.y);
|
|
115
|
-
const a = openingOnLineAt(sk,
|
|
151
|
+
const a = openingOnLineAt(sk, lid, s, 2 / mainT().s);
|
|
116
152
|
// weak = the body was tapped: the edge gets chosen to face whatever it's measured to
|
|
117
153
|
return a ? { type: 'oanchor', id: a.id, part: a.edge, weak: true } : h;
|
|
118
154
|
}
|
|
@@ -126,32 +162,250 @@ function sideAwayFromRooms(lid: string): number {
|
|
|
126
162
|
return 1;
|
|
127
163
|
}
|
|
128
164
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Tap on a line (or its + chip): open the keypad for its length, or edit the existing length. A wall's is measured on
|
|
167
|
+
* `side`, the edge picked — or, picked whole, on the side a space is on (the side it was tapped on, between two), and
|
|
168
|
+
* along its centerline where there's no space; the keypad can switch it.
|
|
169
|
+
*/
|
|
170
|
+
export function dimLine(lid: string, print: boolean, side?: 1 | -1): void {
|
|
131
171
|
const sk = SK(), l = sk.lines[lid];
|
|
132
172
|
if (!l) return;
|
|
173
|
+
const wall = faced(sk, lid);
|
|
133
174
|
if (print) {
|
|
134
175
|
pushHistory();
|
|
135
|
-
|
|
136
|
-
|
|
176
|
+
// (on a wall's edge: that face's length, labelled on that side)
|
|
177
|
+
const f = wall ? side : undefined;
|
|
178
|
+
const off = (36 / V().s) * (f ?? sideAwayFromRooms(lid));
|
|
179
|
+
addCons(sk, { type: 'dist', a: l.a, b: l.b, kind: 'al', value: f ? faceLength(sk, lid, f) : lineLen(sk, l), line: lid, driving: false, layer: 'print', off, ...(f ? { face: f } : {}) });
|
|
137
180
|
afterEdit();
|
|
138
181
|
toast('Print dimension added. Drag it to move it.');
|
|
139
182
|
return;
|
|
140
183
|
}
|
|
141
184
|
const ex = lengthDimOf(sk, lid);
|
|
142
|
-
if (ex) { editDim(ex); return; }
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
185
|
+
if (ex) { editDim(ex, undefined, false, wall && side ? { face: side } : undefined); return; }
|
|
186
|
+
let face: 0 | 1 | -1 = wall ? side ?? defaultFace(sk, lid, ed.wallTap?.id === lid ? ed.wallTap.side : undefined) ?? 0 : 0;
|
|
187
|
+
const len = () => (face ? faceLength(sk, lid, face) : lineLen(sk, l));
|
|
188
|
+
// (while it's being entered, what it measures is what's highlighted: the edge, or the wall)
|
|
189
|
+
const was = ed.sel;
|
|
190
|
+
const show = () => { ed.sel = face ? [{ type: 'wface', id: edgeId(lid, face) }] : [{ type: 'line', id: lid }]; requestDraw(); };
|
|
191
|
+
show();
|
|
192
|
+
const spec: KeypadSpec = {
|
|
193
|
+
title: 'Length', sub: wall ? lengthSub(sk, lid, face) : '', initial: fmtLen(len()), selectAll: true, laser: true,
|
|
194
|
+
extras: wall ? () => lengthChoices(sk, lid).map(ch => ({
|
|
195
|
+
label: ch.label!, on: ch.face === face,
|
|
196
|
+
onPress: () => { face = ch.face!; spec.sub = lengthSub(sk, lid, face); show(); if (keypadFresh()) keypadSetReading(len()); },
|
|
197
|
+
})) : undefined,
|
|
198
|
+
onClose: () => { ed.sel = was; updateCtx(); requestDraw(); },
|
|
199
|
+
onDone: (v, capture) => { if (v > 0) addLength(v, capture !== undefined ? { capture } : {}); },
|
|
200
|
+
// (or a length of the job's, from its list: the line takes it, linked to it)
|
|
201
|
+
measurements: offersJobMeasurements() ? {
|
|
202
|
+
kind: 'length', pick: t => {
|
|
203
|
+
const v = jobValue(t, 'length');
|
|
204
|
+
if (v == null) return;
|
|
205
|
+
closeKeypad();
|
|
206
|
+
addLength(v, { measurementRef: linkSnapshot(t) });
|
|
207
|
+
},
|
|
208
|
+
} : undefined,
|
|
209
|
+
};
|
|
210
|
+
/** The line's length dimension, driving, measured where the keypad says (`extra`: what it's from). */
|
|
211
|
+
function addLength(v: number, extra: Partial<Constraint>): void {
|
|
212
|
+
pushHistory();
|
|
213
|
+
const r = tryAddConstraint({ type: 'dist', a: l.a, b: l.b, kind: 'al', value: v, line: lid, driving: true, off: face ? (face * 30) / V().s : 0, ...(face ? { face } : {}), ...extra });
|
|
214
|
+
// a measurement of the job drives a line, or nothing: not one whose length the drawing already sets
|
|
215
|
+
if (r.ok && r.ref && extra.measurementRef) {
|
|
216
|
+
delete SK().cons[r.id!];
|
|
217
|
+
dropHistory();
|
|
218
|
+
toast(LENGTH_SET_ALREADY, { warn: true });
|
|
219
|
+
} else if (!r.ok) dropHistory();
|
|
220
|
+
afterEdit();
|
|
221
|
+
}
|
|
222
|
+
openKeypad(spec);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Why a measurement of the job can't drive a line the drawing already sets the length of. */
|
|
226
|
+
export const LENGTH_SET_ALREADY = 'The drawing already sets that line’s length. Free it first (make one of its dimensions a reference), then pick the measurement.';
|
|
227
|
+
/** Why a measurement of the job can't drive an angle the drawing already sets. */
|
|
228
|
+
export const ANGLE_SET_ALREADY = 'The drawing already sets that angle. Free it first (make one of its dimensions a reference), then pick the measurement.';
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A measurement of the job's as a keypad of `kind` takes it — inches for a length, degrees for an angle (what the host
|
|
232
|
+
* says it is now, or what it was when listed); null, said why, if it has none, or isn't of that kind.
|
|
233
|
+
*/
|
|
234
|
+
function jobValue(t: LinkTarget, kind: MeasurementKind): number | null {
|
|
235
|
+
if ((t.mkind ?? 'length') !== kind) {
|
|
236
|
+
toast(kind === 'angle' ? 'That’s a length. Pick an angle, or type one in.' : 'That’s an angle. Pick a length, or type one in.', { warn: true });
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const v = linkInfoOf(t)?.value ?? t.value;
|
|
240
|
+
if (v == null || !(v > 0)) { toast(kind === 'angle' ? 'That measurement has no angle to use. Pick another, or type one in.' : 'That measurement has no length to use. Pick another, or type one in.', { warn: true }); return null; }
|
|
241
|
+
return kind === 'angle' ? v : fromUm(v);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Measuring off a wall's faces (its edges) rather than its centerline. */
|
|
245
|
+
|
|
246
|
+
/** A straight wall, whose length can be measured on either face (not a guide). */
|
|
247
|
+
function faced(sk: Sketch, lid?: string): boolean {
|
|
248
|
+
const l = lid ? sk.lines[lid] : undefined;
|
|
249
|
+
return !!l && l.kind === 'wall' && !l.constr;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** What a wall's length (a curved one's diameter) is measured on, as the keypad says it. */
|
|
253
|
+
const lengthSub = (sk: Sketch, lid: string, face: number): string => (face ? edgeLabel(sk, lid, face as 1 | -1) : 'Wall centerline');
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Faces a dimension can be measured between. `face`: a wall's length on its face to the left of A → B (+1), the right
|
|
257
|
+
* (−1) or its centerline (0). `faces`: a distance between two walls ([the line it's square to, the wall its point is
|
|
258
|
+
* on — `l2`]) between the faces toward each other (+1), far faces (−1) or centerlines (0).
|
|
259
|
+
*/
|
|
260
|
+
export interface FaceChoice {
|
|
261
|
+
/** what it's called in the keypad; `short`, in the bar (beside `icon`, which shows the face as the page has it) */
|
|
262
|
+
label?: string; short?: string; icon?: string;
|
|
263
|
+
face?: 0 | 1 | -1; faces?: [number, number]; l2?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** A wall's face as an icon: the one on top, at the bottom, left or right (as the page shows it) — or its centerline. */
|
|
267
|
+
function faceIcon(sk: Sketch, lid: string, side: number): string {
|
|
268
|
+
const l = sk.lines[lid], a = sk.points[l.a], b = sk.points[l.b], level = Math.abs(b.x - a.x) >= Math.abs(b.y - a.y);
|
|
269
|
+
if (!side) return level ? 'faceMidH' : 'faceMidV';
|
|
270
|
+
// (the face's outward direction: the left of A → B for +1; y is up)
|
|
271
|
+
const nx = -(b.y - a.y) * side, ny = (b.x - a.x) * side;
|
|
272
|
+
return level ? (ny > 0 ? 'faceT' : 'faceB') : nx > 0 ? 'faceR' : 'faceL';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* A wall's length: on its inside face (or one space's side, or — named by the way they face — its top or left), its
|
|
277
|
+
* centerline, its other face.
|
|
278
|
+
*/
|
|
279
|
+
function lengthChoices(sk: Sketch, lid: string): FaceChoice[] {
|
|
280
|
+
const n = faceName(sk, lid, 1), byWay = ['Top', 'Bottom', 'Left', 'Right'].includes(n);
|
|
281
|
+
const first: 1 | -1 = byWay ? (n === 'Top' || n === 'Left' ? 1 : -1) : spaceBeside(sk, lid, 1) ? 1 : -1;
|
|
282
|
+
const side = (f: 1 | -1): FaceChoice => ({ label: edgeLabel(sk, lid, f), short: faceName(sk, lid, f).replace(/ side$/, ''), icon: faceIcon(sk, lid, f), face: f });
|
|
283
|
+
return [side(first), { label: 'Centerline', short: 'Center', icon: faceIcon(sk, lid, 0), face: 0 }, side(first === 1 ? -1 : 1)];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A curved wall's diameter (or radius): across its inner face, its centerline, its outer face — its outer face first
|
|
288
|
+
* when that's the one a space is against (a round column in a room).
|
|
289
|
+
*/
|
|
290
|
+
function curveChoices(sk: Sketch, id: string): FaceChoice[] {
|
|
291
|
+
const first: 1 | -1 = spaceBeside(sk, id, 1) && !spaceBeside(sk, id, -1) ? 1 : -1;
|
|
292
|
+
const side = (f: 1 | -1): FaceChoice => ({ label: edgeLabel(sk, id, f), short: faceName(sk, id, f), icon: f === 1 ? 'ringOut' : 'ringIn', face: f });
|
|
293
|
+
return [side(first), { label: 'Centerline', short: 'Center', icon: 'ringMid', face: 0 }, side(first === 1 ? -1 : 1)];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The faces a dimension on walls can be measured on, as choices; none when it isn't on a wall. */
|
|
297
|
+
export function faceChoices(sk: Sketch, c: Constraint): FaceChoice[] {
|
|
298
|
+
if (c.type === 'dist' && c.line && (!c.kind || c.kind === 'al') && faced(sk, c.line)) return lengthChoices(sk, c.line);
|
|
299
|
+
if (c.type === 'diam' && c.c && !sk.lines[c.c] && wallOf(sk, c.c)) return curveChoices(sk, c.c);
|
|
300
|
+
if (c.type === 'pldist' && c.l && sk.lines[c.l] && sk.points[c.p!]) {
|
|
301
|
+
const w1 = faced(sk, c.l), w2 = faced(sk, c.l2);
|
|
302
|
+
if (!w1 && !w2) return [];
|
|
303
|
+
const k = (v: number): [number, number] => [w1 ? v : 0, w2 ? v : 0];
|
|
304
|
+
if (w1 && w2) {
|
|
305
|
+
const l = sk.lines[c.l], a = sk.points[l.a], b = sk.points[l.b], H = Math.abs(b.x - a.x) >= Math.abs(b.y - a.y) ? 'H' : '';
|
|
306
|
+
return [
|
|
307
|
+
{ label: 'Inside faces', short: 'Inside', icon: 'spanIn' + H, faces: k(1) },
|
|
308
|
+
{ label: 'Centerlines', short: 'Centers', icon: 'spanMid' + H, faces: [0, 0] },
|
|
309
|
+
{ label: 'Outside faces', short: 'Outside', icon: 'spanOut' + H, faces: k(-1) },
|
|
310
|
+
];
|
|
311
|
+
}
|
|
312
|
+
// one wall: its face toward the other end, its centerline, its far face
|
|
313
|
+
const wl = w1 ? c.l : c.l2!, other = w1 ? sk.points[c.p!] : sk.points[sk.lines[c.l].a], toward = sideOf(sk, wl, other);
|
|
314
|
+
return [
|
|
315
|
+
{ label: 'Near face', short: 'Near', icon: faceIcon(sk, wl, toward), faces: k(1) },
|
|
316
|
+
{ label: 'Centerline', short: 'Center', icon: faceIcon(sk, wl, 0), faces: [0, 0] },
|
|
317
|
+
{ label: 'Far face', short: 'Far', icon: faceIcon(sk, wl, -toward), faces: k(-1) },
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Whether a dimension is measured on the faces a choice says. */
|
|
324
|
+
export function onChoice(c: Constraint, ch: FaceChoice): boolean {
|
|
325
|
+
if (ch.face !== undefined) return (c.face ?? 0) === ch.face;
|
|
326
|
+
return !!ch.faces && (c.faces?.[0] ?? 0) === ch.faces[0] && (c.faces?.[1] ?? 0) === ch.faces[1];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Measure a dimension on the faces a choice says (its number left as it is). A length's label moves to that side. */
|
|
330
|
+
function applyFace(c: Constraint, ch: FaceChoice): void {
|
|
331
|
+
if (ch.face !== undefined) {
|
|
332
|
+
if (ch.face) c.face = ch.face; else delete c.face;
|
|
333
|
+
if (ch.face && c.type === 'dist') {
|
|
334
|
+
const l = SK().lines[c.line!], k = l && c.a === l.a ? 1 : -1;
|
|
335
|
+
c.off = (Math.abs(c.off ?? 0) || 30 / V().s) * ch.face * k;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (ch.faces) {
|
|
339
|
+
if (ch.l2) c.l2 = ch.l2;
|
|
340
|
+
if (ch.faces[0] || ch.faces[1]) c.faces = [ch.faces[0], ch.faces[1]];
|
|
341
|
+
else delete c.faces;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** A copy of a dimension, measured on the faces a choice says. */
|
|
346
|
+
function withFace(c: Constraint, ch: FaceChoice | null): Constraint {
|
|
347
|
+
const n = { ...c };
|
|
348
|
+
if (ch) applyFace(n, ch);
|
|
349
|
+
return n;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** What a dimension's faces are, to put back. */
|
|
353
|
+
const faceState = (c: Constraint) => ({ face: c.face, faces: c.faces, l2: c.l2, off: c.off });
|
|
354
|
+
function restoreFace(c: Constraint, f: ReturnType<typeof faceState>): void {
|
|
355
|
+
if (f.face === undefined) delete c.face; else c.face = f.face;
|
|
356
|
+
if (f.faces === undefined) delete c.faces; else c.faces = f.faces;
|
|
357
|
+
if (f.l2 === undefined) delete c.l2; else c.l2 = f.l2;
|
|
358
|
+
if (f.off === undefined) delete c.off; else c.off = f.off;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Measure a dimension on other faces of its walls, the drawing kept as it is: its number is what it measures there now. */
|
|
362
|
+
export function setDimFace(id: string, ch: FaceChoice): void {
|
|
363
|
+
const sk = SK(), c = sk.cons[id];
|
|
364
|
+
if (!c || onChoice(c, ch)) return;
|
|
365
|
+
pushHistory();
|
|
366
|
+
applyFace(c, ch);
|
|
367
|
+
if (c.driving !== false) c.value = measure(sk, c);
|
|
368
|
+
afterEdit();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The face of wall `lid` to measure from, to something at `at`: +1 its face toward it, −1 its far face, 0 its centerline.
|
|
373
|
+
* `side` is the edge picked (+1 / −1: the left / right of its A → B; 0 its centerline). Picked whole, to a point: the
|
|
374
|
+
* face toward it where a space is against it. To another wall (`wall`): its room's side, as its length is measured —
|
|
375
|
+
* the side toward the other between two spaces; with no space beside it, its centerline.
|
|
376
|
+
*/
|
|
377
|
+
function faceToward(sk: Sketch, lid: string, at: XY, side?: 0 | 1 | -1, wall = false): number {
|
|
378
|
+
if (!faced(sk, lid)) return 0;
|
|
379
|
+
const toward = sideOf(sk, lid, at);
|
|
380
|
+
if (side === undefined) return spaceBeside(sk, lid, toward) ? 1 : wall && spaceBeside(sk, lid, toward === 1 ? -1 : 1) ? -1 : 0;
|
|
381
|
+
return side === 0 ? 0 : side === toward ? 1 : -1;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** From a wall's edge to a point: square to the wall, from that face. */
|
|
385
|
+
export function dimEdgePoint(lid: string, side: 1 | -1, p: string): void {
|
|
386
|
+
const sk = SK(), l = sk.lines[lid];
|
|
387
|
+
// (one of the wall's own ends is no distance from it)
|
|
388
|
+
if (!l || p === l.a || p === l.b) { toast('Pick a point off the wall to measure from its edge.', { warn: true }); return; }
|
|
389
|
+
dimPointLine(p, lid, { faces: [faceToward(sk, lid, sk.points[p], side), 0], title: 'Distance from the edge' });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Two things picked, at least one a wall's edge: the distance between them — two edges of one wall, its thickness
|
|
394
|
+
* (`thickness` opens it); of two walls, face to face; an edge and a line, from that face; an edge and a point, from it.
|
|
395
|
+
*/
|
|
396
|
+
export function dimEdges(items: { type: string; id: string }[], thickness: (lid: string) => void): void {
|
|
397
|
+
const edges = items.filter(x => x.type === 'wface').map(x => edgeOf(x.id));
|
|
398
|
+
const other = items.find(x => x.type !== 'wface');
|
|
399
|
+
if (edges.length === 2) {
|
|
400
|
+
const [[a, sa], [b, sb]] = edges;
|
|
401
|
+
if (a === b) { thickness(a); return; }
|
|
402
|
+
if (SK().lines[a] && SK().lines[b]) dimTwoLines(a, b, { [a]: sa, [b]: sb });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (edges.length !== 1 || !other) return;
|
|
406
|
+
const [lid, side] = edges[0];
|
|
407
|
+
if (other.type === 'point') dimEdgePoint(lid, side, other.id);
|
|
408
|
+
else if (other.type === 'line') dimTwoLines(lid, other.id, { [lid]: side });
|
|
155
409
|
}
|
|
156
410
|
|
|
157
411
|
export function dimPoints(a: string, b: string, print: boolean): void {
|
|
@@ -170,9 +424,9 @@ export function dimPoints(a: string, b: string, print: boolean): void {
|
|
|
170
424
|
openKeypad({
|
|
171
425
|
onClose: () => { ed.preview = null; requestDraw(); },
|
|
172
426
|
title: 'Distance', initial: fmtLen(L), selectAll: true, laser: true,
|
|
173
|
-
onDone: v => {
|
|
427
|
+
onDone: (v, capture) => {
|
|
174
428
|
pushHistory();
|
|
175
|
-
const r = tryAddConstraint({ type: 'dist', a, b, kind: 'al', value: v, driving: true, off: 0 });
|
|
429
|
+
const r = tryAddConstraint({ type: 'dist', a, b, kind: 'al', value: v, driving: true, off: 0, ...(capture !== undefined ? { capture } : {}) });
|
|
176
430
|
if (!r.ok) dropHistory();
|
|
177
431
|
ed.sel = [];
|
|
178
432
|
afterEdit();
|
|
@@ -182,9 +436,11 @@ export function dimPoints(a: string, b: string, print: boolean): void {
|
|
|
182
436
|
|
|
183
437
|
/**
|
|
184
438
|
* Dimension between two lines: their distance if they're parallel (measured square to them, and kept parallel),
|
|
185
|
-
* otherwise the angle between them. An existing dimension between them is edited instead of stacking another.
|
|
439
|
+
* otherwise the angle between them. An existing dimension between them is edited instead of stacking another. Between
|
|
440
|
+
* walls it's measured on `sides`, the edges picked (+1 / −1: the face to the left / right of A → B; 0 its centerline) —
|
|
441
|
+
* or, picked whole, each wall's room side (see faceToward).
|
|
186
442
|
*/
|
|
187
|
-
export function dimTwoLines(l1: string, l2: string): void {
|
|
443
|
+
export function dimTwoLines(l1: string, l2: string, sides: Record<string, 0 | 1 | -1> = {}): void {
|
|
188
444
|
const sk = SK(), L1 = sk.lines[l1], L2 = sk.lines[l2];
|
|
189
445
|
if (!L1 || !L2) return;
|
|
190
446
|
const [ax, ay] = norm2(sk.points[L1.b].x - sk.points[L1.a].x, sk.points[L1.b].y - sk.points[L1.a].y);
|
|
@@ -197,125 +453,301 @@ export function dimTwoLines(l1: string, l2: string): void {
|
|
|
197
453
|
if (kind === 'angle') return (c.l1 === l1 && c.l2 === l2) || (c.l1 === l2 && c.l2 === l1);
|
|
198
454
|
return (c.l === l1 && (c.p === L2.a || c.p === L2.b)) || (c.l === l2 && (c.p === L1.a || c.p === L1.b));
|
|
199
455
|
});
|
|
200
|
-
|
|
456
|
+
/** The faces between line `l` (the one measured square to) and the other, from point `p` on the other. */
|
|
457
|
+
const facesFrom = (l: string, p: string): FaceChoice => {
|
|
458
|
+
const o = l === l1 ? l2 : l1, ol = sk.lines[o], on = sk.lines[l];
|
|
459
|
+
return { faces: [faceToward(sk, l, sk.points[p], sides[l], true), faceToward(sk, o, sk.points[on.a], sides[o], true)], ...(faced(sk, o) && ol ? { l2: o } : {}) };
|
|
460
|
+
};
|
|
461
|
+
if (existing) {
|
|
462
|
+
const c = sk.cons[existing];
|
|
463
|
+
// (the edges picked: it's measured between them now)
|
|
464
|
+
editDim(existing, undefined, false, c.type === 'pldist' && Object.keys(sides).length ? facesFrom(c.l!, c.p!) : undefined);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
201
467
|
if (!parallel) { dimAngle(l1, l2); return; }
|
|
202
468
|
// measure from the end of the second line that sits most squarely across from the first
|
|
203
469
|
const A = sk.points[L1.a], B = sk.points[L1.b], len2 = (B.x - A.x) ** 2 + (B.y - A.y) ** 2 || 1;
|
|
204
470
|
const t = (pid: string) => { const P = sk.points[pid]; return ((P.x - A.x) * (B.x - A.x) + (P.y - A.y) * (B.y - A.y)) / len2; };
|
|
205
471
|
const p = Math.abs(t(L2.a) - 0.5) <= Math.abs(t(L2.b) - 0.5) ? L2.a : L2.b;
|
|
206
|
-
|
|
472
|
+
const f = facesFrom(l1, p);
|
|
473
|
+
dimPointLine(p, l1, { keepParallel: l2, title: 'Distance between lines', faces: f.faces, l2: f.l2 });
|
|
207
474
|
}
|
|
208
475
|
|
|
209
|
-
|
|
476
|
+
/**
|
|
477
|
+
* The distance from point `p` to line `lid`, square to it. On a wall it's measured from `faces[0]`, its face toward
|
|
478
|
+
* the point (+1), its far face (−1) or its centerline (0) — by default the face toward it where a space is against it;
|
|
479
|
+
* `faces[1]` is the same for wall `l2`, which the point is the end of (a distance between two walls). The keypad can
|
|
480
|
+
* switch them.
|
|
481
|
+
*/
|
|
482
|
+
export function dimPointLine(p: string, lid: string, opts: { keepParallel?: string; title?: string; faces?: [number, number]; l2?: string } = {}): void {
|
|
210
483
|
const sk = SK(), l = sk.lines[lid], P = sk.points[p], A = sk.points[l.a], B = sk.points[l.b];
|
|
211
484
|
const sd = ((B.x - A.x) * (P.y - A.y) - (B.y - A.y) * (P.x - A.x)) / (hyp(B.x - A.x, B.y - A.y) || 1);
|
|
212
485
|
const sign = sd < 0 ? -1 : 1;
|
|
213
|
-
const
|
|
214
|
-
|
|
486
|
+
const c: Constraint = { type: 'pldist', p, l: lid, value: 0, sign, driving: true, ...(opts.l2 ? { l2: opts.l2 } : {}) };
|
|
487
|
+
applyFace(c, { faces: opts.faces ?? [faceToward(sk, lid, P), 0] });
|
|
488
|
+
const choices = faceChoices(sk, c);
|
|
215
489
|
ed.sel = [];
|
|
216
|
-
ed.preview = (
|
|
490
|
+
ed.preview = (cx, T) => { const e = dimEnds(sk, c); if (e) drawPendingDim(cx, T, { from: e.a, to: e.b, a: e.a, b: e.b, nx: 0, ny: 0, off: 0, label: fmtLen(kpTyped() ?? measure(sk, c)) }); };
|
|
217
491
|
requestDraw();
|
|
218
|
-
|
|
492
|
+
const spec: KeypadSpec = {
|
|
219
493
|
onClose: () => { ed.preview = null; requestDraw(); },
|
|
220
|
-
|
|
494
|
+
// (which faces it's measured between: the choices beside the keys)
|
|
495
|
+
title: opts.title || 'Distance to line', sub: opts.keepParallel ? 'Measured square to the lines' : '', initial: fmtLen(measure(sk, c)), selectAll: true,
|
|
496
|
+
extras: choices.length ? () => choices.map(ch => ({
|
|
497
|
+
label: ch.label!, on: onChoice(c, ch),
|
|
498
|
+
onPress: () => { applyFace(c, ch); requestOverlayDraw(); if (keypadFresh()) keypadSetReading(measure(sk, c)); },
|
|
499
|
+
})) : undefined,
|
|
221
500
|
onDone: v => {
|
|
222
501
|
pushHistory();
|
|
223
|
-
const r = tryAddConstraint({
|
|
502
|
+
const r = tryAddConstraint({ ...c, value: v });
|
|
224
503
|
if (!r.ok) dropHistory();
|
|
225
504
|
// a distance between two lines only holds if they stay parallel (no-op if something already keeps them so)
|
|
226
505
|
else if (opts.keepParallel) tryAddConstraint({ type: 'par', l1: lid, l2: opts.keepParallel }, { silent: true });
|
|
227
506
|
ed.sel = [];
|
|
228
507
|
afterEdit();
|
|
229
508
|
},
|
|
230
|
-
}
|
|
509
|
+
};
|
|
510
|
+
openKeypad(spec);
|
|
231
511
|
}
|
|
232
512
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
513
|
+
/**
|
|
514
|
+
* A circle's or arc's diameter: its keypad, or its dimension's. A curved wall's is measured across `side`, the edge
|
|
515
|
+
* picked (+1 its outer face, −1 its inner) — or, picked whole, the side a space is on (the side it was tapped on,
|
|
516
|
+
* between two), and its centerline where there's no space; the keypad can switch it.
|
|
517
|
+
*/
|
|
518
|
+
export function dimCurve(id: string, side?: 1 | -1): void {
|
|
519
|
+
const sk = SK(), wall = !!wallOf(sk, id);
|
|
520
|
+
for (const k in sk.cons) { const c = sk.cons[k]; if (c.type === 'diam' && c.c === id) { editDim(k, undefined, false, wall && side ? { face: side } : undefined); return; } }
|
|
521
|
+
let face: 0 | 1 | -1 = wall ? side ?? defaultFace(sk, id, ed.wallTap?.id === id ? ed.wallTap.side : undefined) ?? 0 : 0;
|
|
522
|
+
const now = () => measure(sk, { type: 'diam', c: id, ...(face ? { face } : {}) });
|
|
523
|
+
// (while it's being entered, what it measures across is what's highlighted: the edge, or the wall)
|
|
524
|
+
const was = ed.sel, type = sk.circles[id] ? 'circle' as const : 'arc' as const;
|
|
525
|
+
const show = () => { ed.sel = face ? [{ type: 'wface', id: edgeId(id, face) }] : [{ type, id }]; requestDraw(); };
|
|
526
|
+
show();
|
|
527
|
+
const spec: KeypadSpec = {
|
|
528
|
+
title: 'Diameter', sub: wall ? lengthSub(sk, id, face) : '', initial: fmtLen(now()), selectAll: true,
|
|
529
|
+
extras: wall ? () => curveChoices(sk, id).map(ch => ({
|
|
530
|
+
label: ch.label!, on: ch.face === face,
|
|
531
|
+
onPress: () => { face = ch.face!; spec.sub = lengthSub(sk, id, face); show(); if (keypadFresh()) keypadSetReading(now()); },
|
|
532
|
+
})) : undefined,
|
|
533
|
+
onClose: () => { ed.sel = was; requestDraw(); },
|
|
241
534
|
onDone: v => {
|
|
242
535
|
if (v <= 0) return;
|
|
243
536
|
pushHistory();
|
|
244
|
-
const r = tryAddConstraint({ type: 'diam', c: id, value: v, driving: true });
|
|
537
|
+
const r = tryAddConstraint({ type: 'diam', c: id, value: v, driving: true, ...(face ? { face } : {}) });
|
|
245
538
|
if (!r.ok) dropHistory();
|
|
539
|
+
// (as a length: the dimension made, nothing left highlighted)
|
|
540
|
+
ed.sel = [];
|
|
246
541
|
afterEdit();
|
|
247
542
|
},
|
|
248
|
-
}
|
|
543
|
+
};
|
|
544
|
+
openKeypad(spec);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** A corner's angle (two lines meeting at `pid`): its angle dimension's keypad, or a new one's. False if it isn't a corner. */
|
|
548
|
+
export function cornerAngle(pid: string, done?: () => void, modeless = false): boolean {
|
|
549
|
+
const sk = SK(), pair = cornerLines(sk, pid);
|
|
550
|
+
if (!pair) return false;
|
|
551
|
+
// edit the corner's existing angle dimension rather than stacking a second one
|
|
552
|
+
const ex = Object.keys(sk.cons).find(id => { const c = sk.cons[id]; return c.type === 'angle' && c.layer !== 'print' && ((c.l1 === pair[0] && c.l2 === pair[1]) || (c.l1 === pair[1] && c.l2 === pair[0])); });
|
|
553
|
+
if (ex) editDim(ex, done, modeless); else dimAngle(pair[0], pair[1], done, modeless);
|
|
554
|
+
return true;
|
|
249
555
|
}
|
|
250
556
|
|
|
251
|
-
export function dimAngle(l1: string, l2: string): void {
|
|
557
|
+
export function dimAngle(l1: string, l2: string, done?: () => void, modeless = false): void {
|
|
252
558
|
const c: Constraint = { type: 'angle', l1, l2, value: 0, driving: true };
|
|
253
559
|
const cur = measure(SK(), c);
|
|
560
|
+
/** The angle dimension, driving, at `v` degrees (`extra`: what it's from). */
|
|
561
|
+
const addAngle = (v: number, extra: Partial<Constraint>) => {
|
|
562
|
+
pushHistory();
|
|
563
|
+
const r = tryAddConstraint({ ...c, value: v, ...extra });
|
|
564
|
+
// a measurement of the job drives an angle, or nothing: not one the drawing already sets
|
|
565
|
+
if (r.ok && r.ref && extra.measurementRef) {
|
|
566
|
+
delete SK().cons[r.id!];
|
|
567
|
+
dropHistory();
|
|
568
|
+
toast(ANGLE_SET_ALREADY, { warn: true });
|
|
569
|
+
} else if (!r.ok) dropHistory();
|
|
570
|
+
afterEdit();
|
|
571
|
+
done?.();
|
|
572
|
+
};
|
|
254
573
|
openKeypad({
|
|
255
|
-
title: 'Angle', sub: 'Degrees', numeric: true, initial: String(Math.round(cur * 10) / 10), selectAll: true,
|
|
256
|
-
onDone: v => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
574
|
+
title: 'Angle', sub: 'Degrees', numeric: true, initial: String(Math.round(cur * 10) / 10), selectAll: true, modeless,
|
|
575
|
+
onDone: v => addAngle(v, {}),
|
|
576
|
+
// (or an angle of the job's, from its list: the corner takes it, linked to it)
|
|
577
|
+
measurements: offersJobMeasurements() ? {
|
|
578
|
+
kind: 'angle', pick: t => {
|
|
579
|
+
const v = jobValue(t, 'angle');
|
|
580
|
+
if (v == null) return;
|
|
581
|
+
closeKeypad();
|
|
582
|
+
addAngle(v, { measurementRef: linkSnapshot(t) });
|
|
583
|
+
},
|
|
584
|
+
} : undefined,
|
|
263
585
|
});
|
|
264
586
|
}
|
|
265
587
|
|
|
266
|
-
|
|
588
|
+
/**
|
|
589
|
+
* The keypad for a dimension. One measured on walls can be switched to their other faces (or centerlines) there — to
|
|
590
|
+
* `pick` straight away — taken with the value on Apply.
|
|
591
|
+
*/
|
|
592
|
+
export function editDim(id: string, done?: () => void, modeless = false, pick?: FaceChoice): void {
|
|
267
593
|
const sk = SK(), c = sk.cons[id];
|
|
268
594
|
if (!c) return;
|
|
269
595
|
ed.sel = [{ type: 'dim', id }];
|
|
270
596
|
requestDraw();
|
|
271
597
|
updateCtx();
|
|
272
598
|
const isAng = c.type === 'angle';
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
599
|
+
// a fillet's radius is kept as its diameter: shown and entered as the radius
|
|
600
|
+
const k = c.rad ? 2 : 1;
|
|
601
|
+
let face: FaceChoice | null = pick && !onChoice(c, pick) ? pick : null;
|
|
602
|
+
/** What it measures on the faces chosen (as it stands, for one that doesn't change them). */
|
|
603
|
+
const now = () => (face ? measure(sk, withFace(c, face)) : c.driving === false ? measure(sk, c) : c.value!) / k;
|
|
604
|
+
const title = c.type === 'diam' ? (c.rad ? 'Radius' : 'Diameter') : isAng ? 'Angle' : c.type === 'pldist' ? 'Offset distance' : c.line ? 'Length' : 'Distance';
|
|
605
|
+
// (which faces it's measured on: the choices beside the keys)
|
|
606
|
+
const spec: KeypadSpec = {
|
|
276
607
|
title,
|
|
277
608
|
sub: c.driving === false ? (c.layer === 'print' ? 'Print dimension (label only)' : 'Reference — not driving the shape') : 'Driving dimension',
|
|
278
|
-
numeric: isAng, initial: isAng ? String(Math.round(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
609
|
+
numeric: isAng, initial: isAng ? String(Math.round(now() * 10) / 10) : fmtLen(now()), selectAll: true, laser: !isAng, modeless,
|
|
610
|
+
// (just the faces it's measured on: Reference and Delete are the bar's, for a dimension selected)
|
|
611
|
+
extras: () => faceChoices(sk, withFace(c, face)).map(ch => ({
|
|
612
|
+
label: ch.label!, on: onChoice(withFace(c, face), ch),
|
|
613
|
+
onPress: () => { face = onChoice(c, ch) ? null : { ...ch, l2: face?.l2 ?? ch.l2 }; if (keypadFresh()) keypadSetReading(now()); },
|
|
614
|
+
})),
|
|
615
|
+
onDone: (v, capture) => { setDimValue(id, v * k, face ?? undefined, { capture }); done?.(); },
|
|
616
|
+
// a line's length or an angle: or one of the job's, from its list — the dimension takes it, linked to it (a
|
|
617
|
+
// dimension already linked to another, re-linked)
|
|
618
|
+
measurements: ((c.type === 'dist' && c.line) || isAng) && c.layer !== 'print' && offersJobMeasurements() ? {
|
|
619
|
+
kind: isAng ? 'angle' : 'length', pick: t => {
|
|
620
|
+
const v = jobValue(t, isAng ? 'angle' : 'length');
|
|
621
|
+
if (v == null) return;
|
|
622
|
+
closeKeypad();
|
|
623
|
+
setDimValue(id, v, face ?? undefined, { link: linkSnapshot(t), asked: true });
|
|
624
|
+
done?.();
|
|
625
|
+
},
|
|
626
|
+
} : undefined,
|
|
627
|
+
};
|
|
628
|
+
openKeypad(spec);
|
|
285
629
|
}
|
|
286
630
|
|
|
287
631
|
const fmtValue = (c: Constraint, v: number) => (c.type === 'angle' ? fmtAng(v) : fmtLen(v));
|
|
288
632
|
|
|
289
|
-
|
|
633
|
+
/**
|
|
634
|
+
* For a cabinet's width or height on a wall face: which of its corners to keep still while the new size is met, in
|
|
635
|
+
* the order to try — its bottom, then its top (height); its left side, then its right (width). Empty otherwise.
|
|
636
|
+
*/
|
|
637
|
+
function holdFirst(sk: ReturnType<typeof SK>, id: string): string[][] {
|
|
638
|
+
for (const cb of Object.values((sk as WallFace).cabs ?? {})) {
|
|
639
|
+
if (cb.h === id) return [[cb.bl, cb.br], [cb.tl, cb.tr]];
|
|
640
|
+
if (cb.w === id) return [[cb.bl, cb.tl], [cb.br, cb.tr]];
|
|
641
|
+
}
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Set a dimension's value — on the faces of its walls `face` says, when it's given (both, or neither). */
|
|
646
|
+
/** What else a new value for a dimension brings with it. */
|
|
647
|
+
export interface DimValueOpts {
|
|
648
|
+
/** the measurement of the job it's linked to from now on (null: it lets go of its link) */
|
|
649
|
+
link?: LinkTarget | null;
|
|
650
|
+
/** the host's record of the laser reading the value is from: kept on the dimension until it's saved to the group */
|
|
651
|
+
capture?: unknown;
|
|
652
|
+
/** the person has said to already: a linked one changes without asking */
|
|
653
|
+
asked?: boolean;
|
|
654
|
+
/** the job's own value coming in: no undo step of its own, and nothing shown if the drawing can't hold it */
|
|
655
|
+
quiet?: boolean;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** What a new value brings with it, once it's taken: a link (or none), and the laser reading it's from (or none). */
|
|
659
|
+
function takeLink(c: Constraint, o: DimValueOpts): void {
|
|
660
|
+
if (o.link) { c.measurementRef = o.link; delete c.measurementId; }
|
|
661
|
+
else if (o.link === null) delete c.measurementRef;
|
|
662
|
+
// (a reading's record is kept for saving to the group; any other value isn't a reading's)
|
|
663
|
+
if (o.capture !== undefined && !c.measurementRef) c.capture = o.capture;
|
|
664
|
+
else if (!o.quiet) delete c.capture;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Give dimension `id` value `v`, solving the drawing to it (or explaining why it can't). Whether it took — false too
|
|
669
|
+
* when the person is asked first, for a dimension linked to a measurement of the job (see editor/linkWrite.ts).
|
|
670
|
+
*/
|
|
671
|
+
export function setDimValue(id: string, v: number, face?: FaceChoice, opts: DimValueOpts = {}): boolean {
|
|
290
672
|
const sk = SK(), c = sk.cons[id];
|
|
291
|
-
if (!c) return;
|
|
292
|
-
if (c.layer === 'print') { toast('Print dimensions follow the drawing. Change a driving dimension instead.'); return; }
|
|
673
|
+
if (!c) return false;
|
|
674
|
+
if (c.layer === 'print') { toast('Print dimensions follow the drawing. Change a driving dimension instead.', { warn: true }); return false; }
|
|
675
|
+
if (c.measurementRef && opts.link === undefined && !opts.asked && !opts.quiet && changesValue(c, v)) {
|
|
676
|
+
askLinkedChange(c, v, unlink => setDimValue(id, v, face, { ...opts, asked: true, ...(unlink ? { link: null } : {}) }), opts.capture);
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
const push = opts.quiet ? () => {} : pushHistory, drop = opts.quiet ? () => {} : dropHistory;
|
|
293
680
|
if (c.driving === false) {
|
|
294
|
-
|
|
681
|
+
push();
|
|
295
682
|
delete sk.cons[id];
|
|
296
|
-
const
|
|
297
|
-
|
|
683
|
+
const n: Constraint = withFace(Object.assign({}, c, { driving: true, value: v }), face ?? null);
|
|
684
|
+
delete n.auto; // entered: a measurement now
|
|
685
|
+
takeLink(n, opts);
|
|
686
|
+
const r = tryAddConstraint(n);
|
|
687
|
+
if (!r.ok) { sk.cons[id] = c; drop(); }
|
|
688
|
+
else if (r.id) {
|
|
689
|
+
// a cabinet's size on a wall face: it's this one now
|
|
690
|
+
for (const cb of Object.values((sk as WallFace).cabs ?? {})) { if (cb.w === id) cb.w = r.id; if (cb.h === id) cb.h = r.id; }
|
|
691
|
+
}
|
|
298
692
|
afterEdit();
|
|
299
|
-
return;
|
|
693
|
+
return r.ok;
|
|
300
694
|
}
|
|
301
|
-
|
|
302
|
-
const old = c.value;
|
|
695
|
+
push();
|
|
696
|
+
const old = c.value, wasAuto = c.auto, was = overflow(sk), oldFace = faceState(c);
|
|
303
697
|
c.value = v;
|
|
304
|
-
|
|
305
|
-
|
|
698
|
+
if (face) applyFace(c, face);
|
|
699
|
+
delete c.auto; // entered: a measurement now
|
|
700
|
+
const snap = fitState(sk);
|
|
701
|
+
// a cabinet's own size grows from the side it stands on: its height upward (its foot stays on the floor), its
|
|
702
|
+
// width to the right — and only the other way if that would run past the wall
|
|
703
|
+
const holding = (hold: string[]) => {
|
|
704
|
+
// (held exactly: fixed points aren't moved by the solver at all; put back as they were after)
|
|
705
|
+
const pts = hold.map(pid => sk.points[pid]).filter(Boolean), had = pts.map(p => p.fixed);
|
|
706
|
+
pts.forEach(p => { p.fixed = true; });
|
|
707
|
+
const r = solver.solve(sk);
|
|
708
|
+
pts.forEach((p, i) => { if (had[i]) p.fixed = true; else delete p.fixed; });
|
|
709
|
+
return r;
|
|
710
|
+
};
|
|
711
|
+
let s: ReturnType<typeof solver.solve> | null = null, fit = false, solved = false;
|
|
712
|
+
// (a cabinet's own size: its sides in order; any other dimension: as it lands, then keeping each thing it ties still)
|
|
713
|
+
const own = holdFirst(sk, id), holds: (string[] | null)[] = own.length ? [...own, null] : [null, ...holdGroups(sk, c)];
|
|
714
|
+
for (const hold of holds) {
|
|
715
|
+
if (s) restoreFit(sk, snap);
|
|
716
|
+
s = hold ? holding(hold) : solver.solve(sk);
|
|
717
|
+
solved ||= s.ok;
|
|
718
|
+
if (s.ok && !pushedOut(was, overflow(sk))) { fit = true; break; }
|
|
719
|
+
}
|
|
720
|
+
s = s!;
|
|
721
|
+
// a value that would push anything past its wall (or make a wall shorter than what's on it) is refused
|
|
722
|
+
if (solved && !fit) {
|
|
723
|
+
restoreFit(sk, snap);
|
|
724
|
+
c.value = old;
|
|
725
|
+
restoreFace(c, oldFace);
|
|
726
|
+
if (wasAuto) c.auto = true;
|
|
727
|
+
drop();
|
|
728
|
+
if (!opts.quiet) toast(pastWallMsg(), { warn: true });
|
|
729
|
+
afterEdit();
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
306
732
|
if (!s.ok) {
|
|
307
|
-
|
|
733
|
+
restoreFit(sk, snap);
|
|
308
734
|
c.value = old;
|
|
309
|
-
|
|
735
|
+
restoreFace(c, oldFace);
|
|
736
|
+
if (wasAuto) c.auto = true;
|
|
737
|
+
drop();
|
|
310
738
|
const bad = s.bad.filter(cid => cid !== id && sk.cons[cid]);
|
|
311
|
-
showConflict({
|
|
739
|
+
if (!opts.quiet) showConflict({
|
|
312
740
|
title: fmtValue(c, v) + ' doesn’t fit',
|
|
313
741
|
msg: 'The other constraints can’t hold with that value. Remove one of these, or try a different number.',
|
|
314
742
|
ids: bad.slice(0, 8),
|
|
315
|
-
retry: () => setDimValue(id, v),
|
|
743
|
+
retry: () => setDimValue(id, v, face, { ...opts, asked: true }),
|
|
316
744
|
});
|
|
745
|
+
afterEdit();
|
|
746
|
+
return false;
|
|
317
747
|
}
|
|
748
|
+
takeLink(c, opts);
|
|
318
749
|
afterEdit();
|
|
750
|
+
return true;
|
|
319
751
|
}
|
|
320
752
|
|
|
321
753
|
export function toggleRef(id: string): void {
|