@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/select.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import { dimEnds, dimRadius, edgeOf, gridSteps, openingFrame } from '../model/planGeom';
|
|
2
|
+
import { imageById } from '../model/images';
|
|
3
|
+
import { editNoteText } from '../editor/markup';
|
|
1
4
|
import { afterEdit, resolveOpeningsNow, solveSketch, tryAddConstraint } from '../app/edit';
|
|
2
5
|
import { ed, isSel, type BoxSel, type Hit, type Snap } from '../app/state';
|
|
3
|
-
import { hyp, norm2, projT, segInter } from '../model/geom';
|
|
6
|
+
import { clamp, hyp, norm2, projT, segInter } from '../model/geom';
|
|
4
7
|
import { dropHistory, pushHistory } from '../model/history';
|
|
5
8
|
import { deep } from '../model/ids';
|
|
6
9
|
import { settings } from '../model/settings';
|
|
7
|
-
import {
|
|
8
|
-
import { connectedGeometry,
|
|
9
|
-
import {
|
|
10
|
-
import type { Note, SelItem, Sketch } from '../model/types';
|
|
10
|
+
import { openingSpan, slideGroup, type SlideGroup } from '../model/openings';
|
|
11
|
+
import { connectedGeometry, joinPoints, mergePoints, restorePos, snapshotPos } from '../model/sketch';
|
|
12
|
+
import { planSK, replaceSK, SK } from '../model/store';
|
|
13
|
+
import type { Note, SelItem, Sketch, Underlay, WallFace } from '../model/types';
|
|
14
|
+
import { cabOfPoint, cabPoints, faceEdit, openingOfPoint } from '../model/faces';
|
|
15
|
+
import { pushedOut, syncFace, type Overflow } from '../model/facesolve';
|
|
16
|
+
import { snapCabinetMove } from '../editor/face';
|
|
17
|
+
import { openCabinet, slideTargets, snapSlide } from '../editor/elevation';
|
|
18
|
+
import { elevAct, elevOpen, fitState, keepInWall, moveOpeningDrag, overflow, planFit, restoreFit, slideWithinWalls, startOpeningDrag, wallBounds, type OpeningDrag } from '../editor/elevation';
|
|
11
19
|
import { hitAny } from '../render/hit';
|
|
12
|
-
import { snapAt } from '../render/snap';
|
|
13
|
-
import { mainT, noteBox, requestDraw, S, s2w, underlayH,
|
|
20
|
+
import { onGrid, snap45, snapAt } from '../render/snap';
|
|
21
|
+
import { linkExtent, mainT, NOTE_TEXT_IN, NOTE_TEXT_MAX_IN, NOTE_TEXT_MIN_IN, noteBox, noteTextBox, noteTextIn, paintPending, requestDraw, requestImageDraw, requestOverlayDraw, S, s2w, type HandlePart, underlayAt, underlayH, underlayLocal } from '../render/view';
|
|
14
22
|
import { solver } from '../solver';
|
|
15
23
|
import type { PEv } from './registry';
|
|
16
24
|
import { onSessionReset } from '../model/session';
|
|
@@ -19,25 +27,70 @@ import { selectionHint } from '../editor/selection';
|
|
|
19
27
|
|
|
20
28
|
type Drag =
|
|
21
29
|
| { kind: 'point'; id: string; start: { x: number; y: number }; grab: { x: number; y: number }; snap: Snap | null }
|
|
22
|
-
|
|
23
|
-
| { kind: '
|
|
30
|
+
/** cab: dragging a cabinet (its box where it started): it snaps to corners, the floor, other cabinets */
|
|
31
|
+
| { kind: 'multi'; ids: string[]; start: { x: number; y: number }[]; grab: { x: number; y: number }; cab?: { id: string; box: { u0: number; u1: number; z0: number; z1: number } }; via?: string; gst?: number; gx?: number; gy?: number }
|
|
32
|
+
/** size a circle by its edge; grab: how far out from its centreline it was picked up (a wall's outer face, say) */
|
|
33
|
+
| { kind: 'radius'; id: string; start: number; grab: number }
|
|
24
34
|
| { kind: 'dimoff'; id: string }
|
|
25
35
|
/** swing a diameter's line around its circle and move the label in/out */
|
|
26
36
|
| { kind: 'diamlabel'; id: string }
|
|
27
37
|
/** slide a door/window dimension (width or a placed one) toward or away from its wall */
|
|
28
38
|
| { kind: 'odimoff'; id: string; part: string }
|
|
29
39
|
/** slide a door/window (or the free chain of openings it's locked into) along its wall; grab: s where it was picked up */
|
|
30
|
-
| { kind: 'opening'; id: string; grab: number; group: SlideGroup }
|
|
31
|
-
|
|
32
|
-
| { kind: '
|
|
33
|
-
|
|
40
|
+
| { kind: 'opening'; id: string; grab: number; group: SlideGroup; snap: { at0: number; w: number; targets: number[] }; base?: Overflow; last?: number }
|
|
41
|
+
/** the wall view: a door or window dragged on the wall (along it, and windows up and down); grab: where it was picked up */
|
|
42
|
+
| { kind: 'wopening'; drag: OpeningDrag; grab: { x: number; y: number }; stuck?: boolean; via?: string }
|
|
43
|
+
/** both: a link's tile and its leader's tip together (grabbed along its leader) */
|
|
44
|
+
| { kind: 'note'; id: string; start: Note; grab: { x: number; y: number }; both?: boolean }
|
|
45
|
+
/** several pieces of markup (and photos) selected, one of them dragged: they all move by as much */
|
|
46
|
+
| { kind: 'group'; notes: { id: string; start: Note }[]; images: { id: string; start: { x: number; y: number } }[]; grab: { x: number; y: number } }
|
|
47
|
+
| { kind: 'umove'; id: string; start: { x: number; y: number }; grab: { x: number; y: number } }
|
|
48
|
+
| { kind: 'uresize'; id: string; part: HandlePart; start: { x: number; y: number; w: number; h: number } }
|
|
34
49
|
/** reshape markup: move an arrow end, or resize the box of a cloud / pen stroke */
|
|
35
50
|
| { kind: 'nresize'; id: string; part: string; start: Note };
|
|
36
51
|
|
|
37
52
|
const RESIZE_CURSOR: Record<HandlePart, string> = { nw: 'nwse-resize', se: 'nwse-resize', ne: 'nesw-resize', sw: 'nesw-resize', n: 'ns-resize', s: 'ns-resize', e: 'ew-resize', w: 'ew-resize' };
|
|
38
53
|
|
|
39
54
|
/** `again`: the press landed on what was already the whole selection (a finger's second tap expands it). */
|
|
40
|
-
|
|
55
|
+
/** `added`: a finger or pencil press added what it landed on to the selection (it goes again if the press drags it). */
|
|
56
|
+
interface SelectState { down?: PEv; hit?: Hit | null; dragging?: boolean; drag?: Drag | null; again?: boolean; added?: boolean }
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A drag that keeps up with the pointer. Each move is solved as it comes — until a solve takes a good part of a frame
|
|
60
|
+
* (a big, well-constrained plan on a device): then moves that arrive meanwhile wait for the next frame and only the
|
|
61
|
+
* latest is solved. Solving each in turn queued them up behind the solve, and what's constrained to the dragged thing
|
|
62
|
+
* trailed further and further behind the finger. A lift solves any move still waiting first.
|
|
63
|
+
*/
|
|
64
|
+
export const dragTiming = {
|
|
65
|
+
/** a solve this slow (ms) and moves wait for the next frame; tests pin it, so a busy machine doesn't change what they see */
|
|
66
|
+
slowMs: 4,
|
|
67
|
+
};
|
|
68
|
+
const dragMove = { ev: null as PEv | null, drag: null as Drag | null, frame: 0, cost: 0 };
|
|
69
|
+
function solveDrag(d: Drag, ev: PEv): void {
|
|
70
|
+
const t0 = performance.now();
|
|
71
|
+
dragTo(d, ev);
|
|
72
|
+
dragMove.cost = performance.now() - t0;
|
|
73
|
+
}
|
|
74
|
+
function moveDrag(d: Drag, ev: PEv): void {
|
|
75
|
+
if (dragMove.cost < dragTiming.slowMs && !dragMove.frame) { solveDrag(d, ev); return; }
|
|
76
|
+
dragMove.ev = ev; dragMove.drag = d;
|
|
77
|
+
if (!dragMove.frame) dragMove.frame = requestAnimationFrame(flushDrag);
|
|
78
|
+
}
|
|
79
|
+
function flushDrag(): void {
|
|
80
|
+
const { ev, drag } = dragMove;
|
|
81
|
+
dropDrag();
|
|
82
|
+
if (!ev || !drag) return;
|
|
83
|
+
solveDrag(drag, ev);
|
|
84
|
+
// (shown in this frame: asking for one from here — itself a frame's callback — would show it a frame later)
|
|
85
|
+
paintPending();
|
|
86
|
+
}
|
|
87
|
+
function dropDrag(): void {
|
|
88
|
+
if (dragMove.frame) cancelAnimationFrame(dragMove.frame);
|
|
89
|
+
dragMove.ev = null; dragMove.drag = null; dragMove.frame = 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** What a finger or pencil can pick several of, one tap at a time. */
|
|
93
|
+
const ADDABLE = new Set(['point', 'line', 'circle', 'arc', 'opening', 'oanchor', 'cab', 'wface']);
|
|
41
94
|
|
|
42
95
|
/**
|
|
43
96
|
* The last tap, for double taps: two taps close together in time and place, with no edit between, act on what the
|
|
@@ -58,25 +111,38 @@ function doubleTapOf(at: PEv, h: Hit, now: number): Hit | null {
|
|
|
58
111
|
|
|
59
112
|
export const selectTool = {
|
|
60
113
|
st: {} as SelectState,
|
|
61
|
-
reset() { this.st = {}; ed.boxSel = null; },
|
|
114
|
+
reset() { dropDrag(); this.st = {}; ed.boxSel = null; },
|
|
62
115
|
hover(ev: PEv) {
|
|
63
116
|
if (ev.touch) return;
|
|
64
|
-
const h = hitAny(ev.sx, ev.sy, { touch: false });
|
|
117
|
+
const h = hitAny(ev.sx, ev.sy, { touch: false, faces: true }), was = ed.hover;
|
|
65
118
|
ed.hover = h && !h.weak ? h : null;
|
|
66
119
|
setCursor(!h ? 'default'
|
|
67
120
|
: h.type === 'uhandle' ? RESIZE_CURSOR[h.part as HandlePart]
|
|
68
121
|
: h.type === 'nhandle' ? RESIZE_CURSOR[h.part as HandlePart] ?? 'crosshair'
|
|
69
|
-
: h.type === '
|
|
70
|
-
: h.type === '
|
|
71
|
-
|
|
122
|
+
: h.type === 'note' && h.part === 'tip' ? 'crosshair'
|
|
123
|
+
: h.type === 'underlay' && imageById(h.id)?.locked ? 'default'
|
|
124
|
+
: h.type === 'dim' || h.type === 'odim' || h.type === 'elev' || h.type === 'cabfoot' ? 'pointer' : 'move');
|
|
125
|
+
// the highlight is part of the drawing: redraw it only when what's under the pointer changed
|
|
126
|
+
const same = was?.type === ed.hover?.type && was?.id === ed.hover?.id && was?.part === ed.hover?.part;
|
|
127
|
+
if (!same) requestDraw();
|
|
72
128
|
},
|
|
73
129
|
down(ev: PEv) {
|
|
74
|
-
const
|
|
130
|
+
const raw = hitAny(ev.sx, ev.sy, { touch: ev.touch, faces: true });
|
|
131
|
+
// (a wall picked whole: the side it was picked on, which side of it a length is measured on between two spaces)
|
|
132
|
+
if (raw?.side && (raw.type === 'line' || raw.type === 'circle' || raw.type === 'arc')) ed.wallTap = { id: raw.id, side: raw.side };
|
|
133
|
+
// (a door's or window's end, as selected: "<opening>|<edge>")
|
|
134
|
+
const h = raw && raw.type === 'oanchor' ? { ...raw, id: raw.id + '|' + raw.part } : raw;
|
|
75
135
|
const again = !!h && ed.sel.length === 1 && isSel(h.type, h.id);
|
|
76
136
|
this.st = { down: ev, hit: h, dragging: false, again };
|
|
77
|
-
if (h && h.type !== 'dim' && h.type !== 'odim' && h.type !== 'uhandle' && h.type !== 'nhandle') {
|
|
137
|
+
if (h && h.type !== 'dim' && h.type !== 'odim' && h.type !== 'elev' && h.type !== 'cabfoot' && h.type !== 'uhandle' && h.type !== 'nhandle') {
|
|
138
|
+
// (a wall and its edge are the same wall picked two ways: picking one, added to the selection, stands in for the other)
|
|
139
|
+
if ((ev.shift || ev.touch) && !isSel(h.type, h.id)) ed.sel = ed.sel.filter(s => !sameWall(s, h));
|
|
78
140
|
if (ev.shift) toggleSel(h);
|
|
79
|
-
else if (!isSel(h.type, h.id))
|
|
141
|
+
else if (!isSel(h.type, h.id)) {
|
|
142
|
+
// a finger or pencil has no Shift: tapping something else adds it (tap empty space to clear)
|
|
143
|
+
if (ev.touch && ADDABLE.has(h.type) && ed.sel.length && ed.sel.every(s => ADDABLE.has(s.type))) { ed.sel.push({ type: h.type as SelItem['type'], id: h.id }); this.st.added = true; }
|
|
144
|
+
else ed.sel = [{ type: h.type as SelItem['type'], id: h.id }];
|
|
145
|
+
}
|
|
80
146
|
updateCtx(); requestDraw();
|
|
81
147
|
}
|
|
82
148
|
},
|
|
@@ -85,35 +151,44 @@ export const selectTool = {
|
|
|
85
151
|
if (!st.down) { this.hover(ev); return; }
|
|
86
152
|
const d = hyp(ev.sx - st.down.sx, ev.sy - st.down.sy);
|
|
87
153
|
if (!st.dragging && d < (ev.touch ? 10 : 5)) return;
|
|
88
|
-
if (st.hit && st.hit.type !== 'room') {
|
|
154
|
+
if (st.hit && st.hit.type !== 'room' && st.hit.type !== 'cabfoot') {
|
|
89
155
|
if (!st.dragging) {
|
|
90
156
|
st.dragging = true;
|
|
157
|
+
// pressed to drag, not to add: just this one moves
|
|
158
|
+
if (st.added) { ed.sel = [{ type: st.hit.type as SelItem['type'], id: st.hit.id }]; updateCtx(); }
|
|
91
159
|
pushHistory();
|
|
92
160
|
st.drag = beginDrag(st.hit, st.down);
|
|
93
161
|
if (!st.drag) { dropHistory(); st.hit = null; return; }
|
|
162
|
+
// a cabinet's, door's or window's corner pressed to drag: it's the object that moves (and is selected)
|
|
163
|
+
const cab = st.hit.type === 'point' ? cabOfPoint(SK(), st.hit.id) : st.hit.type === 'line' ? SK().lines[st.hit.id]?.cab ?? null : null;
|
|
164
|
+
const op = st.hit.type === 'point' && elevOpen() ? openingOfPoint(st.hit.id) : null;
|
|
165
|
+
if ((cab || op) && ed.sel.length === 1 && isSel(st.hit.type, st.hit.id)) { ed.sel = [cab ? { type: 'cab', id: cab } : { type: 'opening', id: op! }]; updateCtx(); }
|
|
166
|
+
dropDrag(); dragMove.cost = 0;
|
|
94
167
|
}
|
|
95
|
-
|
|
168
|
+
moveDrag(st.drag!, ev);
|
|
96
169
|
} else {
|
|
97
170
|
st.dragging = true;
|
|
98
171
|
ed.boxSel = { x0: st.down.sx, y0: st.down.sy, x1: ev.sx, y1: ev.sy };
|
|
99
|
-
|
|
172
|
+
requestOverlayDraw();
|
|
100
173
|
}
|
|
101
174
|
},
|
|
102
175
|
up(ev: PEv) {
|
|
103
176
|
const st = this.st;
|
|
104
177
|
if (!st.down) return;
|
|
105
|
-
if (st.dragging && st.drag) endDrag(st.drag);
|
|
178
|
+
if (st.dragging && st.drag) { flushDrag(); endDrag(st.drag); }
|
|
106
179
|
else if (st.dragging && ed.boxSel) { boxSelect(ed.boxSel, ev.shift); ed.boxSel = null; }
|
|
107
180
|
else if (st.hit) {
|
|
108
181
|
const h = st.hit, now = performance.now();
|
|
182
|
+
// a cabinet's footprint in the plan: open its wall, the cabinet selected
|
|
183
|
+
if (h.type === 'cabfoot') { const [key, cab] = h.id.split('#'); this.st = {}; openCabinet(key, cab); return; }
|
|
109
184
|
// with a finger or pencil, tapping the selected line again selects everything joined to it (as a double tap does)
|
|
110
|
-
const first = ev.shift || h.type === 'dim' || h.type === 'odim' ? null
|
|
185
|
+
const first = ev.shift || h.type === 'dim' || h.type === 'odim' || h.type === 'elev' ? null
|
|
111
186
|
: st.down.touch && st.again ? h
|
|
112
187
|
: doubleTapOf(st.down, h, now);
|
|
113
188
|
const sk = SK();
|
|
114
189
|
// a corner point stands for the lines meeting there
|
|
115
|
-
const from = first?.type === 'point' ? Object.keys(sk.lines).filter(id => sk.lines[id].a === first.id || sk.lines[id].b === first.id) : [];
|
|
116
|
-
if (first && (first.type === 'line' || first.type === 'arc' || from.length)) {
|
|
190
|
+
const from = first?.type === 'point' ? Object.keys(sk.lines).filter(id => !sk.lines[id].anchor && (sk.lines[id].a === first.id || sk.lines[id].b === first.id)) : [];
|
|
191
|
+
if (first && ((first.type === 'line' && !sk.lines[first.id]?.anchor) || first.type === 'arc' || from.length)) {
|
|
117
192
|
// double tap a line or arc (or a point on one): select everything connected to it
|
|
118
193
|
const g = connectedGeometry(sk, first.type === 'line' ? [first.id] : from, first.type === 'arc' ? [first.id] : []);
|
|
119
194
|
ed.sel = [...g.lines.map(id => ({ type: 'line' as const, id })), ...g.arcs.map(id => ({ type: 'arc' as const, id }))];
|
|
@@ -122,10 +197,24 @@ export const selectTool = {
|
|
|
122
197
|
updateCtx(); updateHint(); requestDraw();
|
|
123
198
|
return;
|
|
124
199
|
}
|
|
200
|
+
// a finger or pencil tapping one of several selected things takes it out of the selection
|
|
201
|
+
if (st.down.touch && !st.added && !ev.shift && ed.sel.length > 1 && ADDABLE.has(h.type) && isSel(h.type, h.id)) ed.sel = ed.sel.filter(s => !(s.type === h.type && s.id === h.id));
|
|
125
202
|
// a tap on a dimension selects it; the keypad only opens from the yellow + (or Length etc.)
|
|
126
|
-
|
|
203
|
+
// (its width chip too: a dimension picked, so the + sets it — the door or window picked whole has no +)
|
|
204
|
+
else if (h.type === 'odim') ed.sel = !h.part ? [{ type: 'opening', id: h.id }] : [{ type: 'odim', id: h.id + '|' + h.part }];
|
|
205
|
+
// a wall view chip: a face dimension is selected as one; the others as the chip
|
|
206
|
+
else if (h.type === 'elev') { const a = elevAct(h.id); ed.sel = a.k === 'fdim' ? [{ type: 'dim', id: a.id }] : [{ type: 'elev', id: h.id }]; }
|
|
127
207
|
else if (h.type === 'dim') ed.sel = [{ type: 'dim', id: h.id }];
|
|
128
208
|
else if (h.type === 'room' && first?.type === 'room' && first.id === h.id) openDrawer('rooms');
|
|
209
|
+
// double tap a note (or, with a finger, tap it again): edit its text
|
|
210
|
+
else if (h.type === 'note' && first?.type === 'note' && first.id === h.id && SK().notes.find(n => n.id === h.id)?.type === 'text') {
|
|
211
|
+
ed.sel = [{ type: 'note', id: h.id }];
|
|
212
|
+
lastTap = { t: 0, x: 0, y: 0, hit: null, seq: -1 };
|
|
213
|
+
this.st = {};
|
|
214
|
+
updateCtx(); requestDraw();
|
|
215
|
+
editNoteText(h.id);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
129
218
|
lastTap = { t: now, x: st.down.sx, y: st.down.sy, hit: h, seq: ed.editSeq };
|
|
130
219
|
} else if (!ev.shift) ed.sel = [];
|
|
131
220
|
this.st = {};
|
|
@@ -139,6 +228,17 @@ export const selectTool = {
|
|
|
139
228
|
},
|
|
140
229
|
};
|
|
141
230
|
|
|
231
|
+
/** The wall a wall edge's id is on, as it's picked whole: a line, a circle or an arc. */
|
|
232
|
+
function edgeWall(sk: Sketch, id: string): { type: 'line' | 'circle' | 'arc'; id: string } {
|
|
233
|
+
return { type: sk.lines[id] ? 'line' : sk.circles[id] ? 'circle' : 'arc', id };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Whether `s` is the wall `h` is an edge of, or an edge of the wall `h` is. */
|
|
237
|
+
function sameWall(s: SelItem, h: Hit): boolean {
|
|
238
|
+
if (h.type === 'wface') return s.type !== 'wface' && s.id === edgeOf(h.id)[0];
|
|
239
|
+
return (h.type === 'line' || h.type === 'circle' || h.type === 'arc') && s.type === 'wface' && edgeOf(s.id)[0] === h.id;
|
|
240
|
+
}
|
|
241
|
+
|
|
142
242
|
function toggleSel(h: Hit): void {
|
|
143
243
|
if (isSel(h.type, h.id)) ed.sel = ed.sel.filter(s => !(s.type === h.type && s.id === h.id));
|
|
144
244
|
else ed.sel.push({ type: h.type as SelItem['type'], id: h.id });
|
|
@@ -146,12 +246,15 @@ function toggleSel(h: Hit): void {
|
|
|
146
246
|
|
|
147
247
|
/** Window select (drag right) takes fully enclosed lines; crossing select (drag left) also takes touched ones. */
|
|
148
248
|
function boxSelect(b: BoxSel, add: boolean): void {
|
|
249
|
+
// each mode selects only its own things: Annotate its markup, Draft the drawing
|
|
250
|
+
if (ed.mode === 'annotate') { boxSelectMarkup(b, add); return; }
|
|
149
251
|
const sk = SK(), T = mainT(), crossing = b.x1 < b.x0;
|
|
150
252
|
const x0 = Math.min(b.x0, b.x1), x1 = Math.max(b.x0, b.x1), y0 = Math.min(b.y0, b.y1), y1 = Math.max(b.y0, b.y1);
|
|
151
253
|
const inside = (id: string) => { const p = sk.points[id]; const [x, y] = S(T, p.x, p.y); return x >= x0 && x <= x1 && y >= y0 && y <= y1; };
|
|
152
254
|
const out: SelItem[] = add ? ed.sel.slice() : [];
|
|
153
255
|
for (const id in sk.lines) {
|
|
154
256
|
const l = sk.lines[id];
|
|
257
|
+
if (l.anchor || l.cab) continue;
|
|
155
258
|
const ia = inside(l.a), ib = inside(l.b);
|
|
156
259
|
let hit = ia && ib;
|
|
157
260
|
if (!hit && crossing) {
|
|
@@ -165,43 +268,133 @@ function boxSelect(b: BoxSel, add: boolean): void {
|
|
|
165
268
|
}
|
|
166
269
|
if (hit && !out.some(s => s.id === id)) out.push({ type: 'line', id });
|
|
167
270
|
}
|
|
271
|
+
// a cabinet: taken whole when it's inside (or, crossing, when any of it is)
|
|
272
|
+
for (const [id, cb] of Object.entries((sk as WallFace).cabs ?? {})) {
|
|
273
|
+
const ins = cabPoints(cb).map(inside);
|
|
274
|
+
if (crossing ? ins.some(Boolean) : ins.every(Boolean)) out.push({ type: 'cab', id });
|
|
275
|
+
}
|
|
276
|
+
// a door or window, the same way: by its corners on a wall face, or its two ends along the wall in the plan
|
|
277
|
+
const atScreen = (x: number, y: number) => { const [sx, sy] = S(T, x, y); return sx >= x0 && sx <= x1 && sy >= y0 && sy <= y1; };
|
|
278
|
+
const opCorners = new Map<string, boolean[]>();
|
|
279
|
+
if (elevOpen()) {
|
|
280
|
+
for (const pid in sk.points) {
|
|
281
|
+
const op = openingOfPoint(pid);
|
|
282
|
+
if (op) opCorners.set(op, [...(opCorners.get(op) ?? []), inside(pid)]);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
for (const id in sk.openings) {
|
|
286
|
+
const g = openingFrame(sk, id);
|
|
287
|
+
if (g) opCorners.set(id, [g.s0, g.s1].map(s => atScreen(g.A.x + g.ux * s, g.A.y + g.uy * s)));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (const [id, ins] of opCorners) if (crossing ? ins.some(Boolean) : ins.every(Boolean)) out.push({ type: 'opening', id });
|
|
168
291
|
for (const id in sk.circles) if (inside(sk.circles[id].c)) out.push({ type: 'circle', id });
|
|
169
292
|
for (const id in sk.arcs) if (inside(sk.arcs[id].c)) out.push({ type: 'arc', id });
|
|
170
|
-
|
|
293
|
+
const cabCorners = new Set(Object.values((sk as WallFace).cabs ?? {}).flatMap(cabPoints));
|
|
294
|
+
if (!out.length) for (const id in sk.points) if (inside(id) && !sk.points[id].origin && !sk.points[id].anchor && !cabCorners.has(id)) out.push({ type: 'point', id });
|
|
295
|
+
ed.sel = out;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Annotate's box: the markup in it (window: wholly inside; crossing: any of it inside) — never the drawing or the images. */
|
|
299
|
+
function boxSelectMarkup(b: BoxSel, add: boolean): void {
|
|
300
|
+
const T = mainT(), crossing = b.x1 < b.x0;
|
|
301
|
+
const x0 = Math.min(b.x0, b.x1), x1 = Math.max(b.x0, b.x1), y0 = Math.min(b.y0, b.y1), y1 = Math.max(b.y0, b.y1);
|
|
302
|
+
const out: SelItem[] = add ? ed.sel.filter(s => s.type === 'note') : [];
|
|
303
|
+
if (settings.layers.annot) {
|
|
304
|
+
for (const n of SK().notes) {
|
|
305
|
+
// the note's box on screen
|
|
306
|
+
let l: number, r: number, t: number, bt: number;
|
|
307
|
+
if (n.type === 'text') { const tb = noteTextBox(T, n); l = tb.x; r = tb.x + tb.w; t = tb.y; bt = tb.y + tb.h; }
|
|
308
|
+
else if (n.type === 'link') [l, t, r, bt] = linkExtent(T, n);
|
|
309
|
+
else {
|
|
310
|
+
const nb = noteBox(n);
|
|
311
|
+
if (!nb) continue;
|
|
312
|
+
const [ax, ay] = S(T, nb.left, nb.top), [bx, by] = S(T, nb.right, nb.bottom);
|
|
313
|
+
l = Math.min(ax, bx); r = Math.max(ax, bx); t = Math.min(ay, by); bt = Math.max(ay, by);
|
|
314
|
+
}
|
|
315
|
+
const hit = crossing ? l <= x1 && r >= x0 && t <= y1 && bt >= y0 : l >= x0 && r <= x1 && t >= y0 && bt <= y1;
|
|
316
|
+
if (hit && !out.some(s => s.id === n.id)) out.push({ type: 'note', id: n.id });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
171
319
|
ed.sel = out;
|
|
172
320
|
}
|
|
173
321
|
|
|
174
322
|
function beginDrag(h: Hit, down: PEv): Drag | null {
|
|
175
323
|
const sk = SK();
|
|
324
|
+
// a door's or window's end: it moves, whole, as by its body
|
|
325
|
+
if (h.type === 'oanchor') h = { type: 'opening', id: h.id.split('|')[0] };
|
|
326
|
+
// a wall's edge: the wall moves, as by its body (with the rest of the selection, when the edge is part of it) — a
|
|
327
|
+
// circle's is sized, as by its edge
|
|
328
|
+
const edgeSel = h.type === 'wface' && isSel('wface', h.id);
|
|
329
|
+
if (h.type === 'wface') h = edgeWall(sk, edgeOf(h.id)[0]);
|
|
176
330
|
const [wx, wy] = s2w(down.sx, down.sy);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
331
|
+
// pressed on one of several things selected: they all move together, by as much as it's dragged
|
|
332
|
+
const picked = edgeSel || isSel(h.type, h.id), several = picked && ed.sel.length > 1;
|
|
333
|
+
// (Annotate's: the selected markup, and photos)
|
|
334
|
+
if (several && (h.type === 'note' || h.type === 'underlay')) return groupDrag(sk, h, wx, wy);
|
|
335
|
+
// a cabinet's, door's or window's corner: it moves, whole (it keeps its size)
|
|
336
|
+
const cabOf = h.type === 'point' ? cabOfPoint(sk, h.id) : h.type === 'line' ? sk.lines[h.id]?.cab ?? null : null, opOf = h.type === 'point' && elevOpen() ? openingOfPoint(h.id) : null;
|
|
337
|
+
// (`via`: the corner it was picked up by — dropped on a point, they join)
|
|
338
|
+
const via = h.type === 'point' && (cabOf || (opOf && sk.points[h.id]?.anchor)) ? h.id : undefined;
|
|
339
|
+
if (cabOf) h = { type: 'cab', id: cabOf };
|
|
340
|
+
else if (opOf && sk.points[h.id]?.anchor) h = { type: 'opening', id: opOf };
|
|
341
|
+
/** The points that move: the whole selection's, when what's pressed is part of it (walls, lines, arcs, circles, points,
|
|
342
|
+
cabinets and spaces — doors and windows go with their walls), otherwise just its own. */
|
|
182
343
|
const ptsOf = () => {
|
|
183
344
|
const s = new Set<string>();
|
|
184
345
|
const addL = (id: string) => { const l = sk.lines[id]; if (l) { s.add(l.a); s.add(l.b); } };
|
|
185
|
-
const addArc = (id: string) => { const a = sk.arcs[id]; s.add(a.c); s.add(a.a); s.add(a.b); };
|
|
186
|
-
if (
|
|
346
|
+
const addArc = (id: string) => { const a = sk.arcs[id]; if (a) { s.add(a.c); s.add(a.a); s.add(a.b); } };
|
|
347
|
+
if (picked) {
|
|
187
348
|
for (const it of ed.sel) {
|
|
349
|
+
if (it.type === 'cab') { const cb = (sk as WallFace).cabs?.[it.id]; if (cb) cabPoints(cb).forEach(p => s.add(p)); }
|
|
188
350
|
if (it.type === 'line') addL(it.id);
|
|
351
|
+
if (it.type === 'wface') {
|
|
352
|
+
const w = edgeWall(sk, edgeOf(it.id)[0]);
|
|
353
|
+
if (w.type === 'line') addL(w.id); else if (w.type === 'arc') addArc(w.id); else if (sk.circles[w.id]) s.add(sk.circles[w.id].c);
|
|
354
|
+
}
|
|
189
355
|
if (it.type === 'point') s.add(it.id);
|
|
190
356
|
if (it.type === 'arc') addArc(it.id);
|
|
191
|
-
if (it.type === 'circle')
|
|
357
|
+
if (it.type === 'circle') { const ci = sk.circles[it.id]; if (ci) s.add(ci.c); }
|
|
358
|
+
if (it.type === 'room') { const r = sk.rooms[it.id]; if (r) r.loop.forEach(p => s.add(p)); }
|
|
192
359
|
}
|
|
193
360
|
} else {
|
|
194
361
|
if (h.type === 'line') addL(h.id);
|
|
195
362
|
if (h.type === 'arc') addArc(h.id);
|
|
196
363
|
}
|
|
197
|
-
return [...s].filter(id => !sk.points[id].fixed);
|
|
364
|
+
return [...s].filter(id => sk.points[id] && !sk.points[id].fixed);
|
|
198
365
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!ids.length) { toast('Those points are locked.'); return null; }
|
|
366
|
+
/** Those points moved together, by as much as the pointer moves. */
|
|
367
|
+
const multi = (ids: string[]): Drag | null => {
|
|
368
|
+
if (!ids.length) { toast('Those points are locked.', { warn: true }); return null; }
|
|
202
369
|
return { kind: 'multi', ids, start: ids.map(id => ({ x: sk.points[id].x, y: sk.points[id].y })), grab: { x: wx, y: wy } };
|
|
370
|
+
};
|
|
371
|
+
if (h.type === 'point') {
|
|
372
|
+
const p = sk.points[h.id];
|
|
373
|
+
if (p.fixed) { toast(p.origin ? 'The origin stays put. Lock or unlock other points from the bar below.' : p.anchor ? 'This corner is set by the plan — move the wall, door or window there.' : 'This point is locked. Unlock it to move it.', { warn: true }); return null; }
|
|
374
|
+
// (one of several selected: the selection moves, as by one of its lines)
|
|
375
|
+
if (several) return multi(ptsOf());
|
|
376
|
+
return { kind: 'point', id: h.id, start: { x: p.x, y: p.y }, grab: { x: wx, y: wy }, snap: null };
|
|
377
|
+
}
|
|
378
|
+
if (h.type === 'cab') {
|
|
379
|
+
// a cabinet moves as a whole (and the rest of the selection with it, when it's part of it)
|
|
380
|
+
const cb = (sk as WallFace).cabs?.[h.id];
|
|
381
|
+
if (!cb) return null;
|
|
382
|
+
const ids = [...new Set([...cabPoints(cb), ...(isSel('cab', h.id) ? ptsOf() : [])])].filter(id => !sk.points[id].fixed);
|
|
383
|
+
const cs = cabPoints(cb).map(p => sk.points[p]), box = { u0: Math.min(...cs.map(p => p.x)), u1: Math.max(...cs.map(p => p.x)), z0: Math.min(...cs.map(p => p.y)), z1: Math.max(...cs.map(p => p.y)) };
|
|
384
|
+
return { kind: 'multi', ids, start: ids.map(id => ({ x: sk.points[id].x, y: sk.points[id].y })), grab: { x: wx, y: wy }, cab: { id: h.id, box }, via };
|
|
385
|
+
}
|
|
386
|
+
if (h.type === 'opening' && elevOpen()) {
|
|
387
|
+
const drag = startOpeningDrag(h.id);
|
|
388
|
+
return drag ? { kind: 'wopening', drag, grab: { x: wx, y: wy }, via } : null;
|
|
389
|
+
}
|
|
390
|
+
if (h.type === 'line' && sk.lines[h.id]?.anchor) { toast('The wall’s edges are set by the plan.', { warn: true }); return null; }
|
|
391
|
+
if (h.type === 'line' || h.type === 'arc') return multi(ptsOf());
|
|
392
|
+
// a circle alone is sized by its edge; one of several selected moves with the rest
|
|
393
|
+
if (h.type === 'circle') {
|
|
394
|
+
if (several) return multi(ptsOf());
|
|
395
|
+
const ci = sk.circles[h.id], o = sk.points[ci.c];
|
|
396
|
+
return { kind: 'radius', id: h.id, start: ci.r, grab: hyp(wx - o.x, wy - o.y) - ci.r };
|
|
203
397
|
}
|
|
204
|
-
if (h.type === 'circle') return { kind: 'radius', id: h.id, start: sk.circles[h.id].r };
|
|
205
398
|
if (h.type === 'odim') {
|
|
206
399
|
// width and placed dimensions move; the hover-only wall-end distances don't (tap them to lock instead)
|
|
207
400
|
if (h.part === 'w' || (h.part || '').startsWith('d:')) return { kind: 'odimoff', id: h.id, part: h.part! };
|
|
@@ -214,40 +407,114 @@ function beginDrag(h: Hit, down: PEv): Drag | null {
|
|
|
214
407
|
return { kind: 'dimoff', id: h.id };
|
|
215
408
|
}
|
|
216
409
|
if (h.type === 'opening') {
|
|
410
|
+
// selected with walls or lines: the selection moves (a door or window goes with its wall), not the door along it
|
|
411
|
+
if (several && ed.sel.some(s => MOVE_WITH.has(s.type))) return multi(ptsOf());
|
|
217
412
|
const o = sk.openings[h.id];
|
|
218
413
|
const group = slideGroup(sk, h.id), l = sk.lines[o.line];
|
|
219
|
-
if (!group || !l) { toast('This ' + (o.type === 'door' ? 'door' : 'window') + ' is locked by a dimension. Change the dimension, or make it a reference, to move it.'); return null; }
|
|
414
|
+
if (!group || !l) { toast('This ' + (o.type === 'door' ? 'door' : 'window') + ' is locked by a dimension. Change the dimension, or make it a reference, to move it.', { warn: true }); return null; }
|
|
220
415
|
const A = sk.points[l.a], B = sk.points[l.b];
|
|
221
|
-
|
|
416
|
+
const g = openingSpan(sk, o);
|
|
417
|
+
return { kind: 'opening', id: h.id, grab: projT(A, B, { x: wx, y: wy }) * hyp(B.x - A.x, B.y - A.y), group, snap: { at0: o.at, w: g ? g.w : o.w, targets: slideTargets(sk, h.id, group) } };
|
|
418
|
+
}
|
|
419
|
+
if (h.type === 'note') {
|
|
420
|
+
const n = sk.notes.find(n => n.id === h.id)!;
|
|
421
|
+
// a link's tile: grabbed by its card, the card moves; by its leader's tip, that end; along its leader, both ends
|
|
422
|
+
// together, as a line moves
|
|
423
|
+
if (h.part === 'tip') return { kind: 'nresize', id: h.id, part: 'tip', start: deep(n) };
|
|
424
|
+
return { kind: 'note', id: h.id, start: deep(n), grab: { x: wx, y: wy }, ...(h.part === 'leader' ? { both: true } : {}) };
|
|
222
425
|
}
|
|
223
|
-
if (h.type === 'note') { const n = sk.notes.find(n => n.id === h.id)!; return { kind: 'note', id: h.id, start: deep(n), grab: { x: wx, y: wy } }; }
|
|
224
426
|
if (h.type === 'nhandle') { const n = sk.notes.find(x => x.id === h.id); return n ? { kind: 'nresize', id: h.id, part: h.part!, start: deep(n) } : null; }
|
|
225
427
|
if (h.type === 'underlay' || h.type === 'uhandle') {
|
|
226
|
-
const u =
|
|
428
|
+
const u = imageById(h.id);
|
|
227
429
|
if (!u) return null;
|
|
228
|
-
if (u.locked) { toast('This image is locked. Tap Unlock in the bar below to move or resize it.'); return null; }
|
|
229
|
-
if (h.type === 'underlay') return { kind: 'umove', start: { x: u.x, y: u.y }, grab: { x: wx, y: wy } };
|
|
230
|
-
return { kind: 'uresize', part: h.part as HandlePart, start: { x: u.x, y: u.y, w: u.w, h: underlayH(u) } };
|
|
430
|
+
if (u.locked) { toast('This image is locked. Tap Unlock in the bar below to move or resize it.', { warn: true }); return null; }
|
|
431
|
+
if (h.type === 'underlay') return { kind: 'umove', id: u.id, start: { x: u.x, y: u.y }, grab: { x: wx, y: wy } };
|
|
432
|
+
return { kind: 'uresize', id: u.id, part: h.part as HandlePart, start: { x: u.x, y: u.y, w: u.w, h: underlayH(u) } };
|
|
231
433
|
}
|
|
232
434
|
return null;
|
|
233
435
|
}
|
|
234
436
|
|
|
437
|
+
/** What's moved by its points: dragging a door or window selected with any of these moves them all. */
|
|
438
|
+
const MOVE_WITH = new Set(['line', 'arc', 'circle', 'point', 'cab', 'room']);
|
|
439
|
+
|
|
440
|
+
/** Several pieces of markup (and photos) selected, one of them pressed: they all move by as much as it's dragged. */
|
|
441
|
+
function groupDrag(sk: Sketch, h: Hit, wx: number, wy: number): Drag | null {
|
|
442
|
+
if (h.type === 'underlay' && imageById(h.id)?.locked) { toast('This image is locked. Tap Unlock in the bar below to move or resize it.', { warn: true }); return null; }
|
|
443
|
+
const notes = ed.sel.filter(s => s.type === 'note').map(s => sk.notes.find(n => n.id === s.id)).filter((n): n is Note => !!n);
|
|
444
|
+
// (a locked photo stays where it is)
|
|
445
|
+
const images = ed.sel.filter(s => s.type === 'underlay').map(s => imageById(s.id)).filter((u): u is Underlay => !!u && !u.locked);
|
|
446
|
+
return { kind: 'group', notes: notes.map(n => ({ id: n.id, start: deep(n) })), images: images.map(u => ({ id: u.id, start: { x: u.x, y: u.y } })), grab: { x: wx, y: wy } };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** How far things ran past their walls when each drag began: it may not go further. */
|
|
450
|
+
const dragBase = new WeakMap<Drag, Overflow>();
|
|
451
|
+
|
|
235
452
|
function dragTo(d: Drag, ev: PEv): void {
|
|
236
453
|
const sk = SK();
|
|
237
454
|
const [wx, wy] = s2w(ev.sx, ev.sy);
|
|
455
|
+
// a drag that would push anything past its wall (in the plan: make a wall shorter than what's on it) goes as far as
|
|
456
|
+
// it can and stops there (measured against how things were when the drag began)
|
|
457
|
+
const shapes = d.kind === 'point' || d.kind === 'multi' || d.kind === 'radius';
|
|
458
|
+
if (shapes && !dragBase.has(d)) dragBase.set(d, overflow(sk));
|
|
459
|
+
const was = dragBase.get(d) ?? {}, held = shapes ? fitState(sk) : null;
|
|
460
|
+
const fits = () => !held || !pushedOut(was, overflow(sk));
|
|
461
|
+
const keep = () => { if (!fits()) restoreFit(sk, held!); };
|
|
462
|
+
/** Drag points toward where they're going; a move that would run past (a fast one, a long way) stops at the limit, not short of it. */
|
|
463
|
+
const go = (ids: string[], to: { x: number; y: number }[]): boolean => {
|
|
464
|
+
const ok = solver.drag(sk, ids, to);
|
|
465
|
+
if (fits()) return ok;
|
|
466
|
+
const from = ids.map(id => ({ x: held!.pos.p[id][0], y: held!.pos.p[id][1] }));
|
|
467
|
+
const at = (t: number) => from.map((f, i) => ({ x: f.x + (to[i].x - f.x) * t, y: f.y + (to[i].y - f.y) * t }));
|
|
468
|
+
let lo = 0, hi = 1;
|
|
469
|
+
for (let i = 0; i < 6; i++) {
|
|
470
|
+
const m = (lo + hi) / 2;
|
|
471
|
+
restoreFit(sk, held!);
|
|
472
|
+
solver.drag(sk, ids, at(m));
|
|
473
|
+
if (fits()) lo = m; else hi = m;
|
|
474
|
+
}
|
|
475
|
+
restoreFit(sk, held!);
|
|
476
|
+
if (lo > 0) { solver.drag(sk, ids, at(lo)); overflow(sk); }
|
|
477
|
+
return ok;
|
|
478
|
+
};
|
|
238
479
|
if (d.kind === 'point') {
|
|
480
|
+
// Shift: the end of a line (or wall) goes level, plumb or 45° from the line's other end — at a corner of two, from
|
|
481
|
+
// whichever of their other ends that's nearest to where the pointer is
|
|
482
|
+
// (not a line already held level or plumb: its direction can't change)
|
|
483
|
+
const squared = (l: { a: string; b: string }) => Object.values(sk.cons).some(c => (c.type === 'h' || c.type === 'v') && ((c.a === l.a && c.b === l.b) || (c.a === l.b && c.b === l.a)));
|
|
484
|
+
const ends = ev.shift ? Object.values(sk.lines).filter(l => !l.anchor && (l.a === d.id || l.b === d.id) && !squared(l)).map(l => sk.points[l.a === d.id ? l.b : l.a]) : [];
|
|
485
|
+
if (ends.length) {
|
|
486
|
+
const q = ends.map(o => snap45(o, { x: wx, y: wy })).reduce((m, c) => (hyp(c.x - wx, c.y - wy) < hyp(m.x - wx, m.y - wy) ? c : m));
|
|
487
|
+
// where the drawing can't take it there (other constraints), it's an ordinary drag — never stuck
|
|
488
|
+
if (go([d.id], [{ x: q.x, y: q.y }])) { d.snap = null; ed.snapState = null; return; }
|
|
489
|
+
restoreFit(sk, held!);
|
|
490
|
+
}
|
|
239
491
|
const s = snapAt(ev.sx, ev.sy, { exclude: new Set([d.id]), touch: ev.touch });
|
|
240
492
|
d.snap = s.kind === 'point' || s.kind === 'mid' || s.kind === 'online' || s.kind === 'oncircle' ? s : null;
|
|
241
493
|
ed.snapState = d.snap ? s : null;
|
|
242
|
-
|
|
494
|
+
go([d.id], [{ x: s.x, y: s.y }]);
|
|
243
495
|
} else if (d.kind === 'multi') {
|
|
244
|
-
|
|
245
|
-
|
|
496
|
+
let mx = wx - d.grab.x, my = wy - d.grab.y;
|
|
497
|
+
// Shift: moved level, plumb or 45° only
|
|
498
|
+
if (ev.shift) ({ x: mx, y: my } = snap45({ x: 0, y: 0 }, { x: mx, y: my }));
|
|
499
|
+
if (d.cab && !ev.shift) [mx, my] = snapCabinetMove(d.cab.id, d.cab.box, mx, my, (ev.touch ? 16 : 10) / mainT().s);
|
|
500
|
+
// with Snap to grid, moved by whole steps of it: what's on the grid stays on it (held steady as snaps are)
|
|
501
|
+
else if (settings.snapGrid && !d.cab) {
|
|
502
|
+
const st = gridSteps(mainT().s).minor;
|
|
503
|
+
d.gx = mx = onGrid(mx, st, d.gst === st ? d.gx : undefined);
|
|
504
|
+
d.gy = my = onGrid(my, st, d.gst === st ? d.gy : undefined);
|
|
505
|
+
d.gst = st;
|
|
506
|
+
}
|
|
507
|
+
const [dx, dy] = keepInWall(sk, d.ids, d.start, mx, my);
|
|
508
|
+
go(d.ids, d.start.map(p => ({ x: p.x + dx, y: p.y + dy })));
|
|
246
509
|
} else if (d.kind === 'radius') {
|
|
247
510
|
const ci = sk.circles[d.id], o = sk.points[ci.c];
|
|
248
511
|
const snap = snapshotPos(sk);
|
|
249
|
-
|
|
512
|
+
// (where it was picked up stays under the pointer: a thick wall by its face doesn't jump to the centreline)
|
|
513
|
+
ci.r = Math.max(0.1, hyp(wx - o.x, wy - o.y) - d.grab);
|
|
514
|
+
const b = wallBounds();
|
|
515
|
+
if (b) ci.r = Math.max(0.1, Math.min(ci.r, o.x - b.x0, b.x1 - o.x, o.y - b.y0, b.y1 - o.y));
|
|
250
516
|
if (!solveSketch(['r:' + d.id]).ok) restorePos(sk, snap);
|
|
517
|
+
keep();
|
|
251
518
|
} else if (d.kind === 'odimoff') {
|
|
252
519
|
const o = sk.openings[d.id], g = o && openingSpan(sk, o);
|
|
253
520
|
if (g) {
|
|
@@ -256,70 +523,132 @@ function dragTo(d: Drag, ev: PEv): void {
|
|
|
256
523
|
else { const od = (o.dims || []).find(x => 'd:' + x.id === d.part); if (od) od.off = k; }
|
|
257
524
|
}
|
|
258
525
|
} else if (d.kind === 'diamlabel') {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
c.
|
|
264
|
-
c.off = Math.max(0, hyp(wx - o.x, wy - o.y) - r - 40 / mainT().s);
|
|
526
|
+
// (out from the face it's measured across, for one on a curved wall's face)
|
|
527
|
+
const c = sk.cons[d.id], g = dimRadius(sk, c);
|
|
528
|
+
if (g) {
|
|
529
|
+
c.ang = Math.atan2(wy - g.o.y, wx - g.o.x);
|
|
530
|
+
c.off = Math.max(0, hyp(wx - g.o.x, wy - g.o.y) - g.r - 40 / mainT().s);
|
|
265
531
|
}
|
|
266
532
|
} else if (d.kind === 'dimoff') {
|
|
267
|
-
|
|
533
|
+
// (off the line it's drawn from: a wall's face, for a length measured on one)
|
|
534
|
+
const c = sk.cons[d.id], e = dimEnds(sk, c), a = e ? e.a : sk.points[c.a!], b = e ? e.b : sk.points[c.b!];
|
|
268
535
|
const n = norm2(-(b.y - a.y), b.x - a.x);
|
|
269
536
|
c.off = (wx - (a.x + b.x) / 2) * n[0] + (wy - (a.y + b.y) / 2) * n[1];
|
|
537
|
+
} else if (d.kind === 'wopening') {
|
|
538
|
+
// a locked door won't move: say so once, then nothing
|
|
539
|
+
if (d.stuck) return;
|
|
540
|
+
if (!moveOpeningDrag(d.drag, wx - d.grab.x, wy - d.grab.y, (ev.touch ? 16 : 10) / mainT().s)) { d.stuck = true; return; }
|
|
270
541
|
} else if (d.kind === 'opening') {
|
|
271
542
|
const o = sk.openings[d.id], l = sk.lines[o.line], A = sk.points[l.a], B = sk.points[l.b];
|
|
272
|
-
|
|
543
|
+
const delta = projT(A, B, { x: wx, y: wy }) * hyp(B.x - A.x, B.y - A.y) - d.grab;
|
|
544
|
+
// what's drawn on the wall's faces follows as it slides — a cabinet tied to a window can't be pushed off the
|
|
545
|
+
// wall: the slide stops where it would be (as dragging the window in the wall view does)
|
|
546
|
+
d.base ??= planFit(sk);
|
|
547
|
+
d.last = slideWithinWalls(sk, d.group, snapSlide(d.snap.at0, d.snap.w, delta, d.snap.targets, (ev.touch ? 16 : 10) / mainT().s), d.last ?? 0, d.base);
|
|
273
548
|
resolveOpeningsNow();
|
|
274
549
|
} else if (d.kind === 'note') {
|
|
275
550
|
const n = sk.notes.find(n => n.id === d.id)!;
|
|
276
551
|
const dx = wx - d.grab.x, dy = wy - d.grab.y;
|
|
277
552
|
if (n.pts) n.pts = d.start.pts!.map(p => [p[0] + dx, p[1] + dy] as [number, number]);
|
|
278
553
|
else { n.x = d.start.x! + dx; n.y = d.start.y! + dy; }
|
|
554
|
+
if (d.both && d.start.leader) n.leader = [d.start.leader[0] + dx, d.start.leader[1] + dy];
|
|
555
|
+
} else if (d.kind === 'group') {
|
|
556
|
+
let dx = wx - d.grab.x, dy = wy - d.grab.y;
|
|
557
|
+
// Shift: moved level, plumb or 45° only
|
|
558
|
+
if (ev.shift) ({ x: dx, y: dy } = snap45({ x: 0, y: 0 }, { x: dx, y: dy }));
|
|
559
|
+
for (const g of d.notes) {
|
|
560
|
+
const n = sk.notes.find(x => x.id === g.id);
|
|
561
|
+
if (!n) continue;
|
|
562
|
+
if (n.pts) n.pts = g.start.pts!.map(p => [p[0] + dx, p[1] + dy] as [number, number]);
|
|
563
|
+
else { n.x = g.start.x! + dx; n.y = g.start.y! + dy; }
|
|
564
|
+
// (a link's leader with it: what it points at is moving too)
|
|
565
|
+
if (g.start.leader) n.leader = [g.start.leader[0] + dx, g.start.leader[1] + dy];
|
|
566
|
+
}
|
|
567
|
+
for (const g of d.images) { const u = imageById(g.id); if (u) { u.x = g.start.x + dx; u.y = g.start.y + dy; } }
|
|
279
568
|
} else if (d.kind === 'umove') {
|
|
280
|
-
const u =
|
|
569
|
+
const u = imageById(d.id);
|
|
570
|
+
if (!u) return;
|
|
281
571
|
u.x = d.start.x + wx - d.grab.x;
|
|
282
572
|
u.y = d.start.y + wy - d.grab.y;
|
|
283
573
|
} else if (d.kind === 'nresize') {
|
|
284
|
-
|
|
574
|
+
if (d.part === 'tip') {
|
|
575
|
+
// a leader points at the drawing: its tip snaps to it, as a line's end does
|
|
576
|
+
const n = sk.notes.find(x => x.id === d.id), s = snapAt(ev.sx, ev.sy, { touch: ev.touch });
|
|
577
|
+
ed.snapState = s.kind === 'free' ? null : s;
|
|
578
|
+
if (n) n.leader = [s.x, s.y];
|
|
579
|
+
} else reshapeNote(d.id, d.part, d.start, wx, wy);
|
|
285
580
|
} else if (d.kind === 'uresize') {
|
|
286
|
-
resizeUnderlay(d.part, d.start, wx, wy, ev.shift);
|
|
581
|
+
resizeUnderlay(d.id, d.part, d.start, wx, wy, ev.shift);
|
|
287
582
|
}
|
|
288
583
|
resolveOpeningsNow();
|
|
289
|
-
|
|
584
|
+
// on a wall face, a door or window tied to what's dragged (a dimension to a cabinet) moves on the face as it's
|
|
585
|
+
// solved, but it's drawn from the plan: bring the plan along at each step — as dragging the door or window itself
|
|
586
|
+
// does — rather than only when the drag ends
|
|
587
|
+
if (shapes && faceEdit.key) syncFace(planSK(), faceEdit.key);
|
|
588
|
+
// (a photo moved or sized: only the photos are drawn again — the drawing over them hasn't changed)
|
|
589
|
+
if (d.kind === 'umove' || d.kind === 'uresize') requestImageDraw(); else requestDraw();
|
|
290
590
|
}
|
|
291
591
|
|
|
292
592
|
/** Finish a drag: snapping onto geometry turns into a join or a constraint (when auto-constrain is on). */
|
|
293
593
|
function endDrag(d: Drag): void {
|
|
294
594
|
const sk = SK();
|
|
295
595
|
ed.snapState = null;
|
|
596
|
+
if (d.kind === 'wopening' && !d.drag.started) dropHistory(); // it didn't move
|
|
296
597
|
if (d.kind === 'point' && d.snap && settings.autoCons) {
|
|
297
598
|
const s = d.snap;
|
|
298
599
|
if (s.kind === 'point') {
|
|
299
600
|
const snap = JSON.stringify(sk);
|
|
300
601
|
if (mergePoints(sk, s.ref!, d.id)) {
|
|
301
602
|
const r = solveSketch();
|
|
302
|
-
if (!r.ok) {
|
|
603
|
+
if (!r.ok) { replaceSK(JSON.parse(snap) as Sketch); toast('Can’t join those points — it conflicts with other constraints.', { warn: true }); }
|
|
303
604
|
else { ed.sel = [{ type: 'point', id: s.ref! }]; toast('Points joined'); }
|
|
304
605
|
}
|
|
305
|
-
} else if (s.kind === 'mid') tryAddConstraint({ type: 'mid', p: d.id, l: s.ref! }, { silent:
|
|
606
|
+
} else if (s.kind === 'mid') tryAddConstraint({ type: sk.lines[s.ref!]?.anchor ? 'online' : 'mid', p: d.id, l: s.ref! }, { silent: !!sk.lines[s.ref!]?.anchor }); // (on a wall face's own edge: on it, free to slide)
|
|
306
607
|
else if (s.kind === 'online') tryAddConstraint({ type: 'online', p: d.id, l: s.ref! }, { silent: true });
|
|
307
608
|
else if (s.kind === 'oncircle') tryAddConstraint({ type: 'oncircle', p: d.id, c: s.ref! }, { silent: true });
|
|
308
609
|
}
|
|
309
610
|
if (d.kind === 'point') {
|
|
310
611
|
const p = SK().points[d.id];
|
|
311
|
-
if (p && hyp(p.x - d.start.x, p.y - d.start.y) < 1e-6 && !d.snap && ed.ana.det.has(d.id)) toast('That point is fully defined. Change a dimension to move it.');
|
|
612
|
+
if (p && hyp(p.x - d.start.x, p.y - d.start.y) < 1e-6 && !d.snap && ed.ana.det.has(d.id)) toast('That point is fully defined. Change a dimension to move it.', { warn: true });
|
|
312
613
|
}
|
|
313
614
|
afterEdit();
|
|
615
|
+
// a cabinet, door or window picked up by its corner and dropped with that corner on a point: they join, as points do
|
|
616
|
+
if ((d.kind === 'multi' || d.kind === 'wopening') && d.via && settings.autoCons) joinDropped(d.via);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Join corner `via` (a cabinet's, door's or window's, just dropped) to the point it landed on, if any. */
|
|
620
|
+
function joinDropped(via: string): void {
|
|
621
|
+
const sk = SK(), P = sk.points[via];
|
|
622
|
+
if (!P) return;
|
|
623
|
+
const op = openingOfPoint(via), cab = cabOfPoint(sk, via);
|
|
624
|
+
const own = (id: string) => (op && id.startsWith('@o:' + op + ':')) || (cab && cabOfPoint(sk, id) === cab);
|
|
625
|
+
const tol = (ed.lastPointerTouch ? 16 : 10) / mainT().s;
|
|
626
|
+
let best: string | null = null, bd = tol;
|
|
627
|
+
for (const [id, q] of Object.entries(sk.points)) {
|
|
628
|
+
if (id === via || own(id) || (q.anchor && q.fixed && P.fixed)) continue;
|
|
629
|
+
const d = hyp(q.x - P.x, q.y - P.y);
|
|
630
|
+
if (d < bd) { bd = d; best = id; }
|
|
631
|
+
}
|
|
632
|
+
if (!best) return;
|
|
633
|
+
// (part of the drag's undo step)
|
|
634
|
+
const snap = JSON.stringify(sk);
|
|
635
|
+
if (!joinPoints(sk, via, best)) return;
|
|
636
|
+
if (!solveSketch().ok) { replaceSK(JSON.parse(snap) as Sketch); afterEdit(); toast('Can’t join those points — it conflicts with other constraints.', { warn: true }); return; }
|
|
637
|
+
toast('Points joined');
|
|
638
|
+
afterEdit();
|
|
314
639
|
}
|
|
315
640
|
|
|
316
641
|
/**
|
|
317
642
|
* Resize the image underlay from a handle. Corners scale proportionally about the opposite corner
|
|
318
643
|
* (hold Shift for a free corner); side handles stretch one direction about the opposite side.
|
|
319
644
|
*/
|
|
320
|
-
function resizeUnderlay(part: HandlePart, s0: { x: number; y: number; w: number; h: number },
|
|
321
|
-
const u =
|
|
322
|
-
|
|
645
|
+
function resizeUnderlay(id: string, part: HandlePart, s0: { x: number; y: number; w: number; h: number }, ptx: number, pty: number, free: boolean): void {
|
|
646
|
+
const u = imageById(id), min = 8 / mainT().s;
|
|
647
|
+
if (!u) return;
|
|
648
|
+
// worked in the image's own frame (from its middle as the drag began, unturned), so a turned image resizes along its
|
|
649
|
+
// own sides — and put back turned, the side or corner opposite the handle staying where it was
|
|
650
|
+
const was: Underlay = { ...u, x: s0.x, y: s0.y, w: s0.w, h: s0.h }, { x: wx, y: wy } = underlayLocal(was, ptx, pty);
|
|
651
|
+
const left = -s0.w / 2, right = s0.w / 2, top = s0.h / 2, bottom = -s0.h / 2;
|
|
323
652
|
const W = part.includes('w'), E = part.includes('e'), N = part.includes('n'), Sth = part.includes('s');
|
|
324
653
|
let w = s0.w, h = s0.h;
|
|
325
654
|
if (part.length === 2 && !free) {
|
|
@@ -335,10 +664,29 @@ function resizeUnderlay(part: HandlePart, s0: { x: number; y: number; w: number;
|
|
|
335
664
|
if (N) h = Math.max(min, wy - bottom);
|
|
336
665
|
if (Sth) h = Math.max(min, top - wy);
|
|
337
666
|
}
|
|
667
|
+
const mid = underlayAt(was, (W ? right - w : left) + w / 2, (N ? bottom + h : top) - h / 2);
|
|
338
668
|
u.w = w;
|
|
339
669
|
u.h = h;
|
|
340
|
-
u.x =
|
|
341
|
-
u.y =
|
|
670
|
+
u.x = mid.x - w / 2;
|
|
671
|
+
u.y = mid.y + h / 2;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Scale a note from a corner: the opposite corner stays put and the note grows or shrinks evenly (text, padding and
|
|
676
|
+
* corners together) as the corner follows the pointer along the box's diagonal.
|
|
677
|
+
*/
|
|
678
|
+
function scaleText(n: Note, part: string, start: Note, wx: number, wy: number): void {
|
|
679
|
+
const T = mainT(), b = noteTextBox(T, start);
|
|
680
|
+
const xs = { w: b.x, e: b.x + b.w }, ys = { n: b.y, s: b.y + b.h };
|
|
681
|
+
const corner = { x: xs[part[1] as 'w' | 'e'], y: ys[part[0] as 'n' | 's'] };
|
|
682
|
+
const fixed = { x: part[1] === 'w' ? xs.e : xs.w, y: part[0] === 'n' ? ys.s : ys.n };
|
|
683
|
+
const [px, py] = S(T, wx, wy), dx = corner.x - fixed.x, dy = corner.y - fixed.y;
|
|
684
|
+
const along = ((px - fixed.x) * dx + (py - fixed.y) * dy) / (dx * dx + dy * dy || 1);
|
|
685
|
+
// its size on the drawing (to the quarter inch), which then holds at any zoom
|
|
686
|
+
const was = noteTextIn(start), ts = Math.round(clamp(was * along, NOTE_TEXT_MIN_IN, NOTE_TEXT_MAX_IN) * 4) / 4, k = ts / was;
|
|
687
|
+
[n.x, n.y] = s2w(fixed.x + (b.x - fixed.x) * k, fixed.y + (b.mid - fixed.y) * k);
|
|
688
|
+
delete n.size;
|
|
689
|
+
if (ts === NOTE_TEXT_IN) delete n.ts; else n.ts = ts;
|
|
342
690
|
}
|
|
343
691
|
|
|
344
692
|
/**
|
|
@@ -347,6 +695,7 @@ function resizeUnderlay(part: HandlePart, s0: { x: number; y: number; w: number;
|
|
|
347
695
|
*/
|
|
348
696
|
function reshapeNote(id: string, part: string, start: Note, wx: number, wy: number): void {
|
|
349
697
|
const n = SK().notes.find(x => x.id === id);
|
|
698
|
+
if (n?.type === 'text') { scaleText(n, part, start, wx, wy); return; }
|
|
350
699
|
if (!n || !n.pts || !start.pts) return;
|
|
351
700
|
if (part[0] === 'p') { const i = +part.slice(1); n.pts = start.pts.map((p, k) => (k === i ? [wx, wy] : [p[0], p[1]]) as [number, number]); return; }
|
|
352
701
|
const b = noteBox(start)!, min = 4 / mainT().s;
|