@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/render/draw.js
CHANGED
|
@@ -1,33 +1,63 @@
|
|
|
1
1
|
import { ed, isSel } from '../app/state';
|
|
2
|
-
import { curveDefined, isDim, lineDefined, measure } from '../model/constraints';
|
|
2
|
+
import { consPoints, curveDefined, isDim, lineDefined, measure } from '../model/constraints';
|
|
3
3
|
import { hyp, norm2, normAng, projT } from '../model/geom';
|
|
4
|
-
import { drivingDim, edgePoint, measureOpeningDim, refPoint, refS } from '../model/openings';
|
|
5
|
-
import { arcAngles, gridSteps, OPENING_TICK_EXT, openingFrame, outsideSide } from '../model/planGeom';
|
|
4
|
+
import { drivingDim, edgePoint, measureOpeningDim, refPoint, refS, widthEntered } from '../model/openings';
|
|
5
|
+
import { arcAngles, curveFrame, dimEnds, dimRadius, edgeOf, gridSteps, OPENING_TICK_EXT, openingFrame, outsideSide, wallThick } from '../model/planGeom';
|
|
6
|
+
import { faceCorner } from '../model/wallfaces';
|
|
6
7
|
import { roomGeom } from '../model/rooms';
|
|
8
|
+
import { wallPieces } from '../model/wallshape';
|
|
9
|
+
import { cabinetFootprints } from '../model/facesolve';
|
|
7
10
|
import { settings } from '../model/settings';
|
|
8
|
-
import { curveInfo,
|
|
9
|
-
import { page, SK } from '../model/store';
|
|
11
|
+
import { curveInfo, frameMemo, lineBetween, roomValid, withLineIndex } from '../model/sketch';
|
|
12
|
+
import { page, planSK, SK } from '../model/store';
|
|
13
|
+
import { elev, elevOpen } from '../editor/elevation';
|
|
14
|
+
import { cabPoints } from '../model/faces';
|
|
15
|
+
import { drawKeyPlan, drawWallBackdrop, drawWallChips, drawWallSelection } from './elevation';
|
|
10
16
|
import { fmtAng, fmtArea, fmtLen } from '../units';
|
|
11
17
|
import { FONT, TH } from './theme';
|
|
12
|
-
import { cvs, imgCache, mainT, noteHandles, S, sketchBounds, underlayH, underlayHandles } from './view';
|
|
18
|
+
import { cvs, imgCache, keyPlanSize, mainT, nextThumbFrame, noteFont, noteHandles, noteTextBox, S, sketchBounds, underlayH, underlayHandles, underlayMid } from './view';
|
|
19
|
+
import { dashFor, isSegment, NOTE_TEXT_SIZE, noteLook, shapeOutline } from '../model/markup';
|
|
20
|
+
import { strokeMarkup, strokeOutline } from './markup';
|
|
21
|
+
import { drawLink } from './linkTile';
|
|
22
|
+
import { isLink, linkInfoOf } from '../model/links';
|
|
23
|
+
import { smoothRectPath } from './squircle';
|
|
13
24
|
/** Tap targets registered while drawPlan() paints (null outside it: thumbnails and the elevation register none). */
|
|
14
25
|
let hits = null;
|
|
15
26
|
const addHit = (h) => { if (hits)
|
|
16
27
|
hits.push(h); };
|
|
17
|
-
|
|
28
|
+
/** A rounded rectangle's path, its corners smoothed as Figma's 60% ("iOS"): render/squircle.ts. */
|
|
18
29
|
export function rr(c, x, y, w, h, r) {
|
|
30
|
+
// (one shape for each size, moved into place where the engine can: hundreds of these are drawn every frame)
|
|
31
|
+
if (c.shape)
|
|
32
|
+
c.shape('rr ' + w + ' ' + h + ' ' + r, x, y, (cc, X, Y) => smoothRectPath(cc, X, Y, w, h, r));
|
|
33
|
+
else
|
|
34
|
+
smoothRectPath(c, x, y, w, h, r);
|
|
35
|
+
}
|
|
36
|
+
/** Blue for outlined (linked) chips: the lighter blue on a dark canvas, so the text stays readable. */
|
|
37
|
+
export function linkBlue() {
|
|
38
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(TH.bg);
|
|
39
|
+
const dark = !!m && (parseInt(m[1], 16) * 299 + parseInt(m[2], 16) * 587 + parseInt(m[3], 16) * 114) / 1000 < 128;
|
|
40
|
+
return dark ? TH.lblue : TH.blue;
|
|
41
|
+
}
|
|
42
|
+
/** A small chain-link mark centred on (x, y). */
|
|
43
|
+
export function linkMark(c, x, y, color) {
|
|
44
|
+
c.save();
|
|
45
|
+
c.translate(x, y);
|
|
46
|
+
c.rotate(-Math.PI / 4);
|
|
47
|
+
c.strokeStyle = color;
|
|
48
|
+
c.lineWidth = 1.4;
|
|
19
49
|
c.beginPath();
|
|
20
|
-
c.
|
|
21
|
-
c.
|
|
22
|
-
c.
|
|
23
|
-
c.
|
|
24
|
-
c.arcTo(x, y, x + w, y, r);
|
|
25
|
-
c.closePath();
|
|
50
|
+
c.roundRect(-6.5, -2.25, 7.5, 4.5, 2.25);
|
|
51
|
+
c.roundRect(-1, -2.25, 7.5, 4.5, 2.25);
|
|
52
|
+
c.stroke();
|
|
53
|
+
c.restore();
|
|
26
54
|
}
|
|
55
|
+
/** What a link mark takes before a chip's text. */
|
|
56
|
+
const MARK_W = 14;
|
|
27
57
|
/** Rounded label on the canvas; registers a hit region when `kind` is given. */
|
|
28
|
-
export function chip(c, text, cx, cy, { bg, fg, border, font = '600 12px', pad = 7, h = 22, minW = 0, kind, id, rot = 0 }) {
|
|
58
|
+
export function chip(c, text, cx, cy, { bg, fg, border, font = '600 12px', pad = 7, h = 22, minW = 0, kind, id, rot = 0, mark }) {
|
|
29
59
|
c.font = font + ' ' + FONT;
|
|
30
|
-
const w = Math.max(minW, c.measureText(text).width + pad * 2);
|
|
60
|
+
const mw = mark ? MARK_W : 0, w = Math.max(minW, c.measureText(text).width + pad * 2 + mw);
|
|
31
61
|
const x = cx - w / 2, y = cy - h / 2;
|
|
32
62
|
c.save();
|
|
33
63
|
if (rot) {
|
|
@@ -46,7 +76,9 @@ export function chip(c, text, cx, cy, { bg, fg, border, font = '600 12px', pad =
|
|
|
46
76
|
c.fillStyle = fg;
|
|
47
77
|
c.textAlign = 'center';
|
|
48
78
|
c.textBaseline = 'middle';
|
|
49
|
-
c.fillText(text, cx, cy + 0.5);
|
|
79
|
+
c.fillText(text, cx + mw / 2, cy + 0.5);
|
|
80
|
+
if (mark)
|
|
81
|
+
linkMark(c, x + pad + 4.5, cy, fg);
|
|
50
82
|
c.restore();
|
|
51
83
|
if (kind) {
|
|
52
84
|
const ext = rot ? Math.max(w, h) / 2 : 0;
|
|
@@ -54,44 +86,51 @@ export function chip(c, text, cx, cy, { bg, fg, border, font = '600 12px', pad =
|
|
|
54
86
|
}
|
|
55
87
|
return w;
|
|
56
88
|
}
|
|
57
|
-
|
|
89
|
+
/** The grid over the screen, and `m` px past each edge (see KeepOpts). */
|
|
90
|
+
function drawGrid(c, T, m = 0) {
|
|
58
91
|
const { W, H } = cvs;
|
|
59
92
|
const { minor, major } = gridSteps(T.s);
|
|
60
|
-
const x0 = (
|
|
93
|
+
const x0 = (-m - T.ox) / T.s, x1 = (W + m - T.ox) / T.s, y1 = (T.oy + m) / T.s, y0 = (T.oy - H - m) / T.s;
|
|
61
94
|
const lines = (step, color, w) => {
|
|
62
|
-
|
|
95
|
+
// (too dense to draw: judged on the screen's width, whatever is drawn past it)
|
|
96
|
+
if (W / T.s / step > 600)
|
|
63
97
|
return;
|
|
64
98
|
c.beginPath();
|
|
65
99
|
c.strokeStyle = color;
|
|
66
100
|
c.lineWidth = w;
|
|
67
101
|
for (let x = Math.floor(x0 / step) * step; x <= x1; x += step) {
|
|
68
102
|
const sx = Math.round(x * T.s + T.ox) + 0.5;
|
|
69
|
-
c.moveTo(sx,
|
|
70
|
-
c.lineTo(sx, H);
|
|
103
|
+
c.moveTo(sx, -m);
|
|
104
|
+
c.lineTo(sx, H + m);
|
|
71
105
|
}
|
|
72
106
|
for (let y = Math.floor(y0 / step) * step; y <= y1; y += step) {
|
|
73
107
|
const sy = Math.round(-y * T.s + T.oy) + 0.5;
|
|
74
|
-
c.moveTo(
|
|
75
|
-
c.lineTo(W, sy);
|
|
108
|
+
c.moveTo(-m, sy);
|
|
109
|
+
c.lineTo(W + m, sy);
|
|
76
110
|
}
|
|
77
111
|
c.stroke();
|
|
78
112
|
};
|
|
79
113
|
lines(minor, TH.grid, 1);
|
|
80
114
|
lines(major, TH.grid2, 1);
|
|
81
115
|
}
|
|
116
|
+
/** The page's photos and plans, in order (the last on top). */
|
|
82
117
|
function drawUnderlay(c, T) {
|
|
83
|
-
|
|
84
|
-
if (!u || !settings.layers.underlay)
|
|
118
|
+
if (!settings.layers.underlay)
|
|
85
119
|
return;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
for (const u of page().underlays) {
|
|
121
|
+
const im = imgCache(u.src);
|
|
122
|
+
if (!im || !im.complete || !im.naturalWidth)
|
|
123
|
+
continue;
|
|
124
|
+
// drawn about its middle, turned as it's set (clockwise as seen: the canvas turns that way)
|
|
125
|
+
const h = underlayH(u), m = underlayMid(u), [x, y] = S(T, m.x, m.y), w = u.w * T.s, hh = h * T.s;
|
|
126
|
+
c.save();
|
|
127
|
+
c.globalAlpha = u.opacity ?? 0.6;
|
|
128
|
+
c.translate(x, y);
|
|
129
|
+
if (u.rot)
|
|
130
|
+
c.rotate((u.rot * Math.PI) / 180);
|
|
131
|
+
c.drawImage(im, -w / 2, -hh / 2, w, hh);
|
|
132
|
+
c.restore();
|
|
133
|
+
}
|
|
95
134
|
}
|
|
96
135
|
const inkFor = (defined, constr) => (constr ? TH.constr : defined ? TH.def : TH.ink);
|
|
97
136
|
function drawRooms(c, T, sk, thumb) {
|
|
@@ -122,33 +161,6 @@ function drawRooms(c, T, sk, thumb) {
|
|
|
122
161
|
addHit({ kind: 'room', id, x: cx - w / 2, y: cy - 20, w, h: 40 });
|
|
123
162
|
}
|
|
124
163
|
}
|
|
125
|
-
function wallDegree(sk) {
|
|
126
|
-
const deg = {};
|
|
127
|
-
for (const id in sk.lines) {
|
|
128
|
-
const l = sk.lines[id];
|
|
129
|
-
if (l.kind !== 'wall')
|
|
130
|
-
continue;
|
|
131
|
-
deg[l.a] = (deg[l.a] || 0) + 1;
|
|
132
|
-
deg[l.b] = (deg[l.b] || 0) + 1;
|
|
133
|
-
}
|
|
134
|
-
for (const id in sk.arcs) {
|
|
135
|
-
const a = sk.arcs[id];
|
|
136
|
-
if (a.kind !== 'wall')
|
|
137
|
-
continue;
|
|
138
|
-
deg[a.a] = (deg[a.a] || 0) + 1;
|
|
139
|
-
deg[a.b] = (deg[a.b] || 0) + 1;
|
|
140
|
-
}
|
|
141
|
-
return deg;
|
|
142
|
-
}
|
|
143
|
-
/** Wall centerline path, extended at joints by half the thickness so corners close. */
|
|
144
|
-
function wallPath(c, T, sk, l, deg, extra) {
|
|
145
|
-
const A = sk.points[l.a], B = sk.points[l.b];
|
|
146
|
-
const dx = B.x - A.x, dy = B.y - A.y, L = hyp(dx, dy) || 1e-9, ux = dx / L, uy = dy / L, e = l.thick / 2 + (extra || 0);
|
|
147
|
-
const ea = deg[l.a] > 1 ? e : 0, eb = deg[l.b] > 1 ? e : 0;
|
|
148
|
-
const [x1, y1] = S(T, A.x - ux * ea, A.y - uy * ea), [x2, y2] = S(T, B.x + ux * eb, B.y + uy * eb);
|
|
149
|
-
c.moveTo(x1, y1);
|
|
150
|
-
c.lineTo(x2, y2);
|
|
151
|
-
}
|
|
152
164
|
function curveWalls(sk) {
|
|
153
165
|
const out = [];
|
|
154
166
|
const show = (constr) => !(constr && !settings.layers.construction);
|
|
@@ -175,57 +187,209 @@ function curveWallPath(c, T, sk, cw) {
|
|
|
175
187
|
c.arc(x, y, Math.max(0.5, g.r * T.s), -g.t0, -g.t1, true);
|
|
176
188
|
}
|
|
177
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Cabinets in the plan: each one's footprint against its wall, from the wall's view (read-only — tap one to open its
|
|
192
|
+
* wall). Drawn as cabinetry, not a shape to edit: base cabinets hatched, with a bold front where the doors face the room
|
|
193
|
+
* and their name when there's room for it; wall (upper) cabinets dashed with a cross, as drafted.
|
|
194
|
+
*/
|
|
195
|
+
function drawCabinetFootprints(c, T, sk) {
|
|
196
|
+
const fps = cabinetFootprints(sk);
|
|
197
|
+
if (!fps.length)
|
|
198
|
+
return;
|
|
199
|
+
for (const fp of fps) {
|
|
200
|
+
// pts: along the wall face (0 → 1), then the front, out in the room (2 → 3)
|
|
201
|
+
const q = fp.pts.map(p => S(T, p.x, p.y));
|
|
202
|
+
const path = () => { c.beginPath(); q.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); };
|
|
203
|
+
const hov = ed.hover?.type === 'cabfoot' && ed.hover.id === fp.key + '#' + fp.id;
|
|
204
|
+
c.save();
|
|
205
|
+
if (fp.upper) {
|
|
206
|
+
path();
|
|
207
|
+
c.setLineDash([6, 4]);
|
|
208
|
+
c.strokeStyle = TH.ink2;
|
|
209
|
+
c.lineWidth = hov ? 2 : 1.2;
|
|
210
|
+
c.stroke();
|
|
211
|
+
c.beginPath();
|
|
212
|
+
c.moveTo(...q[0]);
|
|
213
|
+
c.lineTo(...q[2]);
|
|
214
|
+
c.moveTo(...q[1]);
|
|
215
|
+
c.lineTo(...q[3]);
|
|
216
|
+
c.lineWidth = 1;
|
|
217
|
+
c.stroke();
|
|
218
|
+
c.restore();
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
path();
|
|
222
|
+
c.fillStyle = hov ? 'rgba(128,128,128,.30)' : 'rgba(128,128,128,.16)';
|
|
223
|
+
c.fill();
|
|
224
|
+
// hatching, clipped to the footprint
|
|
225
|
+
c.save();
|
|
226
|
+
path();
|
|
227
|
+
c.clip();
|
|
228
|
+
const xs = q.map(p => p[0]), ys = q.map(p => p[1]), x0 = Math.min(...xs), x1 = Math.max(...xs), y0 = Math.min(...ys), y1 = Math.max(...ys);
|
|
229
|
+
c.strokeStyle = TH.ink2;
|
|
230
|
+
c.globalAlpha = 0.45;
|
|
231
|
+
c.lineWidth = 1;
|
|
232
|
+
c.beginPath();
|
|
233
|
+
for (let k = x0 - (y1 - y0); k < x1; k += 7) {
|
|
234
|
+
c.moveTo(k, y1);
|
|
235
|
+
c.lineTo(k + (y1 - y0), y0);
|
|
236
|
+
}
|
|
237
|
+
c.stroke();
|
|
238
|
+
c.restore();
|
|
239
|
+
path();
|
|
240
|
+
c.strokeStyle = TH.ink2;
|
|
241
|
+
c.lineWidth = hov ? 2 : 1.2;
|
|
242
|
+
c.stroke();
|
|
243
|
+
// the front (where its doors are)
|
|
244
|
+
c.beginPath();
|
|
245
|
+
c.moveTo(...q[2]);
|
|
246
|
+
c.lineTo(...q[3]);
|
|
247
|
+
c.strokeStyle = TH.ink;
|
|
248
|
+
c.lineWidth = 2.5;
|
|
249
|
+
c.stroke();
|
|
250
|
+
const w = Math.hypot(q[1][0] - q[0][0], q[1][1] - q[0][1]), d = Math.hypot(q[2][0] - q[1][0], q[2][1] - q[1][1]);
|
|
251
|
+
if (Math.max(w, d) > 64 && Math.min(w, d) > 18) {
|
|
252
|
+
// along its longer side, reading upright
|
|
253
|
+
const [p0, p1] = w >= d ? [q[0], q[1]] : [q[1], q[2]], cx = (q[0][0] + q[2][0]) / 2, cy = (q[0][1] + q[2][1]) / 2;
|
|
254
|
+
let a = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
|
|
255
|
+
if (a > Math.PI / 2)
|
|
256
|
+
a -= Math.PI;
|
|
257
|
+
else if (a <= -Math.PI / 2)
|
|
258
|
+
a += Math.PI;
|
|
259
|
+
c.translate(cx, cy);
|
|
260
|
+
c.rotate(a);
|
|
261
|
+
c.font = '600 10px ' + FONT;
|
|
262
|
+
c.textAlign = 'center';
|
|
263
|
+
c.textBaseline = 'middle';
|
|
264
|
+
c.fillStyle = TH.bg;
|
|
265
|
+
c.globalAlpha = 0.85;
|
|
266
|
+
const tw = c.measureText('Cabinet').width;
|
|
267
|
+
c.fillRect(-tw / 2 - 3, -7, tw + 6, 14);
|
|
268
|
+
c.globalAlpha = 1;
|
|
269
|
+
c.fillStyle = TH.ink;
|
|
270
|
+
c.fillText('Cabinet', 0, 0.5);
|
|
271
|
+
}
|
|
272
|
+
c.restore();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** A wall piece's outline, added to the current path (`fresh`: start a new path first). */
|
|
276
|
+
function piecePath(c, T, pts, fresh = true) {
|
|
277
|
+
if (fresh)
|
|
278
|
+
c.beginPath();
|
|
279
|
+
pts.forEach((p, i) => { const [x, y] = S(T, p.x, p.y); if (i)
|
|
280
|
+
c.lineTo(x, y);
|
|
281
|
+
else
|
|
282
|
+
c.moveTo(x, y); });
|
|
283
|
+
c.closePath();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Walls as solid bands of their thickness, outlined in the definition colour. Straight walls are filled outlines cut
|
|
287
|
+
* to fit where they meet (model/wallshape.ts), so corners at any angle close cleanly; curved walls are thick strokes.
|
|
288
|
+
* Every outline is drawn first and the fill over all of them, so only the outside of the outlines shows, not the seams.
|
|
289
|
+
*/
|
|
178
290
|
function drawWalls(c, T, sk, thumb) {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
if (!
|
|
291
|
+
const shown = (w) => !(w.constr && !settings.layers.construction);
|
|
292
|
+
const pieces = wallPieces(sk, shown), curves = curveWalls(sk);
|
|
293
|
+
if (!pieces.length && !curves.length)
|
|
182
294
|
return;
|
|
183
|
-
c.lineCap = 'butt';
|
|
184
295
|
const ow = thumb ? 1 : 1.6;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
c
|
|
191
|
-
c.
|
|
296
|
+
const ink = (id) => { const l = sk.lines[id]; return ed.highlight.has(id) ? TH.red : inkFor(lineDefined(ed.ana, l) && !thumb, l.constr); };
|
|
297
|
+
c.save();
|
|
298
|
+
c.lineJoin = 'round';
|
|
299
|
+
c.lineCap = 'butt';
|
|
300
|
+
for (const p of pieces) {
|
|
301
|
+
piecePath(c, T, p.pts);
|
|
302
|
+
c.lineWidth = ow * 2;
|
|
303
|
+
c.strokeStyle = ink(p.of);
|
|
192
304
|
c.stroke();
|
|
193
305
|
}
|
|
194
|
-
// curved walls: same outline-then-fill treatment
|
|
195
|
-
const curves = curveWalls(sk);
|
|
196
306
|
for (const cw of curves) {
|
|
197
307
|
curveWallPath(c, T, sk, cw);
|
|
198
308
|
c.lineWidth = cw.thick * T.s + ow * 2;
|
|
199
309
|
c.strokeStyle = ed.highlight.has(cw.id) ? TH.red : inkFor(curveDefined(ed.ana, sk, cw.id) && !thumb, cw.constr);
|
|
200
310
|
c.stroke();
|
|
201
311
|
}
|
|
202
|
-
for
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
c
|
|
207
|
-
|
|
208
|
-
}
|
|
312
|
+
// one fill for every piece: pieces filled one by one leave a hairline where they meet
|
|
313
|
+
c.fillStyle = TH.wall;
|
|
314
|
+
c.beginPath();
|
|
315
|
+
for (const p of pieces)
|
|
316
|
+
piecePath(c, T, p.pts, false);
|
|
317
|
+
c.fill();
|
|
209
318
|
for (const cw of curves) {
|
|
210
319
|
curveWallPath(c, T, sk, cw);
|
|
211
320
|
c.lineWidth = Math.max(0.5, cw.thick * T.s);
|
|
212
321
|
c.strokeStyle = TH.wall;
|
|
213
322
|
c.stroke();
|
|
214
323
|
}
|
|
215
|
-
if (thumb)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
324
|
+
if (!thumb) {
|
|
325
|
+
for (const p of pieces) {
|
|
326
|
+
if (!p.id || !(isSel('line', p.id) || (ed.hover && ed.hover.type === 'line' && ed.hover.id === p.id)))
|
|
327
|
+
continue;
|
|
328
|
+
piecePath(c, T, p.pts);
|
|
329
|
+
c.fillStyle = isSel('line', p.id) ? 'rgba(255,173,0,.55)' : 'rgba(255,173,0,.28)';
|
|
330
|
+
c.fill();
|
|
331
|
+
}
|
|
332
|
+
drawEdges(c, T, sk);
|
|
333
|
+
}
|
|
334
|
+
c.restore();
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Wall edges picked on their own (and the one under the pointer): along the face, from its corner to its corner — a
|
|
338
|
+
* curved wall's all along it.
|
|
339
|
+
*/
|
|
340
|
+
function drawEdges(c, T, sk) {
|
|
341
|
+
const ids = ed.sel.filter(s => s.type === 'wface').map(s => s.id);
|
|
342
|
+
const hov = ed.hover?.type === 'wface' && !ids.includes(ed.hover.id) ? ed.hover.id : null;
|
|
343
|
+
if (hov)
|
|
344
|
+
ids.push(hov);
|
|
345
|
+
for (const id of ids) {
|
|
346
|
+
const [lid, side] = edgeOf(id);
|
|
347
|
+
if (!sk.lines[lid]) {
|
|
348
|
+
drawCurveEdge(c, T, sk, lid, side, id !== hov);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const a = faceCorner(sk, lid, side, 'a'), b = faceCorner(sk, lid, side, 'b');
|
|
352
|
+
if (!a || !b)
|
|
353
|
+
continue;
|
|
354
|
+
const [ax, ay] = S(T, a.x, a.y), [bx, by] = S(T, b.x, b.y), lit = id !== hov;
|
|
355
|
+
c.lineCap = 'round';
|
|
356
|
+
c.strokeStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)';
|
|
357
|
+
c.lineWidth = lit ? 3.5 : 2.5;
|
|
358
|
+
c.beginPath();
|
|
359
|
+
c.moveTo(ax, ay);
|
|
360
|
+
c.lineTo(bx, by);
|
|
361
|
+
c.stroke();
|
|
362
|
+
// (its corners: where a length on it is measured from and to)
|
|
363
|
+
c.fillStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)';
|
|
364
|
+
for (const [x, y] of [[ax, ay], [bx, by]]) {
|
|
219
365
|
c.beginPath();
|
|
220
|
-
|
|
221
|
-
c.
|
|
222
|
-
c.strokeStyle = isSel('line', id) ? 'rgba(255,173,0,.55)' : 'rgba(255,173,0,.28)';
|
|
223
|
-
c.stroke();
|
|
366
|
+
c.arc(x, y, lit ? 4 : 3, 0, Math.PI * 2);
|
|
367
|
+
c.fill();
|
|
224
368
|
}
|
|
225
369
|
}
|
|
226
370
|
}
|
|
371
|
+
/** A curved wall's outer (+1) or inner (−1) edge, picked (`lit`) or under the pointer. */
|
|
372
|
+
function drawCurveEdge(c, T, sk, id, side, lit) {
|
|
373
|
+
const w = sk.circles[id] ?? sk.arcs[id], f = curveFrame(sk, id);
|
|
374
|
+
if (!w || !f)
|
|
375
|
+
return;
|
|
376
|
+
const rr = (f.r + (side * wallThick(w)) / 2) * T.s, [x, y] = S(T, f.o.x, f.o.y);
|
|
377
|
+
if (rr <= 0)
|
|
378
|
+
return;
|
|
379
|
+
c.lineCap = 'round';
|
|
380
|
+
c.strokeStyle = lit ? TH.yellow : 'rgba(255,173,0,.6)';
|
|
381
|
+
c.lineWidth = lit ? 3.5 : 2.5;
|
|
382
|
+
c.beginPath();
|
|
383
|
+
if (f.arc)
|
|
384
|
+
c.arc(x, y, rr, -f.arc.t0, -f.arc.t1, true);
|
|
385
|
+
else
|
|
386
|
+
c.arc(x, y, rr, 0, Math.PI * 2);
|
|
387
|
+
c.stroke();
|
|
388
|
+
}
|
|
227
389
|
function drawOpenings(c, T, sk) {
|
|
228
|
-
|
|
390
|
+
// the selected or hovered one last: a neighbour drawn after it would knock the wall out over its outline
|
|
391
|
+
const lit = (id) => isSel('opening', id) || (ed.hover?.type === 'opening' && ed.hover.id === id);
|
|
392
|
+
for (const id of Object.keys(sk.openings).sort((a, b) => Number(lit(a)) - Number(lit(b)))) {
|
|
229
393
|
const o = sk.openings[id], g = openingFrame(sk, id);
|
|
230
394
|
if (!g)
|
|
231
395
|
continue;
|
|
@@ -244,10 +408,10 @@ function drawOpenings(c, T, sk) {
|
|
|
244
408
|
const sel1 = isSel('opening', id);
|
|
245
409
|
const hov = !sel1 && ed.hover && ed.hover.type === 'opening' && ed.hover.id === id;
|
|
246
410
|
const ext = OPENING_TICK_EXT / T.s;
|
|
247
|
-
// hover / selection band so it's obvious what a click will grab
|
|
411
|
+
// hover / selection band so it's obvious what a click will grab: exactly its footprint in the wall, face to face
|
|
412
|
+
// and end to end (not padded out past them)
|
|
248
413
|
if (sel1 || hov) {
|
|
249
|
-
|
|
250
|
-
quad(s0 - pad, s1 + pad, t / 2 + ext + pad);
|
|
414
|
+
quad(s0, s1, t / 2);
|
|
251
415
|
c.fillStyle = sel1 ? 'rgba(255,173,0,.30)' : 'rgba(255,173,0,.16)';
|
|
252
416
|
c.fill();
|
|
253
417
|
c.lineWidth = sel1 ? 2 : 1.5;
|
|
@@ -317,6 +481,8 @@ function drawLines(c, T, sk, thumb) {
|
|
|
317
481
|
c.strokeStyle = selected ? 'rgba(255,173,0,.6)' : 'rgba(255,173,0,.3)';
|
|
318
482
|
c.stroke();
|
|
319
483
|
}
|
|
484
|
+
if (l.anchor || l.cab)
|
|
485
|
+
continue; // a wall face's edge from the plan, a cabinet's: the wall view's backdrop draws them
|
|
320
486
|
c.beginPath();
|
|
321
487
|
c.moveTo(x1, y1);
|
|
322
488
|
c.lineTo(x2, y2);
|
|
@@ -380,13 +546,20 @@ function drawCurves(c, T, sk, thumb) {
|
|
|
380
546
|
c.setLineDash([]);
|
|
381
547
|
}
|
|
382
548
|
}
|
|
383
|
-
|
|
549
|
+
/** Points on a line that isn't fully defined: drawn in ink, not green (worked out once a frame, not once a point). */
|
|
550
|
+
const looseEnds = (sk) => frameMemo(sk, 'looseEnds', () => {
|
|
551
|
+
const out = new Set();
|
|
384
552
|
for (const lid in sk.lines) {
|
|
385
553
|
const l = sk.lines[lid];
|
|
386
|
-
if (
|
|
387
|
-
|
|
554
|
+
if (!lineDefined(ed.ana, l)) {
|
|
555
|
+
out.add(l.a);
|
|
556
|
+
out.add(l.b);
|
|
557
|
+
}
|
|
388
558
|
}
|
|
389
|
-
return
|
|
559
|
+
return out;
|
|
560
|
+
});
|
|
561
|
+
function inkForPoint(sk, id) {
|
|
562
|
+
return looseEnds(sk).has(id) ? TH.ink : TH.def;
|
|
390
563
|
}
|
|
391
564
|
function drawLock(c, x, y) {
|
|
392
565
|
c.save();
|
|
@@ -409,6 +582,8 @@ function drawLock(c, x, y) {
|
|
|
409
582
|
function drawPoints(c, T, sk) {
|
|
410
583
|
const { W, H } = cvs;
|
|
411
584
|
const touch = ed.lastPointerTouch;
|
|
585
|
+
// a cabinet's corners aren't grab points: it moves as one, like a door or window
|
|
586
|
+
const cabCorners = new Set(Object.values(sk.cabs ?? {}).flatMap(cabPoints));
|
|
412
587
|
for (const id in sk.points) {
|
|
413
588
|
const p = sk.points[id];
|
|
414
589
|
const [x, y] = S(T, p.x, p.y);
|
|
@@ -425,7 +600,11 @@ function drawPoints(c, T, sk) {
|
|
|
425
600
|
c.stroke();
|
|
426
601
|
}
|
|
427
602
|
const selected = isSel('point', id), hov = ed.hover && ed.hover.type === 'point' && ed.hover.id === id;
|
|
603
|
+
// a wall face's corners from the plan, and a cabinet's, show only when picked or under the pointer (like a window's)
|
|
604
|
+
if ((p.anchor || cabCorners.has(id)) && !selected && !hov)
|
|
605
|
+
continue;
|
|
428
606
|
const r = (touch ? 5 : 4.2) + (hov ? 1.5 : 0);
|
|
607
|
+
// (a door's or window's corners on a wall face are free until the plan locks it, or the face pins it: as a cabinet's)
|
|
429
608
|
const det = ed.ana.det.has(id);
|
|
430
609
|
c.beginPath();
|
|
431
610
|
c.arc(x, y, r, 0, Math.PI * 2);
|
|
@@ -447,28 +626,32 @@ function drawPoints(c, T, sk) {
|
|
|
447
626
|
c.strokeStyle = TH.ink;
|
|
448
627
|
c.stroke();
|
|
449
628
|
}
|
|
450
|
-
if (p.fixed && !p.origin)
|
|
629
|
+
if (p.fixed && !p.origin && !p.anchor)
|
|
451
630
|
drawLock(c, x + 9, y - 14);
|
|
452
631
|
}
|
|
453
632
|
}
|
|
454
633
|
function dimAnchor(sk, c) {
|
|
455
634
|
const P = (id) => sk.points[id];
|
|
456
635
|
if (c.type === 'dist') {
|
|
457
|
-
|
|
636
|
+
// (measured on a wall's face: from its corner to corner, the label off the face on that side)
|
|
637
|
+
const e = dimEnds(sk, c);
|
|
638
|
+
if (!e)
|
|
639
|
+
return null;
|
|
640
|
+
const { a, b } = e;
|
|
458
641
|
const dx = b.x - a.x, dy = b.y - a.y, L = hyp(dx, dy) || 1e-9, nx = -dy / L, ny = dx / L, off = c.off || 0;
|
|
459
642
|
return { m: { x: (a.x + b.x) / 2 + nx * off, y: (a.y + b.y) / 2 + ny * off }, a, b, nx, ny, L };
|
|
460
643
|
}
|
|
461
644
|
if (c.type === 'pldist') {
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
645
|
+
// (between walls' faces: from face to face)
|
|
646
|
+
const e = dimEnds(sk, c);
|
|
647
|
+
if (!e)
|
|
648
|
+
return null;
|
|
649
|
+
return { m: { x: (e.a.x + e.b.x) / 2, y: (e.a.y + e.b.y) / 2 }, a: e.a, b: e.b };
|
|
466
650
|
}
|
|
467
651
|
if (c.type === 'diam') {
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
return { m: { x: o.x, y: o.y }, o, r };
|
|
652
|
+
// (across a curved wall's face: its arrows on that face)
|
|
653
|
+
const g = dimRadius(sk, c);
|
|
654
|
+
return g ? { m: { x: g.o.x, y: g.o.y }, o: g.o, r: g.r } : null;
|
|
472
655
|
}
|
|
473
656
|
if (c.type === 'angle') {
|
|
474
657
|
const l1 = sk.lines[c.l1], l2 = sk.lines[c.l2];
|
|
@@ -483,26 +666,46 @@ function dimAnchor(sk, c) {
|
|
|
483
666
|
}
|
|
484
667
|
return null;
|
|
485
668
|
}
|
|
486
|
-
/**
|
|
669
|
+
/** Whether a dimension, or something it measures (its lines, points or curves), is selected or hovered. */
|
|
670
|
+
function dimLive(sk, id, d) {
|
|
671
|
+
const pts = consPoints(sk, d), lines = new Set([d.line, d.l, d.l1, d.l2].filter(Boolean)), curves = new Set([d.c, d.c1, d.c2].filter(Boolean));
|
|
672
|
+
const hit = (x) => !!x && (((x.type === 'dim' || x.type === 'cons') && x.id === id) || (x.type === 'point' && pts.has(x.id)) || (x.type === 'line' && lines.has(x.id))
|
|
673
|
+
|| ((x.type === 'circle' || x.type === 'arc') && curves.has(x.id)));
|
|
674
|
+
return ed.sel.some(hit) || hit(ed.hover);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Driving/reference dimension chips. (Lengths are added from the bottom bar's + / Length, not on-canvas prompts.) What's
|
|
678
|
+
* been entered (blue, or red when it can't be met) always shows; a reference only while it or what it measures is
|
|
679
|
+
* selected or hovered.
|
|
680
|
+
*/
|
|
487
681
|
function drawDims(c, T, sk) {
|
|
488
682
|
if (!settings.layers.dims)
|
|
489
683
|
return;
|
|
490
684
|
for (const id in sk.cons) {
|
|
491
685
|
const d = sk.cons[id];
|
|
492
|
-
if (!isDim(d) || d.layer === 'print')
|
|
493
|
-
continue;
|
|
686
|
+
if (!isDim(d) || d.layer === 'print' || d.elev)
|
|
687
|
+
continue; // the wall view's own dimensions are its chips
|
|
494
688
|
const A = dimAnchor(sk, d);
|
|
495
689
|
if (!A)
|
|
496
690
|
continue;
|
|
497
691
|
const ref = d.driving === false, val = ref ? measure(sk, d) : d.value;
|
|
498
692
|
const selected = isSel('dim', id) || isSel('cons', id);
|
|
499
693
|
const bad = ed.highlight.has(id) || ed.ana.bad.has(id);
|
|
694
|
+
if (ref && !bad && !dimLive(sk, id, d))
|
|
695
|
+
continue;
|
|
696
|
+
// a measurement of the job sets it: outlined, with a link mark — red once the job's measurement is gone
|
|
697
|
+
const linked = !!d.measurementRef && !ref && !bad, gone = linked && linkInfoOf(d.measurementRef)?.exists === false;
|
|
500
698
|
let bg = ref ? TH.ref : TH.blue, fg = ref ? TH.reftext : '#fff';
|
|
501
699
|
if (bad) {
|
|
502
700
|
bg = TH.red;
|
|
503
701
|
fg = '#fff';
|
|
504
702
|
}
|
|
505
|
-
|
|
703
|
+
if (linked) {
|
|
704
|
+
bg = TH.bg;
|
|
705
|
+
fg = gone ? TH.red : d.type === 'angle' ? TH.purple : linkBlue();
|
|
706
|
+
}
|
|
707
|
+
const border = selected ? [TH.yellow, 2.5] : linked ? [fg, 1.5] : null;
|
|
708
|
+
const mark = linked ? 'link' : undefined;
|
|
506
709
|
const wrap = (s) => (ref ? '(' : '') + s + (ref ? ')' : '');
|
|
507
710
|
if (d.type === 'angle' && 'vtx' in A) {
|
|
508
711
|
const [vx, vy] = S(T, A.vtx.x, A.vtx.y);
|
|
@@ -517,16 +720,19 @@ function drawDims(c, T, sk) {
|
|
|
517
720
|
c.save();
|
|
518
721
|
c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.purple;
|
|
519
722
|
c.lineWidth = 1.2;
|
|
723
|
+
if (ref)
|
|
724
|
+
c.setLineDash([3, 3]);
|
|
520
725
|
c.beginPath();
|
|
521
726
|
c.arc(vx, vy, 22, t1, t1 + sweep, sweep < 0);
|
|
522
727
|
c.stroke();
|
|
523
728
|
c.restore();
|
|
524
729
|
}
|
|
525
|
-
chip(c, wrap(fmtAng(val)), vx + A.bis[0] * 50, vy - A.bis[1] * 50,
|
|
730
|
+
chip(c, wrap(fmtAng(val)), vx + A.bis[0] * 50, vy - A.bis[1] * 50, linked ? { bg, fg, border, kind: 'dim', id, mark }
|
|
731
|
+
: { bg: bad ? TH.red : ref ? TH.ref : TH.purple, fg: ref ? TH.reftext : '#fff', border, kind: 'dim', id });
|
|
526
732
|
continue;
|
|
527
733
|
}
|
|
528
734
|
if (d.type === 'diam' && 'o' in A) {
|
|
529
|
-
drawDiameter(c, T, A.o, A.r, d, 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id });
|
|
735
|
+
drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id, mark }, ref);
|
|
530
736
|
continue;
|
|
531
737
|
}
|
|
532
738
|
if (!('a' in A))
|
|
@@ -534,12 +740,14 @@ function drawDims(c, T, sk) {
|
|
|
534
740
|
const [mx, my] = S(T, A.m.x, A.m.y);
|
|
535
741
|
const lineColor = bad ? TH.red : ref ? TH.chipline : TH.blue;
|
|
536
742
|
if (d.type === 'dist' && A.nx != null && Math.abs((d.off || 0) * T.s) > 10) {
|
|
537
|
-
drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny, d.off, lineColor);
|
|
743
|
+
drawDimLeaders(c, T, A.a, A.b, A.nx, A.ny, d.off, lineColor, ref);
|
|
538
744
|
}
|
|
539
745
|
else if (d.type === 'pldist' || !d.line) {
|
|
746
|
+
// (dashed only when it's a reference: a dashed line means the number holds nothing, wherever it's drawn)
|
|
540
747
|
const [ax, ay] = S(T, A.a.x, A.a.y), [bx, by] = S(T, A.b.x, A.b.y);
|
|
541
748
|
c.save();
|
|
542
|
-
|
|
749
|
+
if (ref)
|
|
750
|
+
c.setLineDash([3, 3]);
|
|
543
751
|
c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue;
|
|
544
752
|
c.lineWidth = 1.2;
|
|
545
753
|
c.beginPath();
|
|
@@ -548,7 +756,7 @@ function drawDims(c, T, sk) {
|
|
|
548
756
|
c.stroke();
|
|
549
757
|
c.restore();
|
|
550
758
|
}
|
|
551
|
-
chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id });
|
|
759
|
+
chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id, mark });
|
|
552
760
|
}
|
|
553
761
|
}
|
|
554
762
|
/**
|
|
@@ -639,6 +847,9 @@ function drawOpeningDims(c, T, sk) {
|
|
|
639
847
|
const r = refS(sk, o, d.ref), rp = refPoint(sk, d.ref);
|
|
640
848
|
if (r == null || !rp)
|
|
641
849
|
continue;
|
|
850
|
+
// a reference shows only while its door or window is selected or hovered; what locks it always
|
|
851
|
+
if (!d.driving && !ed.badOpeningDims.has(d.id) && !active)
|
|
852
|
+
continue;
|
|
642
853
|
const e = d.edge === 's0' ? g.s0 : g.s1;
|
|
643
854
|
const k = d.off ?? kRow(place(r, e));
|
|
644
855
|
const bad = ed.badOpeningDims.has(d.id);
|
|
@@ -673,22 +884,23 @@ function drawOpeningDims(c, T, sk) {
|
|
|
673
884
|
if (g.s0 > 0.5) {
|
|
674
885
|
ext(0, kw, grey);
|
|
675
886
|
span(0, g.s0, kw, grey);
|
|
676
|
-
label(0, g.s0, 'a', fmtLen(g.s0), kw, TH.ref, TH.reftext);
|
|
887
|
+
label(0, g.s0, 'a', '(' + fmtLen(g.s0) + ')', kw, TH.ref, TH.reftext);
|
|
677
888
|
}
|
|
678
889
|
if (L - g.s1 > 0.5) {
|
|
679
890
|
ext(L, kw, grey);
|
|
680
891
|
span(g.s1, L, kw, grey);
|
|
681
|
-
label(g.s1, L, 'b', fmtLen(L - g.s1), kw, TH.ref, TH.reftext);
|
|
892
|
+
label(g.s1, L, 'b', '(' + fmtLen(L - g.s1) + ')', kw, TH.ref, TH.reftext);
|
|
682
893
|
}
|
|
683
894
|
}
|
|
684
|
-
// width: a
|
|
685
|
-
//
|
|
686
|
-
if (o
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
895
|
+
// width: a full dimension, a solid line with extension lines from the jambs; blue once it's been entered (and then
|
|
896
|
+
// always shown), grey while it's the standard size it was placed at (and then only while the opening is selected or hovered)
|
|
897
|
+
if (!widthEntered(o) && !active)
|
|
898
|
+
continue;
|
|
899
|
+
const wc = widthEntered(o) ? TH.blue : TH.chipline;
|
|
900
|
+
ext(g.s0, kw, wc);
|
|
901
|
+
ext(g.s1, kw, wc);
|
|
902
|
+
span(g.s0, g.s1, kw, wc, false);
|
|
903
|
+
label(g.s0, g.s1, 'w', fmtLen(g.w), kw, widthEntered(o) ? TH.blue : TH.ref, widthEntered(o) ? '#fff' : TH.reftext, isSel('opening', id) ? [TH.yellow, 2] : null);
|
|
692
904
|
}
|
|
693
905
|
}
|
|
694
906
|
/**
|
|
@@ -700,7 +912,8 @@ function drawOpeningAnchors(c, T, sk) {
|
|
|
700
912
|
return;
|
|
701
913
|
for (const id in sk.openings) {
|
|
702
914
|
const o = sk.openings[id];
|
|
703
|
-
const
|
|
915
|
+
const endPicked = (e) => isSel('oanchor', id + '|' + e);
|
|
916
|
+
const show = ed.tool === 'dim' || isSel('opening', id) || endPicked('s0') || endPicked('s1') || (ed.hover != null && ed.hover.id === id);
|
|
704
917
|
if (!show)
|
|
705
918
|
continue;
|
|
706
919
|
const locked = !!drivingDim(o);
|
|
@@ -711,7 +924,7 @@ function drawOpeningAnchors(c, T, sk) {
|
|
|
711
924
|
const [x, y] = S(T, p.x, p.y);
|
|
712
925
|
const hov = ed.hover && ed.hover.type === 'oanchor' && ed.hover.id === id && ed.hover.part === edge;
|
|
713
926
|
// a body pick hasn't settled on an edge yet, so both light up
|
|
714
|
-
const picked = ed.pickedAnchor && ed.pickedAnchor.id === id && (ed.pickedAnchor.body || ed.pickedAnchor.edge === edge);
|
|
927
|
+
const picked = (ed.pickedAnchor && ed.pickedAnchor.id === id && (ed.pickedAnchor.body || ed.pickedAnchor.edge === edge)) || endPicked(edge);
|
|
715
928
|
const r = (ed.lastPointerTouch ? 5 : 4.2) + (hov ? 1.5 : 0);
|
|
716
929
|
c.save();
|
|
717
930
|
c.beginPath();
|
|
@@ -840,7 +1053,7 @@ export function drawPendingDim(c, T, p) {
|
|
|
840
1053
|
* A linear dimension pulled off its line: extension lines from both ends out to a dimension line with
|
|
841
1054
|
* arrowheads, so the label always reads as attached to what it measures.
|
|
842
1055
|
*/
|
|
843
|
-
function drawDimLeaders(c, T, a, b, nx, ny, off, color) {
|
|
1056
|
+
function drawDimLeaders(c, T, a, b, nx, ny, off, color, dashed = false) {
|
|
844
1057
|
const [ax, ay] = S(T, a.x, a.y), [bx, by] = S(T, b.x, b.y);
|
|
845
1058
|
const [qax, qay] = S(T, a.x + nx * off, a.y + ny * off), [qbx, qby] = S(T, b.x + nx * off, b.y + ny * off);
|
|
846
1059
|
const [ex, ey] = norm2(qax - ax, qay - ay); // screen direction from geometry toward the dimension line
|
|
@@ -856,10 +1069,13 @@ function drawDimLeaders(c, T, a, b, nx, ny, off, color) {
|
|
|
856
1069
|
}
|
|
857
1070
|
c.stroke();
|
|
858
1071
|
c.lineWidth = 1.2;
|
|
1072
|
+
if (dashed)
|
|
1073
|
+
c.setLineDash([3, 3]);
|
|
859
1074
|
c.beginPath();
|
|
860
1075
|
c.moveTo(qax, qay);
|
|
861
1076
|
c.lineTo(qbx, qby);
|
|
862
1077
|
c.stroke();
|
|
1078
|
+
c.setLineDash([]);
|
|
863
1079
|
const [ux, uy] = norm2(qbx - qax, qby - qay);
|
|
864
1080
|
arrowHead(c, [qbx, qby], ux, uy);
|
|
865
1081
|
arrowHead(c, [qax, qay], -ux, -uy);
|
|
@@ -869,7 +1085,7 @@ function drawDimLeaders(c, T, a, b, nx, ny, off, color) {
|
|
|
869
1085
|
* Diameter dimension, CAD style: a line across the circle through its centre with arrowheads on the
|
|
870
1086
|
* circle, a leader out past the edge and a short landing with the label, so the centre stays free to grab.
|
|
871
1087
|
*/
|
|
872
|
-
function drawDiameter(c, T, o, r, d, text, color, chipOpts) {
|
|
1088
|
+
function drawDiameter(c, T, o, r, d, text, color, chipOpts, dashed = false) {
|
|
873
1089
|
const a = d.ang ?? DIAM_DEFAULT_ANG, ux = Math.cos(a), uy = Math.sin(a);
|
|
874
1090
|
const [ox, oy] = S(T, o.x, o.y);
|
|
875
1091
|
const rs = r * T.s, sx = ux, sy = -uy; // screen direction (y down)
|
|
@@ -882,16 +1098,22 @@ function drawDiameter(c, T, o, r, d, text, color, chipOpts) {
|
|
|
882
1098
|
c.strokeStyle = color;
|
|
883
1099
|
c.fillStyle = color;
|
|
884
1100
|
c.lineWidth = 1.2;
|
|
1101
|
+
// a radius (a fillet's) runs from the centre out through the arc; a diameter right across
|
|
1102
|
+
const from = d.rad ? [ox, oy] : p2;
|
|
1103
|
+
if (dashed)
|
|
1104
|
+
c.setLineDash([3, 3]);
|
|
885
1105
|
c.beginPath();
|
|
886
|
-
c.moveTo(
|
|
1106
|
+
c.moveTo(from[0], from[1]);
|
|
887
1107
|
c.lineTo(q[0], q[1]);
|
|
888
1108
|
c.lineTo(end[0], end[1]);
|
|
889
1109
|
c.stroke();
|
|
1110
|
+
c.setLineDash([]);
|
|
890
1111
|
arrowHead(c, p1, sx, sy);
|
|
891
|
-
|
|
1112
|
+
if (!d.rad)
|
|
1113
|
+
arrowHead(c, p2, -sx, -sy);
|
|
892
1114
|
c.restore();
|
|
893
1115
|
c.font = (chipOpts.font || '600 12px') + ' ' + FONT;
|
|
894
|
-
const w = c.measureText(text).width + 14;
|
|
1116
|
+
const w = c.measureText(text).width + 14 + (chipOpts.mark ? MARK_W : 0);
|
|
895
1117
|
chip(c, text, end[0] + side * (w / 2 + 2), end[1], chipOpts);
|
|
896
1118
|
}
|
|
897
1119
|
/** Architectural dimension strings on the Print dimensions layer (reference-only). */
|
|
@@ -954,6 +1176,48 @@ function drawPrintDims(c, T, sk) {
|
|
|
954
1176
|
addHit({ kind: 'pdim', id, x: mx - w / 2 - 4, y: my - 18, w: w + 8, h: 24 });
|
|
955
1177
|
}
|
|
956
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* A badge's symbol drawn as lines rather than a character: ∥ ⊥ ∘ ◎ aren't in every typeface — on a device the
|
|
1181
|
+
* app's own (Inter) lacks them and Skia has nothing to fall back on, so they came out as the font's "missing glyph"
|
|
1182
|
+
* box. Letters and = — are in every font and stay text. False for a symbol that isn't one of these.
|
|
1183
|
+
*/
|
|
1184
|
+
function drawSymbol(c, g, x, y, ink) {
|
|
1185
|
+
c.strokeStyle = ink;
|
|
1186
|
+
c.lineWidth = 1.6;
|
|
1187
|
+
c.lineCap = 'round';
|
|
1188
|
+
c.beginPath();
|
|
1189
|
+
if (g === '∥') {
|
|
1190
|
+
c.moveTo(x - 2.5, y - 5);
|
|
1191
|
+
c.lineTo(x - 2.5, y + 5);
|
|
1192
|
+
c.moveTo(x + 2.5, y - 5);
|
|
1193
|
+
c.lineTo(x + 2.5, y + 5);
|
|
1194
|
+
c.stroke();
|
|
1195
|
+
return true;
|
|
1196
|
+
}
|
|
1197
|
+
if (g === '⊥') {
|
|
1198
|
+
c.moveTo(x, y - 5);
|
|
1199
|
+
c.lineTo(x, y + 4.5);
|
|
1200
|
+
c.moveTo(x - 5, y + 4.5);
|
|
1201
|
+
c.lineTo(x + 5, y + 4.5);
|
|
1202
|
+
c.stroke();
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
if (g === '∘') {
|
|
1206
|
+
c.arc(x, y, 3, 0, Math.PI * 2);
|
|
1207
|
+
c.stroke();
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
if (g === '◎') {
|
|
1211
|
+
c.lineWidth = 1.3;
|
|
1212
|
+
c.arc(x, y, 4.5, 0, Math.PI * 2);
|
|
1213
|
+
c.stroke();
|
|
1214
|
+
c.beginPath();
|
|
1215
|
+
c.arc(x, y, 1.8, 0, Math.PI * 2);
|
|
1216
|
+
c.fill();
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
957
1221
|
const GLYPH = { h: 'H', v: 'V', par: '∥', perp: '⊥', col: '—', tan: 'T', eq: '=', eqr: '=', mid: 'M', online: '∘', oncircle: '∘', conc: '◎' };
|
|
958
1222
|
function badgeSpots(T, sk, c) {
|
|
959
1223
|
const P = (id) => sk.points[id];
|
|
@@ -969,7 +1233,7 @@ function badgeSpots(T, sk, c) {
|
|
|
969
1233
|
out.push([x1 + (x2 - x1) * frac + nx * off * side, y1 + (y2 - y1) * frac + ny * off * side]);
|
|
970
1234
|
};
|
|
971
1235
|
if (c.type === 'h' || c.type === 'v') {
|
|
972
|
-
const lid =
|
|
1236
|
+
const lid = lineBetween(sk, c.a, c.b);
|
|
973
1237
|
if (lid)
|
|
974
1238
|
onLine(lid, 0.22, -1);
|
|
975
1239
|
else {
|
|
@@ -1025,7 +1289,7 @@ function drawBadges(c, T, sk) {
|
|
|
1025
1289
|
}
|
|
1026
1290
|
for (const id in sk.cons) {
|
|
1027
1291
|
const k = sk.cons[id];
|
|
1028
|
-
if (isDim(k))
|
|
1292
|
+
if (isDim(k) || k.elev)
|
|
1029
1293
|
continue;
|
|
1030
1294
|
const g = GLYPH[k.type];
|
|
1031
1295
|
if (!g)
|
|
@@ -1044,11 +1308,14 @@ function drawBadges(c, T, sk) {
|
|
|
1044
1308
|
c.lineWidth = 1;
|
|
1045
1309
|
c.strokeStyle = bad ? TH.red : TH.badgeline;
|
|
1046
1310
|
c.stroke();
|
|
1047
|
-
|
|
1048
|
-
c.
|
|
1049
|
-
c
|
|
1050
|
-
|
|
1051
|
-
|
|
1311
|
+
const ink = bad ? '#fff' : selected ? '#181A1C' : TH.badgefg;
|
|
1312
|
+
c.fillStyle = ink;
|
|
1313
|
+
if (!drawSymbol(c, g, x, y, ink)) {
|
|
1314
|
+
c.font = '700 11px ' + FONT;
|
|
1315
|
+
c.textAlign = 'center';
|
|
1316
|
+
c.textBaseline = 'middle';
|
|
1317
|
+
c.fillText(g, x, y + 0.5);
|
|
1318
|
+
}
|
|
1052
1319
|
c.restore();
|
|
1053
1320
|
addHit({ kind: 'cons', id, x: x - 10, y: y - 10, w: 20, h: 20 });
|
|
1054
1321
|
}
|
|
@@ -1074,8 +1341,13 @@ function drawNotes(c, T, sk) {
|
|
|
1074
1341
|
if (!settings.layers.annot)
|
|
1075
1342
|
return;
|
|
1076
1343
|
const dim = ed.mode === 'draft' ? 0.75 : 1;
|
|
1344
|
+
nextThumbFrame();
|
|
1077
1345
|
for (const n of sk.notes) {
|
|
1078
1346
|
const selected = isSel('note', n.id);
|
|
1347
|
+
if (isLink(n)) {
|
|
1348
|
+
drawLink(c, T, n, { selected, alpha: dim, info: linkInfoOf(n.link) });
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1079
1351
|
c.save();
|
|
1080
1352
|
c.globalAlpha = dim;
|
|
1081
1353
|
c.strokeStyle = n.color;
|
|
@@ -1086,98 +1358,105 @@ function drawNotes(c, T, sk) {
|
|
|
1086
1358
|
c.shadowColor = TH.yellow;
|
|
1087
1359
|
c.shadowBlur = 0;
|
|
1088
1360
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
c.strokeStyle = 'rgba(255,173,0,.45)';
|
|
1097
|
-
c.stroke();
|
|
1098
|
-
c.restore();
|
|
1099
|
-
}
|
|
1100
|
-
c.stroke();
|
|
1101
|
-
if (n.type === 'arrow' && n.pts.length > 1) {
|
|
1102
|
-
const p1 = n.pts[n.pts.length - 2], p2 = n.pts[n.pts.length - 1];
|
|
1103
|
-
const [x1, y1] = S(T, p1[0], p1[1]), [x2, y2] = S(T, p2[0], p2[1]);
|
|
1104
|
-
const a = Math.atan2(y2 - y1, x2 - x1);
|
|
1105
|
-
c.beginPath();
|
|
1106
|
-
c.moveTo(x2, y2);
|
|
1107
|
-
c.lineTo(x2 - 13 * Math.cos(a - 0.45), y2 - 13 * Math.sin(a - 0.45));
|
|
1108
|
-
c.lineTo(x2 - 13 * Math.cos(a + 0.45), y2 - 13 * Math.sin(a + 0.45));
|
|
1109
|
-
c.closePath();
|
|
1110
|
-
c.fill();
|
|
1111
|
-
}
|
|
1361
|
+
const look = noteLook(n);
|
|
1362
|
+
const outline = shapeOutline(n);
|
|
1363
|
+
if (outline) {
|
|
1364
|
+
strokeOutline(c, outline.map(p => S(T, p[0], p[1])), look, selected ? 'rgba(255,173,0,.45)' : undefined);
|
|
1365
|
+
}
|
|
1366
|
+
else if ((n.type === 'pen' || isSegment(n)) && n.pts) {
|
|
1367
|
+
strokeMarkup(c, n.pts.map(p => S(T, p[0], p[1])), look, selected ? 'rgba(255,173,0,.45)' : undefined);
|
|
1112
1368
|
}
|
|
1113
1369
|
else if (n.type === 'text') {
|
|
1114
|
-
|
|
1115
|
-
c
|
|
1116
|
-
|
|
1117
|
-
|
|
1370
|
+
// laid out at the one text size and scaled to the note's size on the drawing (its outline stays the same width)
|
|
1371
|
+
const { x, w, mid, k } = noteTextBox(T, n, c), lw = selected ? 2.5 : 1.5;
|
|
1372
|
+
c.translate(x, mid);
|
|
1373
|
+
c.scale(k, k);
|
|
1374
|
+
rr(c, 0, -14, w / k, 28, 7);
|
|
1118
1375
|
c.fillStyle = TH.bg;
|
|
1119
1376
|
c.globalAlpha = 0.92 * dim;
|
|
1120
1377
|
c.fill();
|
|
1121
1378
|
c.globalAlpha = dim;
|
|
1122
|
-
c.lineWidth =
|
|
1379
|
+
c.lineWidth = lw / k;
|
|
1123
1380
|
c.strokeStyle = selected ? TH.yellow : n.color;
|
|
1381
|
+
c.setLineDash(dashFor(look.style, lw).map(v => v / k));
|
|
1124
1382
|
c.stroke();
|
|
1383
|
+
c.setLineDash([]);
|
|
1384
|
+
c.font = noteFont(NOTE_TEXT_SIZE);
|
|
1125
1385
|
c.fillStyle = n.color;
|
|
1386
|
+
c.textAlign = 'left';
|
|
1126
1387
|
c.textBaseline = 'middle';
|
|
1127
|
-
c.fillText(n.text,
|
|
1388
|
+
c.fillText(n.text, 8, 0.5 / k); // (left: not whatever was drawn last)
|
|
1128
1389
|
}
|
|
1129
1390
|
else if (n.type === 'cloud' && n.pts) {
|
|
1130
1391
|
const [x1, y1] = S(T, n.pts[0][0], n.pts[0][1]), [x2, y2] = S(T, n.pts[1][0], n.pts[1][1]);
|
|
1131
1392
|
cloudPath(c, Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
c.
|
|
1393
|
+
if (selected) {
|
|
1394
|
+
c.save();
|
|
1395
|
+
c.lineWidth = look.width + 6;
|
|
1396
|
+
c.strokeStyle = 'rgba(255,173,0,.45)';
|
|
1397
|
+
c.stroke();
|
|
1398
|
+
c.restore();
|
|
1399
|
+
}
|
|
1400
|
+
c.lineWidth = look.width;
|
|
1401
|
+
if (look.style === 'dot')
|
|
1402
|
+
c.lineCap = 'round';
|
|
1403
|
+
c.setLineDash(dashFor(look.style, look.width));
|
|
1135
1404
|
c.stroke();
|
|
1405
|
+
c.setLineDash([]);
|
|
1136
1406
|
}
|
|
1137
1407
|
c.restore();
|
|
1138
1408
|
}
|
|
1139
1409
|
}
|
|
1140
|
-
/** Selected
|
|
1410
|
+
/** Selected images: blue outline with corner (scale) and side (stretch) handles — one image alone — or a lock badge when locked. */
|
|
1141
1411
|
function drawUnderlaySelection(c, T) {
|
|
1142
|
-
|
|
1143
|
-
if (!u || !settings.layers.underlay || ed.mode !== 'annotate' || !isSel('underlay', 'u'))
|
|
1412
|
+
if (!settings.layers.underlay || ed.mode !== 'annotate')
|
|
1144
1413
|
return;
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
c.
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1414
|
+
const sel = page().underlays.filter(u => isSel('underlay', u.id));
|
|
1415
|
+
for (const u of sel) {
|
|
1416
|
+
const hs = underlayHandles(T, u), nw = hs[0];
|
|
1417
|
+
c.save();
|
|
1418
|
+
c.strokeStyle = TH.blue;
|
|
1419
|
+
c.lineWidth = 1.5;
|
|
1420
|
+
if (u.locked)
|
|
1421
|
+
c.setLineDash([6, 4]);
|
|
1422
|
+
// its outline, turned with it: through the corners
|
|
1423
|
+
c.beginPath();
|
|
1424
|
+
[hs[0], hs[2], hs[4], hs[6]].forEach((q, i) => (i ? c.lineTo(q.x, q.y) : c.moveTo(q.x, q.y)));
|
|
1425
|
+
c.closePath();
|
|
1426
|
+
c.stroke();
|
|
1427
|
+
c.setLineDash([]);
|
|
1428
|
+
if (u.locked)
|
|
1429
|
+
drawLock(c, nw.x + 12, nw.y + 12);
|
|
1430
|
+
else if (sel.length === 1)
|
|
1431
|
+
drawHandleSet(c, hs);
|
|
1155
1432
|
c.restore();
|
|
1156
|
-
return;
|
|
1157
1433
|
}
|
|
1158
|
-
drawHandleSet(c, hs);
|
|
1159
|
-
c.restore();
|
|
1160
1434
|
}
|
|
1161
|
-
/** Resize handles: round white corners (and arrow ends), blue bars on the sides. */
|
|
1435
|
+
/** Resize handles: round white corners (and arrow ends), blue bars on the sides — along them, on a turned image (`ang`). */
|
|
1162
1436
|
function drawHandleSet(c, hs) {
|
|
1163
1437
|
const r = ed.lastPointerTouch ? 8 : 6;
|
|
1164
1438
|
for (const hd of hs) {
|
|
1165
1439
|
const side = hd.part === 'n' || hd.part === 's' || hd.part === 'e' || hd.part === 'w';
|
|
1440
|
+
c.save();
|
|
1441
|
+
c.translate(hd.x, hd.y);
|
|
1442
|
+
if (hd.ang)
|
|
1443
|
+
c.rotate(hd.ang);
|
|
1166
1444
|
c.beginPath();
|
|
1167
1445
|
if (!side)
|
|
1168
|
-
c.arc(
|
|
1446
|
+
c.arc(0, 0, r, 0, Math.PI * 2);
|
|
1169
1447
|
else if (hd.part === 'e' || hd.part === 'w')
|
|
1170
|
-
rr(c,
|
|
1448
|
+
rr(c, -3.5, -15, 7, 30, 3.5);
|
|
1171
1449
|
else
|
|
1172
|
-
rr(c,
|
|
1450
|
+
rr(c, -12, -3, 24, 6, 3);
|
|
1173
1451
|
c.fillStyle = side ? TH.blue : '#fff';
|
|
1174
1452
|
c.fill();
|
|
1175
1453
|
c.lineWidth = 1.5;
|
|
1176
1454
|
c.strokeStyle = TH.blue;
|
|
1177
1455
|
c.stroke();
|
|
1456
|
+
c.restore();
|
|
1178
1457
|
}
|
|
1179
1458
|
}
|
|
1180
|
-
/** A single selected
|
|
1459
|
+
/** A single selected piece of markup: its adjustment handles (and a box around all but the straight ones and notes). */
|
|
1181
1460
|
function drawNoteSelection(c, T) {
|
|
1182
1461
|
if (ed.mode !== 'annotate' || !settings.layers.annot)
|
|
1183
1462
|
return;
|
|
@@ -1191,7 +1470,8 @@ function drawNoteSelection(c, T) {
|
|
|
1191
1470
|
if (!hs.length)
|
|
1192
1471
|
return;
|
|
1193
1472
|
c.save();
|
|
1194
|
-
|
|
1473
|
+
// (a note has its own outline, and a link its card)
|
|
1474
|
+
if (!isSegment(n) && n.type !== 'text' && n.type !== 'link') {
|
|
1195
1475
|
const nw = hs[0], se = hs[4];
|
|
1196
1476
|
c.strokeStyle = TH.blue;
|
|
1197
1477
|
c.lineWidth = 1.2;
|
|
@@ -1243,6 +1523,16 @@ function drawSnap(c, T) {
|
|
|
1243
1523
|
c.rect(x - 5, y - 5, 10, 10);
|
|
1244
1524
|
c.stroke();
|
|
1245
1525
|
}
|
|
1526
|
+
else if (s.kind === 'grid') {
|
|
1527
|
+
// on the grid: the grid point it's on, marked (it moves in steps from one to the next)
|
|
1528
|
+
c.beginPath();
|
|
1529
|
+
c.arc(x, y, 3.5, 0, Math.PI * 2);
|
|
1530
|
+
c.fill();
|
|
1531
|
+
c.lineWidth = 1.2;
|
|
1532
|
+
c.beginPath();
|
|
1533
|
+
c.arc(x, y, 7, 0, Math.PI * 2);
|
|
1534
|
+
c.stroke();
|
|
1535
|
+
}
|
|
1246
1536
|
const lbl = s.label || (s.guides && s.guides.length ? s.guides.map(g => g.label).join(' · ') : '');
|
|
1247
1537
|
if (lbl) {
|
|
1248
1538
|
c.font = '600 11px ' + FONT;
|
|
@@ -1259,8 +1549,9 @@ function drawSnap(c, T) {
|
|
|
1259
1549
|
}
|
|
1260
1550
|
/** Paint the browser canvas, keeping the frame's tap targets for hit testing. */
|
|
1261
1551
|
export function draw() {
|
|
1262
|
-
const { W, H, dpr, ctx } = cvs;
|
|
1263
|
-
|
|
1552
|
+
const { cv, W, H, dpr, ctx } = cvs;
|
|
1553
|
+
// (no browser canvas — a unit test — nothing to paint)
|
|
1554
|
+
if (!W || !cv)
|
|
1264
1555
|
return;
|
|
1265
1556
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1266
1557
|
ed.hitChips = drawPlan(ctx, W, H);
|
|
@@ -1274,20 +1565,70 @@ export function drawPlan(ctx, W, H) {
|
|
|
1274
1565
|
hits = out;
|
|
1275
1566
|
try {
|
|
1276
1567
|
paintPlan(ctx, W, H);
|
|
1568
|
+
paintOverlay(ctx, W, H);
|
|
1277
1569
|
}
|
|
1278
1570
|
finally {
|
|
1279
1571
|
hits = null;
|
|
1280
1572
|
}
|
|
1281
1573
|
return out;
|
|
1282
1574
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1575
|
+
/**
|
|
1576
|
+
* The drawing without its moving parts (see drawPlanOverlay), for a host that keeps it as a picture and redraws only
|
|
1577
|
+
* those parts as the pointer moves. Returns the tap targets, as drawPlan does.
|
|
1578
|
+
*/
|
|
1579
|
+
export function drawPlanStatic(ctx, W, H, opts = {}) {
|
|
1580
|
+
const out = [];
|
|
1581
|
+
hits = out;
|
|
1582
|
+
try {
|
|
1583
|
+
paintPlan(ctx, W, H, opts);
|
|
1584
|
+
}
|
|
1585
|
+
finally {
|
|
1586
|
+
hits = null;
|
|
1587
|
+
}
|
|
1588
|
+
return out;
|
|
1589
|
+
}
|
|
1590
|
+
/** The photos and plans under the drawing, with the background and grid (see KeepOpts.layer). */
|
|
1591
|
+
export function drawPlanUnder(ctx, W, H, opts = {}) {
|
|
1592
|
+
paintPlan(ctx, W, H, { ...opts, layer: 'under' });
|
|
1593
|
+
}
|
|
1594
|
+
/** The parts that move with the pointer: a tool's preview, the snap marker, the selection box, the conflict tint. */
|
|
1595
|
+
export function drawPlanOverlay(ctx, W, H, opts = {}) {
|
|
1596
|
+
paintOverlay(ctx, W, H, opts.fixed ?? ctx);
|
|
1597
|
+
}
|
|
1598
|
+
function paintPlan(ctx, W, H, opts = {}) {
|
|
1599
|
+
const m = opts.margin ?? 0, wall = elevOpen() && !!elev.frame;
|
|
1600
|
+
if (opts.layer !== 'above') {
|
|
1601
|
+
ctx.fillStyle = TH.bg;
|
|
1602
|
+
ctx.fillRect(-m, -m, W + 2 * m, H + 2 * m);
|
|
1603
|
+
}
|
|
1604
|
+
// the photos and plans under the drawing (the wall view has none: its grid is on the wall, drawn with it)
|
|
1605
|
+
if (opts.layer === 'under') {
|
|
1606
|
+
if (!wall) {
|
|
1607
|
+
const T = mainT();
|
|
1608
|
+
drawGrid(ctx, T, m);
|
|
1609
|
+
drawUnderlay(ctx, T);
|
|
1610
|
+
}
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
const sk = SK();
|
|
1614
|
+
// (the sketch can't change while a frame is drawn: index its lines once for the frame)
|
|
1615
|
+
withLineIndex(sk, () => {
|
|
1616
|
+
if (wall)
|
|
1617
|
+
paintWall(ctx, W, opts.fixed ?? ctx);
|
|
1618
|
+
else
|
|
1619
|
+
paintPlanView(ctx, sk, m, opts.layer !== 'above');
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
function paintPlanView(ctx, sk, m, under = true) {
|
|
1623
|
+
const T = mainT();
|
|
1624
|
+
if (under) {
|
|
1625
|
+
drawGrid(ctx, T, m);
|
|
1626
|
+
drawUnderlay(ctx, T);
|
|
1627
|
+
}
|
|
1289
1628
|
if (settings.layers.rooms)
|
|
1290
1629
|
drawRooms(ctx, T, sk);
|
|
1630
|
+
if (settings.layers.cabinets)
|
|
1631
|
+
drawCabinetFootprints(ctx, T, sk);
|
|
1291
1632
|
drawWalls(ctx, T, sk);
|
|
1292
1633
|
drawOpenings(ctx, T, sk);
|
|
1293
1634
|
drawLines(ctx, T, sk);
|
|
@@ -1295,13 +1636,51 @@ function paintPlan(ctx, W, H) {
|
|
|
1295
1636
|
drawPrintDims(ctx, T, sk);
|
|
1296
1637
|
if (ed.mode === 'draft')
|
|
1297
1638
|
drawBadges(ctx, T, sk);
|
|
1298
|
-
|
|
1639
|
+
// (the drawing's points only in Draft: Annotate can't move them, and shouldn't look as if it could — the user's
|
|
1640
|
+
// call, 2026-10-21)
|
|
1641
|
+
if (ed.mode === 'draft')
|
|
1642
|
+
drawPoints(ctx, T, sk);
|
|
1299
1643
|
drawOpeningAnchors(ctx, T, sk);
|
|
1300
1644
|
drawDims(ctx, T, sk);
|
|
1301
1645
|
drawOpeningDims(ctx, T, sk);
|
|
1302
1646
|
drawNotes(ctx, T, sk);
|
|
1303
|
-
drawUnderlaySelection(ctx, T);
|
|
1304
1647
|
drawNoteSelection(ctx, T);
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* The wall view: the open wall face, drawn as the plan is (its grid on the wall only, lines, curves, constraints, dimensions, points), over
|
|
1651
|
+
* the wall itself (render/elevation.ts) and under the dimensions its doors, windows and cabinets get, plus a key plan.
|
|
1652
|
+
*/
|
|
1653
|
+
function paintWall(ctx, W, fixed) {
|
|
1654
|
+
const sk = SK(), plan = planSK(), f = elev.frame, T = mainT();
|
|
1655
|
+
// the grid only on the wall itself: outside it is plain, so it's plain that nothing goes there
|
|
1656
|
+
const [gx0, gy0] = S(T, f.face[0], f.H), [gx1, gy1] = S(T, f.face[1], 0);
|
|
1657
|
+
ctx.save();
|
|
1658
|
+
ctx.beginPath();
|
|
1659
|
+
ctx.rect(gx0, gy0, gx1 - gx0, gy1 - gy0);
|
|
1660
|
+
ctx.clip();
|
|
1661
|
+
drawGrid(ctx, T);
|
|
1662
|
+
ctx.restore();
|
|
1663
|
+
drawWallBackdrop(ctx, T, plan, f, sk);
|
|
1664
|
+
drawLines(ctx, T, sk);
|
|
1665
|
+
drawCurves(ctx, T, sk);
|
|
1666
|
+
// (over the edges — its own and its neighbours' — so a selected cabinet's outline shows all the way round)
|
|
1667
|
+
drawWallSelection(ctx, T, plan, f, sk);
|
|
1668
|
+
drawPrintDims(ctx, T, sk);
|
|
1669
|
+
drawBadges(ctx, T, sk);
|
|
1670
|
+
if (ed.mode === 'draft')
|
|
1671
|
+
drawPoints(ctx, T, sk);
|
|
1672
|
+
drawDims(ctx, T, sk);
|
|
1673
|
+
const on = (a) => isSel('elev', JSON.stringify(a)) || (a.k === 'fdim' && isSel('dim', a.id));
|
|
1674
|
+
for (const h of drawWallChips(ctx, T, plan, f, sk, on))
|
|
1675
|
+
addHit({ kind: 'elev', id: JSON.stringify(h.act), part: h.look, x: h.x, y: h.y, w: h.w, h: h.h });
|
|
1676
|
+
// (where you stand: it stays put on the screen as the wall is panned and zoomed)
|
|
1677
|
+
const k = keyPlanSize(W);
|
|
1678
|
+
drawKeyPlan(fixed, W - k - 14, 70, k, plan, f);
|
|
1679
|
+
}
|
|
1680
|
+
function paintOverlay(ctx, W, H, fixed = ctx) {
|
|
1681
|
+
const T = mainT();
|
|
1682
|
+
// a selected image's outline and handles: with the moving parts, as it's turned, moved or sized (over the drawing)
|
|
1683
|
+
drawUnderlaySelection(ctx, T);
|
|
1305
1684
|
if (ed.preview)
|
|
1306
1685
|
ed.preview(ctx, T);
|
|
1307
1686
|
drawSnap(ctx, T);
|
|
@@ -1318,14 +1697,15 @@ function paintPlan(ctx, W, H) {
|
|
|
1318
1697
|
ctx.strokeRect(Math.min(x0, x1) + 0.5, Math.min(y0, y1) + 0.5, Math.abs(x1 - x0), Math.abs(y1 - y0));
|
|
1319
1698
|
ctx.restore();
|
|
1320
1699
|
}
|
|
1700
|
+
// (the screen's edge, however the view moves)
|
|
1321
1701
|
if (ed.conflictTint) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1702
|
+
fixed.save();
|
|
1703
|
+
fixed.fillStyle = 'rgba(218,17,0,.07)';
|
|
1704
|
+
fixed.fillRect(0, 0, W, H);
|
|
1705
|
+
fixed.strokeStyle = 'rgba(218,17,0,.7)';
|
|
1706
|
+
fixed.lineWidth = 6;
|
|
1707
|
+
fixed.strokeRect(3, 3, W - 6, H - 6);
|
|
1708
|
+
fixed.restore();
|
|
1329
1709
|
}
|
|
1330
1710
|
}
|
|
1331
1711
|
/** Page thumbnail for the drawer (no chips, no definition colors). */
|
|
@@ -1360,11 +1740,9 @@ export function paintThumb(c, w, h, pg) {
|
|
|
1360
1740
|
ed.hover = null;
|
|
1361
1741
|
cvs.W = w;
|
|
1362
1742
|
cvs.H = h;
|
|
1743
|
+
// (the page's lines indexed by their ends while it's drawn, as a frame is: each room's edges looked up, not searched for)
|
|
1363
1744
|
try {
|
|
1364
|
-
drawRooms(c, T, sk, true);
|
|
1365
|
-
drawWalls(c, T, sk, true);
|
|
1366
|
-
drawLines(c, T, sk, true);
|
|
1367
|
-
drawCurves(c, T, sk, true);
|
|
1745
|
+
withLineIndex(sk, () => { drawRooms(c, T, sk, true); drawWalls(c, T, sk, true); drawLines(c, T, sk, true); drawCurves(c, T, sk, true); });
|
|
1368
1746
|
}
|
|
1369
1747
|
finally {
|
|
1370
1748
|
cvs.W = save.W;
|