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