@weasel-js/core 1.11.0 → 1.11.1

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.
Files changed (365) hide show
  1. package/CHANGELOG.md +427 -0
  2. package/README.md +1 -1
  3. package/dist/animation/index.js +3 -3
  4. package/dist/animation/rig/bindRig.js +1 -1
  5. package/dist/animation/rig/rigNode.js.map +1 -1
  6. package/dist/barrel/canvas.js +7 -3
  7. package/dist/barrel/interactions.js +5 -4
  8. package/dist/barrel/paths.js +3 -3
  9. package/dist/barrel/renderer.js +3 -2
  10. package/dist/barrel/routing.js +6 -11
  11. package/dist/barrel/scene.js +8 -5
  12. package/dist/barrel/viewport.js +4 -3
  13. package/dist/canvas/Canvas.js +3 -1
  14. package/dist/canvas/Canvas.js.map +1 -1
  15. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  16. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  17. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  18. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  19. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
  20. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  22. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  24. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/features.js +45 -5
  26. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/props.js +1 -11
  28. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  29. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  30. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  32. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  34. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  35. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +93 -0
  37. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  38. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  39. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/useCanvasTools.js +8 -8
  41. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  43. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +13 -2
  45. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -3
  47. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  49. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +5 -3
  51. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  53. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  54. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  55. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  56. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -4
  57. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  58. package/dist/canvas/SceneCanvas.js +16 -14
  59. package/dist/canvas/SceneCanvas.js.map +1 -1
  60. package/dist/canvas/SurfaceCanvas.js +5 -22
  61. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  62. package/dist/canvas/canvasRenderer.js +9 -5
  63. package/dist/canvas/canvasRenderer.js.map +1 -1
  64. package/dist/canvas/contextAttributes.js +14 -0
  65. package/dist/canvas/contextAttributes.js.map +1 -0
  66. package/dist/canvas/deps/editAnchors.js +9 -113
  67. package/dist/canvas/deps/editAnchors.js.map +1 -1
  68. package/dist/canvas/deps/insert.js +4 -2
  69. package/dist/canvas/deps/insert.js.map +1 -1
  70. package/dist/canvas/deps/pathEditCommits.js +193 -0
  71. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  72. package/dist/canvas/deps/snap.js +12 -9
  73. package/dist/canvas/deps/snap.js.map +1 -1
  74. package/dist/canvas/deps/transformFollowers.js +20 -0
  75. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  76. package/dist/canvas/derivedPath.js +20 -1
  77. package/dist/canvas/derivedPath.js.map +1 -1
  78. package/dist/canvas/drawnBounds.js +29 -3
  79. package/dist/canvas/drawnBounds.js.map +1 -1
  80. package/dist/canvas/nodeShapes/derivedPainter.js +9 -1
  81. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  82. package/dist/canvas/nodeShapes/pathPainter.js +3 -2
  83. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  84. package/dist/canvas/nodeShapes/shapePainter.js +1 -1
  85. package/dist/canvas/overlayRoleStrokes.js +12 -0
  86. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  87. package/dist/canvas/pathOverlay.js +27 -7
  88. package/dist/canvas/pathOverlay.js.map +1 -1
  89. package/dist/canvas/renderDebugSnapshot.js +1 -0
  90. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  91. package/dist/canvas/renderSceneToPixels.js +6 -5
  92. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  93. package/dist/canvas/sceneAdapter.js.map +1 -1
  94. package/dist/canvas/useCanvasPaint.js +2 -1
  95. package/dist/canvas/useCanvasPaint.js.map +1 -1
  96. package/dist/canvas/useInFlightBounds.js +52 -0
  97. package/dist/canvas/useInFlightBounds.js.map +1 -0
  98. package/dist/canvas/useViewHelpers.js +7 -9
  99. package/dist/canvas/useViewHelpers.js.map +1 -1
  100. package/dist/clipboard.d.ts +3 -2
  101. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  102. package/dist/clone.d.ts +4 -3
  103. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  104. package/dist/core/ops/setDependsOn.js +37 -0
  105. package/dist/core/ops/setDependsOn.js.map +1 -0
  106. package/dist/core/ops/writtenBy.js +53 -0
  107. package/dist/core/ops/writtenBy.js.map +1 -0
  108. package/dist/core/pages/fitViewToPage.js +12 -1
  109. package/dist/core/pages/fitViewToPage.js.map +1 -1
  110. package/dist/core/pages/pageEdits.js +164 -0
  111. package/dist/core/pages/pageEdits.js.map +1 -0
  112. package/dist/core/pages/pages.js +30 -1
  113. package/dist/core/pages/pages.js.map +1 -1
  114. package/dist/core/scene/NodeRouting.js +8 -3
  115. package/dist/core/scene/NodeRouting.js.map +1 -1
  116. package/dist/core/scene/arrivalWindow.js.map +1 -1
  117. package/dist/core/scene/carryDescendants.js +51 -0
  118. package/dist/core/scene/carryDescendants.js.map +1 -0
  119. package/dist/core/scene/docValues.js +57 -0
  120. package/dist/core/scene/docValues.js.map +1 -0
  121. package/dist/core/scene/editPass.js +111 -0
  122. package/dist/core/scene/editPass.js.map +1 -0
  123. package/dist/core/scene/effectivePose.js +7 -1
  124. package/dist/core/scene/effectivePose.js.map +1 -1
  125. package/dist/core/scene/fieldEdits.js +4 -1
  126. package/dist/core/scene/fieldEdits.js.map +1 -1
  127. package/dist/core/scene/historyFacade.js +1 -0
  128. package/dist/core/scene/historyFacade.js.map +1 -1
  129. package/dist/core/scene/layoutPass.js +28 -3
  130. package/dist/core/scene/layoutPass.js.map +1 -1
  131. package/dist/core/scene/recorder.js +28 -19
  132. package/dist/core/scene/recorder.js.map +1 -1
  133. package/dist/core/scene/scene.js +15 -9
  134. package/dist/core/scene/scene.js.map +1 -1
  135. package/dist/core/scene/sceneChanges.js +13 -0
  136. package/dist/core/scene/sceneChanges.js.map +1 -0
  137. package/dist/core/scene/sceneEdits.js +8 -1
  138. package/dist/core/scene/sceneEdits.js.map +1 -1
  139. package/dist/core/scene/sceneMutators.js +1 -0
  140. package/dist/core/scene/sceneMutators.js.map +1 -1
  141. package/dist/core/scene/sceneRecording.js +20 -3
  142. package/dist/core/scene/sceneRecording.js.map +1 -1
  143. package/dist/core/scene/sceneSelection.js +4 -1
  144. package/dist/core/scene/sceneSelection.js.map +1 -1
  145. package/dist/core/scene/sceneState.js +11 -5
  146. package/dist/core/scene/sceneState.js.map +1 -1
  147. package/dist/core/scene/serialization.js +5 -3
  148. package/dist/core/scene/serialization.js.map +1 -1
  149. package/dist/core/scene/types.js.map +1 -1
  150. package/dist/core/scene/unitScopes.js +19 -1
  151. package/dist/core/scene/unitScopes.js.map +1 -1
  152. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  153. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  154. package/dist/core/shapeKinds.js +25 -11
  155. package/dist/core/shapeKinds.js.map +1 -1
  156. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-BHuoMs1o.d.ts} +1 -1
  157. package/dist/debug/createDebugOverlayLayer.js +16 -111
  158. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  159. package/dist/debug/createDebugSink.js +2 -1
  160. package/dist/debug/createDebugSink.js.map +1 -1
  161. package/dist/debug/defaultTheme.js +4 -2
  162. package/dist/debug/defaultTheme.js.map +1 -1
  163. package/dist/debug/features.js +17 -0
  164. package/dist/debug/features.js.map +1 -0
  165. package/dist/debug/overlayPanels.js +117 -0
  166. package/dist/debug/overlayPanels.js.map +1 -0
  167. package/dist/debug/parseDebugFlags.js +4 -13
  168. package/dist/debug/parseDebugFlags.js.map +1 -1
  169. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-BpZNOv7q.d.ts} +114 -892
  170. package/dist/devtools/handlers.js +112 -0
  171. package/dist/devtools/handlers.js.map +1 -0
  172. package/dist/devtools/history.js +71 -0
  173. package/dist/devtools/history.js.map +1 -0
  174. package/dist/devtools/hook.js +74 -0
  175. package/dist/devtools/hook.js.map +1 -0
  176. package/dist/devtools/label.js +25 -0
  177. package/dist/devtools/label.js.map +1 -0
  178. package/dist/devtools/notices.js +58 -0
  179. package/dist/devtools/notices.js.map +1 -0
  180. package/dist/devtools/protocol.js +7 -0
  181. package/dist/devtools/protocol.js.map +1 -0
  182. package/dist/devtools/registry.js +90 -0
  183. package/dist/devtools/registry.js.map +1 -0
  184. package/dist/devtools/root.js +80 -0
  185. package/dist/devtools/root.js.map +1 -0
  186. package/dist/devtools/serialize.js +86 -0
  187. package/dist/devtools/serialize.js.map +1 -0
  188. package/dist/devtools/session.js +111 -0
  189. package/dist/devtools/session.js.map +1 -0
  190. package/dist/devtools/transport.js +67 -0
  191. package/dist/devtools/transport.js.map +1 -0
  192. package/dist/devtools/useDevtoolsRoot.js +81 -0
  193. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  194. package/dist/devtools-protocol.d.ts +435 -0
  195. package/dist/devtools-protocol.js +7 -0
  196. package/dist/devtools.d.ts +8 -0
  197. package/dist/devtools.js +12 -0
  198. package/dist/devtools.js.map +1 -0
  199. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  200. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  201. package/dist/features/ingestion/contentHandlers.js +25 -2
  202. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  203. package/dist/features/measure/measureCommands.js +7 -5
  204. package/dist/features/measure/measureCommands.js.map +1 -1
  205. package/dist/features/paths/index.js +2 -2
  206. package/dist/features/paths/pathEditExtension.js.map +1 -1
  207. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  208. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  209. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  210. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  211. package/dist/features/selection/selectionPose.js +4 -1
  212. package/dist/features/selection/selectionPose.js.map +1 -1
  213. package/dist/features/snap/gather.js +9 -1
  214. package/dist/features/snap/gather.js.map +1 -1
  215. package/dist/features/snap/lazySession.js +17 -0
  216. package/dist/features/snap/lazySession.js.map +1 -0
  217. package/dist/features/snap/marks.js.map +1 -1
  218. package/dist/features/snap/probes2d.js +27 -19
  219. package/dist/features/snap/probes2d.js.map +1 -1
  220. package/dist/features/snap/readoutLayer.js.map +1 -1
  221. package/dist/features/snap/roles.js +2 -1
  222. package/dist/features/snap/roles.js.map +1 -1
  223. package/dist/features/snap/rotationSource.js +25 -8
  224. package/dist/features/snap/rotationSource.js.map +1 -1
  225. package/dist/features/snap/sceneSession2d.js +6 -4
  226. package/dist/features/snap/sceneSession2d.js.map +1 -1
  227. package/dist/features/snap/session2d.js +37 -23
  228. package/dist/features/snap/session2d.js.map +1 -1
  229. package/dist/features/snap/spanSnap.js +5 -2
  230. package/dist/features/snap/spanSnap.js.map +1 -1
  231. package/dist/icons.table.js +1 -1
  232. package/dist/icons.table.js.map +1 -1
  233. package/dist/icons.table.json +0 -10
  234. package/dist/index.d.ts +701 -696
  235. package/dist/index.js +35 -29
  236. package/dist/interactions/actions/align/action.js +6 -4
  237. package/dist/interactions/actions/align/action.js.map +1 -1
  238. package/dist/interactions/actions/align/align.js.map +1 -1
  239. package/dist/interactions/actions/booleans/action.js +7 -1
  240. package/dist/interactions/actions/booleans/action.js.map +1 -1
  241. package/dist/interactions/actions/clone/action.js +1 -1
  242. package/dist/interactions/actions/clone/behaviors.js +4 -4
  243. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  244. package/dist/interactions/actions/defaults/bendSegment.js +1 -1
  245. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  246. package/dist/interactions/actions/defaults/createPaintAction.js +1 -1
  247. package/dist/interactions/actions/defaults/delete.js +48 -39
  248. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  249. package/dist/interactions/actions/defaults/duplicate.js +1 -1
  250. package/dist/interactions/actions/defaults/group.js +2 -2
  251. package/dist/interactions/actions/defaults/ingest.js +7 -5
  252. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  253. package/dist/interactions/actions/defaults/nudge.js +13 -38
  254. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  255. package/dist/interactions/actions/distribute/action.js +6 -4
  256. package/dist/interactions/actions/distribute/action.js.map +1 -1
  257. package/dist/interactions/actions/drawnBoxes.js +2 -1
  258. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  259. package/dist/interactions/actions/edit-anchors/action.js +3 -6
  260. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  261. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  262. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  263. package/dist/interactions/actions/flip/action.js +6 -4
  264. package/dist/interactions/actions/flip/action.js.map +1 -1
  265. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  266. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  267. package/dist/interactions/actions/insert/action.js +1 -2
  268. package/dist/interactions/actions/insert/action.js.map +1 -1
  269. package/dist/interactions/actions/insert/commit.js +1 -1
  270. package/dist/interactions/actions/insert/parent.js +11 -0
  271. package/dist/interactions/actions/insert/parent.js.map +1 -0
  272. package/dist/interactions/actions/insert/placeAction.js +7 -2
  273. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  274. package/dist/interactions/actions/insert/session.js +5 -1
  275. package/dist/interactions/actions/insert/session.js.map +1 -1
  276. package/dist/interactions/actions/move/action.js +8 -4
  277. package/dist/interactions/actions/move/action.js.map +1 -1
  278. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  279. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  280. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  281. package/dist/interactions/actions/move/capture.js +1 -1
  282. package/dist/interactions/actions/move/commit/index.js +0 -2
  283. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  284. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  285. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  286. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  287. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  288. package/dist/interactions/actions/move/frame.js +4 -10
  289. package/dist/interactions/actions/move/frame.js.map +1 -1
  290. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  291. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  292. package/dist/interactions/actions/move/layout/container.js +2 -1
  293. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  294. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  295. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  296. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  297. package/dist/interactions/actions/move/layout/exits.js +5 -5
  298. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  299. package/dist/interactions/actions/move/session.js +14 -3
  300. package/dist/interactions/actions/move/session.js.map +1 -1
  301. package/dist/interactions/actions/move/translate.js +8 -37
  302. package/dist/interactions/actions/move/translate.js.map +1 -1
  303. package/dist/interactions/actions/moveNodes.js +87 -0
  304. package/dist/interactions/actions/moveNodes.js.map +1 -0
  305. package/dist/interactions/actions/planeInput.js.map +1 -1
  306. package/dist/interactions/actions/resize/action.js +83 -72
  307. package/dist/interactions/actions/resize/action.js.map +1 -1
  308. package/dist/interactions/actions/resize/commit.js +2 -1
  309. package/dist/interactions/actions/resize/commit.js.map +1 -1
  310. package/dist/interactions/actions/resize/session.js +2 -0
  311. package/dist/interactions/actions/resize/session.js.map +1 -1
  312. package/dist/interactions/actions/resolvePreviews.js +3 -3
  313. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  314. package/dist/interactions/actions/rotate/action.js +7 -4
  315. package/dist/interactions/actions/rotate/action.js.map +1 -1
  316. package/dist/interactions/actions/rotate/commit.js +2 -1
  317. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  318. package/dist/interactions/actions/rotate/session.js +2 -0
  319. package/dist/interactions/actions/rotate/session.js.map +1 -1
  320. package/dist/interactions/actions/selectionSubtree.js +13 -2
  321. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  322. package/dist/interactions/actions/testUtils.js.map +1 -1
  323. package/dist/interactions/actions/transform-again/action.js +5 -6
  324. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  325. package/dist/interactions/actions/transformFinish.js +106 -0
  326. package/dist/interactions/actions/transformFinish.js.map +1 -0
  327. package/dist/kitSettingsSchema.js +0 -4
  328. package/dist/kitSettingsSchema.js.map +1 -1
  329. package/dist/move.d.ts +5 -4
  330. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  331. package/dist/resize.d.ts +4 -3
  332. package/dist/routing.d.ts +1 -1
  333. package/dist/routing.js +2 -2
  334. package/dist/test-hook/createTestHook.js +1 -1
  335. package/dist/test-hook/createTestHook.js.map +1 -1
  336. package/dist/test-hook/useTestHook.js +2 -1
  337. package/dist/test-hook/useTestHook.js.map +1 -1
  338. package/dist/test-seams.d.ts +9 -5
  339. package/dist/tools/builtin/pen/penHandle.js +3 -5
  340. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  341. package/dist/tools/index.js +26 -0
  342. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  343. package/dist/types-BN9hbdMI.d.ts +184 -0
  344. package/dist/types-CniVctmk.d.ts +949 -0
  345. package/dist/types-oFeQ5VjV.d.ts +264 -0
  346. package/dist/version.js +1 -1
  347. package/package.json +35 -25
  348. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  349. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  350. package/dist/tools/builtin/bone/boneActions.js +0 -156
  351. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  352. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  353. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  354. package/dist/tools/builtin/bone/placedRig.js +0 -28
  355. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  356. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  357. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  358. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  359. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  360. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  361. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  362. package/dist/tools/builtin/weight/weightActions.js +0 -140
  363. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  364. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  365. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"snap.js","names":[],"sources":["../../../src/canvas/deps/snap.ts"],"sourcesContent":["/**\n * `useSnapDepSource` — wires the `snap` dep whose `begin` every snapping\n * gesture runs: a session over the snap sources `session` names, writing its\n * marks to the outcome store this hook returns.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useKitSettings, useLatest } from '@weasel-js/react';\nimport type { DebugSink } from '@weasel-js/routing';\nimport type { SnapDep } from 'interactions/actions/snapDep';\nimport { ownGeometryAt } from 'features/paths/pathSnap/pathSnapSource';\nimport type { View } from 'core/viewport/view';\nimport type { Scene } from 'core/scene/types';\nimport type { DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport { snapSourcesFor } from 'features/snap/gather';\nimport { sceneSession2d } from 'features/snap/sceneSession2d';\nimport { DEFAULT_SNAP_PX } from 'features/snap/session2d';\nimport type { SnapReadoutDep } from 'features/snap/readoutDep';\nimport { rotationSnapSource } from 'features/snap/rotationSource';\nimport { pathSnapSource } from 'features/paths/pathSnap/pathSnapSource';\nimport type { SnapSource } from 'features/snap/types';\n\ntype Point = { x: number; y: number };\n\n/** What `begin` reads: the scene its probes come from and the sources it asks. */\nexport interface SnapSessionSources {\n scene: Scene<unknown, string, unknown>;\n /** The boxes the subject is drawn with, read when a session begins. */\n boxes: () => DrawnBoxes;\n /** `<SceneCanvas snap.sources>`, then the presets' `snapSources`. */\n sources: readonly SnapSource[];\n /** Sources `useSnapSource` adds, after `sources`. */\n registry?: SnapSourceRegistry;\n /** Ids of the canvas's default sources to leave out. */\n omit?: readonly string[];\n /** Overrides the kit setting `targets.snapPx`. */\n reachPx?: number;\n /** How a node's rotation is read, for angle targets. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** How a pose's origin is read, for the `origin` probe. */\n originProjection?: OriginProjection<unknown>;\n /** The canvas's debug sink, read when a session begins: each frame's\n * candidates are recorded into it. */\n debug?: () => DebugSink | null | undefined;\n}\n\n/** The sources every canvas asks after its own, unless `omit` names one:\n * quarter turns and other nodes' angles for a rotate, and other nodes'\n * anchors for a point gesture, under `pathSnapSource`'s own id, so a\n * consumer's `pathSnapSource()` takes its place. */\nconst DEFAULT_SNAP_SOURCES: readonly SnapSource[] = [\n rotationSnapSource(),\n { ...pathSnapSource({ only: ['anchor'] }), gestures: ['point'] },\n];\n\n/** `listed`, one source per id (the last one listed, where it stands),\n * then each default whose id is neither listed nor in `omit`. */\nfunction withDefaults(listed: readonly SnapSource[], omit: readonly string[] = []): SnapSource[] {\n const last = new Map(listed.map((s, i) => [s.id, i]));\n const taken = new Set([...last.keys(), ...omit]);\n return [...listed.filter((s, i) => last.get(s.id) === i), ...DEFAULT_SNAP_SOURCES.filter((s) => !taken.has(s.id))];\n}\n\nexport function useSnapDepSource(\n viewRef: React.RefObject<View | null>,\n session: SnapSessionSources,\n /** The canvas's outcome store, which its readout layer draws. */\n store: SnapOutcomeStore,\n): SnapOutcomeStore {\n const sessionRef = useLatest(session);\n const kitSettings = useKitSettings();\n useDepSource('snapReadout', (): SnapReadoutDep => ({ store }));\n\n useDepSource('snap', (): SnapDep => ({\n begin: (req) => {\n const s = sessionRef.current;\n const view = viewRef.current;\n const sources = withDefaults([...s.sources, ...(s.registry?.list() ?? [])], s.omit);\n const wantsGeometry = snapSourcesFor(sources, req.gesture).some((src) => src.roles.includes('geometry'));\n const debug = s.debug?.() ?? undefined;\n return sceneSession2d({\n req,\n scene: s.scene,\n boxes: s.boxes(),\n sources,\n ...(wantsGeometry ? { geometryAt: (press: Point, ids: readonly string[]) => ownGeometryAt(s.scene, press, ids, view?.scale ?? { x: 1, y: 1 }) } : {}),\n view: () => viewRef.current,\n reachPx: s.reachPx ?? kitSettings.get('targets.snapPx') ?? DEFAULT_SNAP_PX,\n ...(s.poseDescriptor ? { poseDescriptor: s.poseDescriptor } : {}),\n ...(s.originProjection ? { originProjection: s.originProjection } : {}),\n ...(debug ? { debug } : {}),\n store,\n });\n },\n }));\n return store;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,IAAM,uBAA8C,CAClD,mBAAmB,GACnB;CAAE,GAAG,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;CAAG,UAAU,CAAC,OAAO;AAAE,CACjE;;;AAIA,SAAS,aAAa,QAA+B,OAA0B,CAAC,GAAiB;CAC/F,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACpD,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,GAAG,GAAG,IAAI,CAAC;CAC/C,OAAO,CAAC,GAAG,OAAO,QAAQ,GAAG,MAAM,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,GAAG,GAAG,qBAAqB,QAAQ,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;AACnH;AAEA,SAAgB,iBACd,SACA,SAEA,OACkB;CAClB,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,cAAc,eAAe;CACnC,aAAa,sBAAsC,EAAE,MAAM,EAAE;CAE7D,aAAa,eAAwB,EACnC,QAAQ,QAAQ;EACd,MAAM,IAAI,WAAW;EACrB,MAAM,OAAO,QAAQ;EACrB,MAAM,UAAU,aAAa,CAAC,GAAG,EAAE,SAAS,GAAI,EAAE,UAAU,KAAK,KAAK,CAAC,CAAE,GAAG,EAAE,IAAI;EAClF,MAAM,gBAAgB,eAAe,SAAS,IAAI,OAAO,CAAC,CAAC,MAAM,QAAQ,IAAI,MAAM,SAAS,UAAU,CAAC;EACvG,MAAM,QAAQ,EAAE,QAAQ,KAAK;EAC7B,OAAO,eAAe;GACpB;GACA,OAAO,EAAE;GACT,OAAO,EAAE,MAAM;GACf;GACA,GAAI,gBAAgB,EAAE,aAAa,OAAc,QAA2B,cAAc,EAAE,OAAO,OAAO,KAAK,MAAM,SAAS;IAAE,GAAG;IAAG,GAAG;GAAE,CAAC,EAAE,IAAI,CAAC;GACnJ,YAAY,QAAQ;GACpB,SAAS,EAAE,WAAW,YAAY,IAAI,gBAAgB,KAAK;GAC3D,GAAI,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,IAAI,CAAC;GAC/D,GAAI,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,IAAI,CAAC;GACrE,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB;EACF,CAAC;CACH,EACF,EAAE;CACF,OAAO;AACT"}
1
+ {"version":3,"file":"snap.js","names":[],"sources":["../../../src/canvas/deps/snap.ts"],"sourcesContent":["/**\n * `useSnapDepSource` — wires the `snap` dep whose `begin` every snapping\n * gesture runs: a session over the snap sources `session` names, writing its\n * marks to the outcome store this hook returns.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useKitSettings, useLatest } from '@weasel-js/react';\nimport type { DebugSink } from '@weasel-js/routing';\nimport type { SnapDep } from 'interactions/actions/snapDep';\nimport { ownGeometryAt } from 'features/paths/pathSnap/pathSnapSource';\nimport type { View } from 'core/viewport/view';\nimport type { Scene } from 'core/scene/types';\nimport type { DrawnBoxes } from 'interactions/actions/drawnBoxes';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { OriginProjection } from 'interactions/gestures/shared/originProjection';\nimport type { SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport type { SnapSourceRegistry } from 'features/snap/registry';\nimport { assertSnapSourceIds, snapSourcesFor } from 'features/snap/gather';\nimport { sceneSession2d } from 'features/snap/sceneSession2d';\nimport { DEFAULT_SNAP_PX } from 'features/snap/session2d';\nimport type { SnapReadoutDep } from 'features/snap/readoutDep';\nimport { rotationSnapSource } from 'features/snap/rotationSource';\nimport { pathSnapSource } from 'features/paths/pathSnap/pathSnapSource';\nimport type { SnapSource } from 'features/snap/types';\n\ntype Point = { x: number; y: number };\n\n/** What `begin` reads: the scene its probes come from and the sources it asks. */\nexport interface SnapSessionSources {\n scene: Scene<unknown, string, unknown>;\n /** The boxes the subject is drawn with, read when a session begins. */\n boxes: () => DrawnBoxes;\n /** `<SceneCanvas snap.sources>`, then the presets' `snapSources`. */\n sources: readonly SnapSource[];\n /** Sources `useSnapSource` adds, after `sources`. */\n registry?: SnapSourceRegistry;\n /** Ids of the canvas's default sources to leave out. */\n omit?: readonly string[];\n /** Overrides the kit setting `targets.snapPx`. */\n reachPx?: number;\n /** How a node's rotation is read, for angle targets. */\n poseDescriptor?: PoseDescriptor<unknown>;\n /** How a pose's origin is read, for the `origin` probe. */\n originProjection?: OriginProjection<unknown>;\n /** The canvas's debug sink, read when a session begins: each frame's\n * candidates are recorded into it. */\n debug?: () => DebugSink | null | undefined;\n}\n\n/** The sources every canvas asks after its own, unless `omit` names one:\n * quarter turns and other nodes' angles for a rotate, and other nodes'\n * anchors for a point gesture, under `pathSnapSource`'s own id, so a\n * consumer's `pathSnapSource()` takes its place. */\nconst DEFAULT_SNAP_SOURCES: readonly SnapSource[] = [\n rotationSnapSource(),\n { ...pathSnapSource({ only: ['anchor'] }), gestures: ['point'] },\n];\n\n/** `listed`, which must not repeat an id, then each default whose id is\n * neither listed nor in `omit`. */\nfunction withDefaults(listed: readonly SnapSource[], omit: readonly string[] = []): SnapSource[] {\n assertSnapSourceIds(listed);\n const taken = new Set([...listed.map((s) => s.id), ...omit]);\n return [...listed, ...DEFAULT_SNAP_SOURCES.filter((s) => !taken.has(s.id))];\n}\n\nexport function useSnapDepSource(\n viewRef: React.RefObject<View | null>,\n session: SnapSessionSources,\n /** The canvas's outcome store, which its readout layer draws. */\n store: SnapOutcomeStore,\n): SnapOutcomeStore {\n const sessionRef = useLatest(session);\n const kitSettings = useKitSettings();\n useDepSource('snapReadout', (): SnapReadoutDep => ({ store }));\n\n useDepSource('snap', (): SnapDep => ({\n begin: (req) => {\n const s = sessionRef.current;\n const view = viewRef.current;\n const omit = new Set(req.omit);\n const sources = [\n ...withDefaults([...s.sources, ...(s.registry?.list() ?? [])], s.omit).filter((src) => !omit.has(src.id)),\n ...(req.sources ?? []),\n ];\n assertSnapSourceIds(sources);\n const wantsGeometry = snapSourcesFor(sources, req.gesture).some((src) => src.roles.includes('geometry'));\n const debug = s.debug?.() ?? undefined;\n const reachPx = s.reachPx ?? kitSettings.get('targets.snapPx') ?? DEFAULT_SNAP_PX;\n return sceneSession2d({\n req,\n scene: s.scene,\n boxes: s.boxes(),\n sources,\n ...(wantsGeometry ? { geometryAt: (press: Point, ids: readonly string[]) => ownGeometryAt(s.scene, press, ids, view?.scale ?? { x: 1, y: 1 }, reachPx) } : {}),\n view: () => viewRef.current,\n reachPx,\n ...(s.poseDescriptor ? { poseDescriptor: s.poseDescriptor } : {}),\n ...(s.originProjection ? { originProjection: s.originProjection } : {}),\n ...(debug ? { debug } : {}),\n store,\n });\n },\n }));\n return store;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,IAAM,uBAA8C,CAClD,mBAAmB,GACnB;CAAE,GAAG,eAAe,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;CAAG,UAAU,CAAC,OAAO;AAAE,CACjE;;;AAIA,SAAS,aAAa,QAA+B,OAA0B,CAAC,GAAiB;CAC/F,oBAAoB,MAAM;CAC1B,MAAM,wBAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,MAAM,EAAE,EAAE,GAAG,GAAG,IAAI,CAAC;CAC3D,OAAO,CAAC,GAAG,QAAQ,GAAG,qBAAqB,QAAQ,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;AAC5E;AAEA,SAAgB,iBACd,SACA,SAEA,OACkB;CAClB,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,cAAc,eAAe;CACnC,aAAa,sBAAsC,EAAE,MAAM,EAAE;CAE7D,aAAa,eAAwB,EACnC,QAAQ,QAAQ;EACd,MAAM,IAAI,WAAW;EACrB,MAAM,OAAO,QAAQ;EACrB,MAAM,OAAO,IAAI,IAAI,IAAI,IAAI;EAC7B,MAAM,UAAU,CACd,GAAG,aAAa,CAAC,GAAG,EAAE,SAAS,GAAI,EAAE,UAAU,KAAK,KAAK,CAAC,CAAE,GAAG,EAAE,IAAI,CAAC,CAAC,QAAQ,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,GACxG,GAAI,IAAI,WAAW,CAAC,CACtB;EACA,oBAAoB,OAAO;EAC3B,MAAM,gBAAgB,eAAe,SAAS,IAAI,OAAO,CAAC,CAAC,MAAM,QAAQ,IAAI,MAAM,SAAS,UAAU,CAAC;EACvG,MAAM,QAAQ,EAAE,QAAQ,KAAK;EAC7B,MAAM,UAAU,EAAE,WAAW,YAAY,IAAI,gBAAgB,KAAK;EAClE,OAAO,eAAe;GACpB;GACA,OAAO,EAAE;GACT,OAAO,EAAE,MAAM;GACf;GACA,GAAI,gBAAgB,EAAE,aAAa,OAAc,QAA2B,cAAc,EAAE,OAAO,OAAO,KAAK,MAAM,SAAS;IAAE,GAAG;IAAG,GAAG;GAAE,GAAG,OAAO,EAAE,IAAI,CAAC;GAC5J,YAAY,QAAQ;GACpB;GACA,GAAI,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,eAAe,IAAI,CAAC;GAC/D,GAAI,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,IAAI,CAAC;GACrE,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB;EACF,CAAC;CACH,EACF,EAAE;CACF,OAAO;AACT"}
@@ -0,0 +1,20 @@
1
+ import { useDepSource } from "@weasel-js/routing/react";
2
+
3
+ //#region src/canvas/deps/transformFollowers.ts
4
+ /**
5
+ * Add a follower to the `transformFollowers` dep for the lifetime of the
6
+ * calling component, after every one already there in its scope and the
7
+ * scopes above. `follower` is read each time the dep is; answering null adds
8
+ * nothing.
9
+ */
10
+ function useTransformFollower(follower) {
11
+ useDepSource("transformFollowers", (below) => {
12
+ const under = below() ?? [];
13
+ const mine = follower();
14
+ return mine === null ? under : [...under, mine];
15
+ });
16
+ }
17
+
18
+ //#endregion
19
+ export { useTransformFollower };
20
+ //# sourceMappingURL=transformFollowers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformFollowers.js","names":[],"sources":["../../../src/canvas/deps/transformFollowers.ts"],"sourcesContent":["import { useDepSource } from '@weasel-js/routing/react';\nimport type { TransformFollower } from 'interactions/actions/transformFinish';\n\n/**\n * Add a follower to the `transformFollowers` dep for the lifetime of the\n * calling component, after every one already there in its scope and the\n * scopes above. `follower` is read each time the dep is; answering null adds\n * nothing.\n */\nexport function useTransformFollower(follower: () => TransformFollower | null): void {\n useDepSource('transformFollowers', (below) => {\n const under = below() ?? [];\n const mine = follower();\n return mine === null ? under : [...under, mine];\n });\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,qBAAqB,UAAgD;CACnF,aAAa,uBAAuB,UAAU;EAC5C,MAAM,QAAQ,MAAM,KAAK,CAAC;EAC1B,MAAM,OAAO,SAAS;EACtB,OAAO,SAAS,OAAO,QAAQ,CAAC,GAAG,OAAO,IAAI;CAChD,CAAC;AACH"}
@@ -174,7 +174,26 @@ function withoutInheritedFrame(path, painted, own) {
174
174
  });
175
175
  return carryPathBy(path, redo === null ? undo : multiply(undo, redo));
176
176
  }
177
+ var drawnScenes = /* @__PURE__ */ new WeakMap();
178
+ /** `scene` read as `drawnBoundsOf` reads one, each node that shows a computed
179
+ * path answering it. One object per scene, so it can key a memo. */
180
+ function drawnBoundsScene(scene) {
181
+ let held = drawnScenes.get(scene);
182
+ if (held === void 0) {
183
+ const pathOf = sceneDerivedPathOf(scene);
184
+ held = {
185
+ get: (id) => scene.get(id),
186
+ childrenOf: (id) => scene.childrenOf(id),
187
+ derivedPathOf: (id) => {
188
+ const node = scene.get(id);
189
+ return node !== void 0 && showsComputedPath(scene, node) ? pathOf(node) : null;
190
+ }
191
+ };
192
+ drawnScenes.set(scene, held);
193
+ }
194
+ return held;
195
+ }
177
196
 
178
197
  //#endregion
179
- export { resolveDerivedPath, sceneDepLookup, sceneDerivedPathOf, scenePaintedPoseOf, sceneShownToPose, showsComputedPath, withDerivedPaths };
198
+ export { drawnBoundsScene, resolveDerivedPath, sceneDepLookup, sceneDerivedPathOf, scenePaintedPoseOf, sceneShownToPose, showsComputedPath, withDerivedPaths };
180
199
  //# sourceMappingURL=derivedPath.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"derivedPath.js","names":[],"sources":["../../src/canvas/derivedPath.ts"],"sourcesContent":["/**\n * The render walk's half of derived paths: resolving one against a scene, and\n * handing it to the painter.\n *\n * The resolver itself lives in `core/scene/derivedPath.ts`, beside\n * `derivedPose` — a *pose* can derive from a dependency's path, so\n * `effectivePose` has to be able to reach it.\n */\nimport type { DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { multiply, poseFrameInverseOf, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { asNodeId } from 'core/scene/types';\nimport { definesFrame, derivedDepOf, effectivePose } from 'core/scene/effectivePose';\nimport { composesFrames, composeWorldPose, type PoseAdapter } from 'core/scene/composeFrame';\nimport { resolveDerivedPath } from 'core/scene/derivedPath';\nimport { createNodeMemoScope, type NodeMemoScope } from 'core/scene/nodeMemo';\nimport { carryBoxPath, carryPathBy, pathAsPainted } from 'core/scene/depFrame';\nimport type { SceneViewDrawOne } from './NodeShape';\n\nexport { resolveDerivedPath } from 'core/scene/derivedPath';\n\ntype ToPose<TData, TLayer extends string, TPose> = (node: Node<TData, TLayer, TPose>) => TPose;\n\n/** `node` as the scene shows it, its `pose` an override's or a derived one: the stored node\n * itself where the two agree. */\nfunction shownNode<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: Node<TData, TLayer, TPose>,\n): Node<TData, TLayer, TPose> {\n const pose = effectivePose(scene, node);\n return pose === node.pose ? node : { ...node, pose };\n}\n\n/** A slot's `toPose`, handed each node as the scene shows it, so a view drawn through one\n * shows a drag in flight and a rig's motion as every other view does. */\nexport function sceneShownToPose<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose: ToPose<TData, TLayer, TPose>,\n): ToPose<TData, TLayer, TPose> {\n return (node) => toPose(shownNode(scene, node));\n}\n\n/**\n * `(node) => the path its pose override shows it by`, in the frame the reader paints in: as\n * the override has it, or under a `toPose` carried from the box the scene shows the node at\n * to the box `toPose` answers.\n */\nfunction sceneOverridePathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: ToPose<TData, TLayer, TPose>,\n): (node: Node<TData, TLayer, TPose>) => Path | undefined {\n if (toPose === undefined) return (node) => scene.overrides.read(node.id)?.path;\n return (node) => {\n const path = scene.overrides.read(node.id)?.path;\n if (path === undefined) return undefined;\n const shown = shownNode(scene, node);\n return carryBoxPath(path, shown.pose, toPose(shown));\n };\n}\n\n/**\n * `(node) => the pose it is painted at`: the slot's `toPose` of the node as the scene shows\n * it if it has one, else the scene's effective pose, folded through the parent chain when\n * the scene's composition makes a stored pose local to its container.\n */\nexport function scenePaintedPoseOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => TPose {\n const local = toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node);\n if (!composesFrames(scene.composition)) return local;\n const chain: PoseAdapter<TPose> = {\n getPose: (id) => {\n const n = scene.get(asNodeId(id));\n return n ? local(n) : (undefined as unknown as TPose);\n },\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n definesFrame: (id) => {\n const n = scene.get(asNodeId(id));\n return n === undefined || definesFrame(n);\n },\n };\n return (node) => composeWorldPose(chain, node.id, scene.composition.compose);\n}\n\n/**\n * `(id) => that node, the pose it is painted at, and the path it derives` —\n * resolved against the scene, so a dependency that is itself derived resolves\n * before it is read.\n *\n * A derived edge is drawn from these coordinates and must meet the nodes it\n * connects, so this reads the pose each one is painted at: see\n * {@link scenePaintedPoseOf}.\n */\nexport function sceneDepLookup<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n if (slot === undefined) return (id) => derivedDepOf(scene, id);\n const ctx: SlotLookup<TPose> = {\n at: scenePaintedPoseOf(scene, toPose) as (node: unknown) => TPose,\n local: (toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node)) as (node: unknown) => TPose,\n slot,\n shown: sceneOverridePathOf(scene, toPose) as (node: unknown) => Path | undefined,\n childrenOf: (id: NodeId) => scene.childrenOf(id),\n lookup: (id: NodeId) => {\n const node = scene.get(id);\n return node === undefined ? undefined : new SlotDep(node, ctx);\n },\n };\n return ctx.lookup;\n}\n\ninterface SlotLookup<TPose> {\n at: (node: unknown) => TPose;\n /** The pose the node is shown at in its own frame: the box its override path is drawn in. */\n local: (node: unknown) => TPose;\n slot: string | NodeMemoScope;\n /** The path a pose override shows a node by, where it has one. */\n shown: (node: unknown) => Path | undefined;\n childrenOf: (id: NodeId) => readonly NodeId[];\n lookup: (id: NodeId) => DerivedDep<TPose> | undefined;\n}\n\n/** A class, not an object literal with a `get path()` — see `SceneDep` in\n * `core/scene/effectivePose.ts` for why. */\nclass SlotDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(posed: unknown, private readonly ctx: SlotLookup<TPose>) {\n this.node = posed as Node<unknown, string, TPose>;\n this.pose = ctx.at(posed);\n }\n\n get path(): Path | null {\n const shown = this.ctx.shown(this.node);\n if (shown !== undefined) return pathAsPainted(shown, this.ctx.local(this.node), this.pose);\n return resolveDerivedPath(\n this.node as never, this.ctx.lookup, this.ctx.childrenOf, this.ctx.slot,\n );\n }\n}\n\n/** Whether something outside `node`'s data says what path it shows: it derives one, or a\n * pose override carries one. The gate both scene walks check before resolving a path. */\nexport function showsComputedPath<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n): boolean {\n return node.dependsOn !== undefined || scene.overrides.read(node.id)?.path !== undefined;\n}\n\n/**\n * `(node) => the path it shows in place of its data's`: a pose override's `path`, else the\n * one it derives, read through {@link sceneDepLookup}. A reader painting through a `toPose`\n * is answered the override's path carried into the box its `toPose` answers.\n */\nexport function sceneDerivedPathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => Path | null {\n const depOf = sceneDepLookup(scene, toPose);\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const derived = (node: Node<TData, TLayer, TPose>): Path | null =>\n resolveDerivedPath(node, depOf, childrenOf, slot);\n const overridePathOf = sceneOverridePathOf(scene, toPose);\n return (node) => overridePathOf(node) ?? derived(node);\n}\n\nconst toPoseScopes = new WeakMap<object, { local?: NodeMemoScope; world?: NodeMemoScope }>();\n\n/** Where one way of reading poses memoizes its paths, or `undefined` for the\n * scene's own slot: two readers painting one scene at different poses must\n * not serve each other's paths. Every reader composing stored poses shares a\n * slot; a `toPose` gets a scope that is collected with the function. */\nfunction memoSlotFor(\n toPose: object | undefined,\n composes: boolean,\n): string | NodeMemoScope | undefined {\n const frame = composes ? 'world' : 'local';\n if (toPose === undefined) return frame === 'world' ? 'kit:derivedPath:world' : undefined;\n let scopes = toPoseScopes.get(toPose);\n if (scopes === undefined) {\n scopes = {};\n toPoseScopes.set(toPose, scopes);\n }\n return (scopes[frame] ??= createNodeMemoScope());\n}\n\n/**\n * Wrap a `drawOne` so a node that derives its geometry, or whose pose override\n * carries a path, arrives at the painter with the resolved path. The painter has no scene handle and so cannot read\n * the dependencies' poses itself; both scene walks wrap here instead.\n *\n * A node that derives nothing passes the caller's `ctx` through untouched —\n * this runs per node per frame, and an added `{ derivedPath: null }` would be\n * an allocation per node for a field no painter reads.\n */\nexport function withDerivedPaths<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const derivedPathOf = sceneDerivedPathOf(scene, toPose);\n const ownPoseOf = composesFrames(scene.composition)\n ? (toPose ? sceneShownToPose(scene, toPose) : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node))\n : null;\n return (node, pose, view, ctx) => {\n if (!showsComputedPath(scene, node)) return drawOne(node, pose, view, ctx);\n let path = derivedPathOf(node);\n if (path !== null && ownPoseOf !== null && scene.overrides.read(node.id)?.path === undefined) {\n path = withoutInheritedFrame(path, pose, ownPoseOf(node));\n }\n return drawOne(node, pose, view, { ...ctx, derivedPath: path });\n };\n}\n\n/**\n * A derived path is drawn from its dependencies' painted poses, so it is already where it\n * shows; but the pose rotation the walk wraps every node in is `painted`'s, which carries its\n * containers' turns as well as its own. Carried back through what the containers add, the\n * wrap turns it by its own frame alone, as it does a root's.\n */\nfunction withoutInheritedFrame(path: Path, painted: unknown, own: unknown): Path {\n const undo = poseFrameInverseOf(painted);\n if (undo === null) return path;\n const box = painted as { x: number; y: number; width: number; height: number };\n const o = own as { rotation?: number; transform?: unknown } | null;\n const redo = poseFrameOf({ x: box.x, y: box.y, width: box.width, height: box.height, rotation: o?.rotation, transform: o?.transform });\n return carryPathBy(path, redo === null ? undo : multiply(undo, redo));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"derivedPath.js","names":[],"sources":["../../src/canvas/derivedPath.ts"],"sourcesContent":["/**\n * The render walk's half of derived paths: resolving one against a scene, and\n * handing it to the painter.\n *\n * The resolver itself lives in `core/scene/derivedPath.ts`, beside\n * `derivedPose` — a *pose* can derive from a dependency's path, so\n * `effectivePose` has to be able to reach it.\n */\nimport type { DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { multiply, poseFrameInverseOf, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { asNodeId } from 'core/scene/types';\nimport { definesFrame, derivedDepOf, effectivePose } from 'core/scene/effectivePose';\nimport { composesFrames, composeWorldPose, type PoseAdapter } from 'core/scene/composeFrame';\nimport { resolveDerivedPath } from 'core/scene/derivedPath';\nimport { createNodeMemoScope, type NodeMemoScope } from 'core/scene/nodeMemo';\nimport { carryBoxPath, carryPathBy, pathAsPainted } from 'core/scene/depFrame';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport type { DrawnBoundsScene } from './drawnBounds';\n\nexport { resolveDerivedPath } from 'core/scene/derivedPath';\n\ntype ToPose<TData, TLayer extends string, TPose> = (node: Node<TData, TLayer, TPose>) => TPose;\n\n/** `node` as the scene shows it, its `pose` an override's or a derived one: the stored node\n * itself where the two agree. */\nfunction shownNode<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: Node<TData, TLayer, TPose>,\n): Node<TData, TLayer, TPose> {\n const pose = effectivePose(scene, node);\n return pose === node.pose ? node : { ...node, pose };\n}\n\n/** A slot's `toPose`, handed each node as the scene shows it, so a view drawn through one\n * shows a drag in flight and a rig's motion as every other view does. */\nexport function sceneShownToPose<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose: ToPose<TData, TLayer, TPose>,\n): ToPose<TData, TLayer, TPose> {\n return (node) => toPose(shownNode(scene, node));\n}\n\n/**\n * `(node) => the path its pose override shows it by`, in the frame the reader paints in: as\n * the override has it, or under a `toPose` carried from the box the scene shows the node at\n * to the box `toPose` answers.\n */\nfunction sceneOverridePathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: ToPose<TData, TLayer, TPose>,\n): (node: Node<TData, TLayer, TPose>) => Path | undefined {\n if (toPose === undefined) return (node) => scene.overrides.read(node.id)?.path;\n return (node) => {\n const path = scene.overrides.read(node.id)?.path;\n if (path === undefined) return undefined;\n const shown = shownNode(scene, node);\n return carryBoxPath(path, shown.pose, toPose(shown));\n };\n}\n\n/**\n * `(node) => the pose it is painted at`: the slot's `toPose` of the node as the scene shows\n * it if it has one, else the scene's effective pose, folded through the parent chain when\n * the scene's composition makes a stored pose local to its container.\n */\nexport function scenePaintedPoseOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => TPose {\n const local = toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node);\n if (!composesFrames(scene.composition)) return local;\n const chain: PoseAdapter<TPose> = {\n getPose: (id) => {\n const n = scene.get(asNodeId(id));\n return n ? local(n) : (undefined as unknown as TPose);\n },\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n definesFrame: (id) => {\n const n = scene.get(asNodeId(id));\n return n === undefined || definesFrame(n);\n },\n };\n return (node) => composeWorldPose(chain, node.id, scene.composition.compose);\n}\n\n/**\n * `(id) => that node, the pose it is painted at, and the path it derives` —\n * resolved against the scene, so a dependency that is itself derived resolves\n * before it is read.\n *\n * A derived edge is drawn from these coordinates and must meet the nodes it\n * connects, so this reads the pose each one is painted at: see\n * {@link scenePaintedPoseOf}.\n */\nexport function sceneDepLookup<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n if (slot === undefined) return (id) => derivedDepOf(scene, id);\n const ctx: SlotLookup<TPose> = {\n at: scenePaintedPoseOf(scene, toPose) as (node: unknown) => TPose,\n local: (toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node)) as (node: unknown) => TPose,\n slot,\n shown: sceneOverridePathOf(scene, toPose) as (node: unknown) => Path | undefined,\n childrenOf: (id: NodeId) => scene.childrenOf(id),\n lookup: (id: NodeId) => {\n const node = scene.get(id);\n return node === undefined ? undefined : new SlotDep(node, ctx);\n },\n };\n return ctx.lookup;\n}\n\ninterface SlotLookup<TPose> {\n at: (node: unknown) => TPose;\n /** The pose the node is shown at in its own frame: the box its override path is drawn in. */\n local: (node: unknown) => TPose;\n slot: string | NodeMemoScope;\n /** The path a pose override shows a node by, where it has one. */\n shown: (node: unknown) => Path | undefined;\n childrenOf: (id: NodeId) => readonly NodeId[];\n lookup: (id: NodeId) => DerivedDep<TPose> | undefined;\n}\n\n/** A class, not an object literal with a `get path()` — see `SceneDep` in\n * `core/scene/effectivePose.ts` for why. */\nclass SlotDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(posed: unknown, private readonly ctx: SlotLookup<TPose>) {\n this.node = posed as Node<unknown, string, TPose>;\n this.pose = ctx.at(posed);\n }\n\n get path(): Path | null {\n const shown = this.ctx.shown(this.node);\n if (shown !== undefined) return pathAsPainted(shown, this.ctx.local(this.node), this.pose);\n return resolveDerivedPath(\n this.node as never, this.ctx.lookup, this.ctx.childrenOf, this.ctx.slot,\n );\n }\n}\n\n/** Whether something outside `node`'s data says what path it shows: it derives one, or a\n * pose override carries one. The gate both scene walks check before resolving a path. */\nexport function showsComputedPath<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n): boolean {\n return node.dependsOn !== undefined || scene.overrides.read(node.id)?.path !== undefined;\n}\n\n/**\n * `(node) => the path it shows in place of its data's`: a pose override's `path`, else the\n * one it derives, read through {@link sceneDepLookup}. A reader painting through a `toPose`\n * is answered the override's path carried into the box its `toPose` answers.\n */\nexport function sceneDerivedPathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => Path | null {\n const depOf = sceneDepLookup(scene, toPose);\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const derived = (node: Node<TData, TLayer, TPose>): Path | null =>\n resolveDerivedPath(node, depOf, childrenOf, slot);\n const overridePathOf = sceneOverridePathOf(scene, toPose);\n return (node) => overridePathOf(node) ?? derived(node);\n}\n\nconst toPoseScopes = new WeakMap<object, { local?: NodeMemoScope; world?: NodeMemoScope }>();\n\n/** Where one way of reading poses memoizes its paths, or `undefined` for the\n * scene's own slot: two readers painting one scene at different poses must\n * not serve each other's paths. Every reader composing stored poses shares a\n * slot; a `toPose` gets a scope that is collected with the function. */\nfunction memoSlotFor(\n toPose: object | undefined,\n composes: boolean,\n): string | NodeMemoScope | undefined {\n const frame = composes ? 'world' : 'local';\n if (toPose === undefined) return frame === 'world' ? 'kit:derivedPath:world' : undefined;\n let scopes = toPoseScopes.get(toPose);\n if (scopes === undefined) {\n scopes = {};\n toPoseScopes.set(toPose, scopes);\n }\n return (scopes[frame] ??= createNodeMemoScope());\n}\n\n/**\n * Wrap a `drawOne` so a node that derives its geometry, or whose pose override\n * carries a path, arrives at the painter with the resolved path. The painter has no scene handle and so cannot read\n * the dependencies' poses itself; both scene walks wrap here instead.\n *\n * A node that derives nothing passes the caller's `ctx` through untouched —\n * this runs per node per frame, and an added `{ derivedPath: null }` would be\n * an allocation per node for a field no painter reads.\n */\nexport function withDerivedPaths<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const derivedPathOf = sceneDerivedPathOf(scene, toPose);\n const ownPoseOf = composesFrames(scene.composition)\n ? (toPose ? sceneShownToPose(scene, toPose) : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node))\n : null;\n return (node, pose, view, ctx) => {\n if (!showsComputedPath(scene, node)) return drawOne(node, pose, view, ctx);\n let path = derivedPathOf(node);\n if (path !== null && ownPoseOf !== null && scene.overrides.read(node.id)?.path === undefined) {\n path = withoutInheritedFrame(path, pose, ownPoseOf(node));\n }\n return drawOne(node, pose, view, { ...ctx, derivedPath: path });\n };\n}\n\n/**\n * A derived path is drawn from its dependencies' painted poses, so it is already where it\n * shows; but the pose rotation the walk wraps every node in is `painted`'s, which carries its\n * containers' turns as well as its own. Carried back through what the containers add, the\n * wrap turns it by its own frame alone, as it does a root's.\n */\nfunction withoutInheritedFrame(path: Path, painted: unknown, own: unknown): Path {\n const undo = poseFrameInverseOf(painted);\n if (undo === null) return path;\n const box = painted as { x: number; y: number; width: number; height: number };\n const o = own as { rotation?: number; transform?: unknown } | null;\n const redo = poseFrameOf({ x: box.x, y: box.y, width: box.width, height: box.height, rotation: o?.rotation, transform: o?.transform });\n return carryPathBy(path, redo === null ? undo : multiply(undo, redo));\n}\n\nconst drawnScenes = new WeakMap<object, DrawnBoundsScene>();\n\n/** `scene` read as `drawnBoundsOf` reads one, each node that shows a computed\n * path answering it. One object per scene, so it can key a memo. */\nexport function drawnBoundsScene<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>): DrawnBoundsScene {\n let held = drawnScenes.get(scene);\n if (held === undefined) {\n const pathOf = sceneDerivedPathOf(scene);\n held = {\n get: (id) => scene.get(id) as never,\n childrenOf: (id) => scene.childrenOf(id),\n derivedPathOf: (id) => {\n const node = scene.get(id);\n return node !== undefined && showsComputedPath(scene, node) ? pathOf(node) : null;\n },\n };\n drawnScenes.set(scene, held);\n }\n return held;\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { axisAlignedBounds } from "../core/geometry/unionBounds.js";
1
+ import { axisAlignedBounds, unionBounds } from "../core/geometry/unionBounds.js";
2
2
  import { framedBounds, poseDescriptorForNode } from "../core/geometry/poseDescriptor.js";
3
3
  import { AUTO_POSE_DESCRIPTOR } from "../core/geometry/autoPoseDescriptor.js";
4
4
  import { targetCorners, targetTransformOf, worldToLocal } from "../core/geometry/targetFrame.js";
5
5
  import { NO_INK_EXTENTS, inkExtentsBetween, outsetBox } from "../core/geometry/inkExtents.js";
6
6
  import { findNodeShape } from "./NodeShape.js";
7
- import { meanScale } from "@weasel-js/geom";
7
+ import { boundsOfPath, meanScale } from "@weasel-js/geom";
8
8
 
9
9
  //#region src/canvas/drawnBounds.ts
10
10
  /**
@@ -18,9 +18,12 @@ import { meanScale } from "@weasel-js/geom";
18
18
  * drawn in: a turned node's box comes back turned with it. A node with no
19
19
  * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or
20
20
  * `includeStroke: false` answers `framedBounds` unchanged. A container's box
21
- * reaches as far as its members' boxes do.
21
+ * reaches as far as its members' boxes do. A node showing a derived path is
22
+ * boxed around that path, and its placeholder pose plays no part.
22
23
  */
23
24
  function drawnBoundsOf(scene, id, pose, opts = {}) {
25
+ const derived = scene.derivedPathOf?.(id) ?? null;
26
+ if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);
24
27
  return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));
25
28
  }
26
29
  /** How far `id`'s ink at `pose` reaches past its pose box on each side, in
@@ -41,6 +44,29 @@ function inkExtentsOf(scene, id, pose, opts = {}) {
41
44
  const drawn = findShapeDrawnBounds(node, pose, inkCtx(opts.scale, own));
42
45
  return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);
43
46
  }
47
+ /** The box of a node drawn along `path`: the path's own, grown by the stroke
48
+ * its painter reports. */
49
+ function derivedBoundsOf(scene, id, pose, path, opts) {
50
+ const { x, y, width, height } = boundsOfPath(path);
51
+ const own = {
52
+ x,
53
+ y,
54
+ width,
55
+ height
56
+ };
57
+ const node = scene.get(id);
58
+ if (opts.includeStroke === false || !node) return own;
59
+ const drawn = findShapeDrawnBounds(node, pose, {
60
+ ...inkCtx(opts.scale, {
61
+ x: 0,
62
+ y: 0,
63
+ width: 0,
64
+ height: 0
65
+ }),
66
+ derivedPath: path
67
+ });
68
+ return drawn === null ? own : unionBounds([own, drawn]) ?? own;
69
+ }
44
70
  /** A container's extents: its members' drawn boxes, taken into its own frame,
45
71
  * past its own box. */
46
72
  function membersExtents(scene, members, own, opts) {
@@ -1 +1 @@
1
- {"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { meanScale, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A container's box\n * reaches as far as its members' boxes do.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = node.kind === 'container' ? scene.childrenOf?.(id) ?? [] : [];\n if (members.length > 0) return membersExtents(scene, members, own, opts);\n const drawn = findShapeDrawnBounds(node, pose, inkCtx(opts.scale, own));\n return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);\n}\n\n/** A container's extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds, unionBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { boundsOfPath, meanScale, type Path, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. `derivedPathOf` absent: every node is measured by its pose,\n * a path-derived one included — `drawnBoundsScene` supplies it for a `Scene`. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n /** The path a node shows in place of its pose's, or `null` for one that shows its own. */\n derivedPathOf?(id: NodeId): Path | null;\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A container's box\n * reaches as far as its members' boxes do. A node showing a derived path is\n * boxed around that path, and its placeholder pose plays no part.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n const derived = scene.derivedPathOf?.(id) ?? null;\n if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = node.kind === 'container' ? scene.childrenOf?.(id) ?? [] : [];\n if (members.length > 0) return membersExtents(scene, members, own, opts);\n const drawn = findShapeDrawnBounds(node, pose, inkCtx(opts.scale, own));\n return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);\n}\n\n/** The box of a node drawn along `path`: the path's own, grown by the stroke\n * its painter reports. */\nfunction derivedBoundsOf(scene: DrawnBoundsScene, id: NodeId, pose: unknown, path: Path, opts: DrawnBoundsOptions): Bounds {\n const { x, y, width, height } = boundsOfPath(path);\n const own = { x, y, width, height };\n const node = scene.get(id);\n if (opts.includeStroke === false || !node) return own;\n const ctx = { ...inkCtx(opts.scale, { x: 0, y: 0, width: 0, height: 0 }), derivedPath: path };\n const drawn = findShapeDrawnBounds(node, pose, ctx);\n return drawn === null ? own : unionBounds([own, drawn]) ?? own;\n}\n\n/** A container's extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { derivedPartsOf } from "../../core/scene/derivedParts.js";
2
2
  import { DEFAULT_SHAPE_FILL } from "../../util/paint.js";
3
3
  import { derivesPath } from "./memo.js";
4
4
  import { inkReach, resolveNodeFill, resolveNodeStroke, withAnimatedVertexColors } from "./nodePaint.js";
5
+ import { strokedBounds } from "./strokedBounds.js";
5
6
  import { commandFill, commandStroke } from "@weasel-js/renderer";
6
7
  import { boundsOfPath } from "@weasel-js/geom";
7
8
 
@@ -18,6 +19,12 @@ function pathCommand(path, declared, strokeSpec, vertexColors) {
18
19
  ...stroke ? { stroke } : {}
19
20
  };
20
21
  }
22
+ /** The stroked box of the path on `ctx`, or `null` with no path or no stroke. */
23
+ function derivedDrawnBounds(node, ctx) {
24
+ const path = ctx?.derivedPath;
25
+ const stroke = resolveNodeStroke(node.data?.stroke);
26
+ return path == null || stroke === null ? null : strokedBounds(path, stroke, ctx?.scale ?? 1, ctx?.screen);
27
+ }
21
28
  /** One part's command: its own paint where it names one, else the node's. */
22
29
  function partCommand(part, d) {
23
30
  const strokeSpec = resolveNodeStroke(part.stroke === void 0 ? d?.stroke : part.stroke);
@@ -59,6 +66,7 @@ var DERIVED_PAINTER = {
59
66
  return withAnimatedVertexColors([pathCommand(path, declared, strokeSpec, d?.vertexColors)], ctx);
60
67
  },
61
68
  silhouette: (_node, _pose, ctx) => ctx?.derivedPath ?? null,
69
+ drawnBounds: (node, _pose, ctx) => derivedDrawnBounds(node, ctx),
62
70
  ink: (node, _pose, ctx) => {
63
71
  const d = node.data;
64
72
  const stroke = resolveNodeStroke(d?.stroke);
@@ -70,5 +78,5 @@ var DERIVED_PAINTER = {
70
78
  };
71
79
 
72
80
  //#endregion
73
- export { DERIVED_PAINTER };
81
+ export { DERIVED_PAINTER, derivedDrawnBounds };
74
82
  //# sourceMappingURL=derivedPainter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"derivedPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/derivedPainter.ts"],"sourcesContent":["import { boundsOfPath, type Path } from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DEFAULT_SHAPE_FILL } from '../../util/paint';\nimport { derivedPartsOf, type DerivedPart } from 'core/scene/derivedParts';\nimport { derivesPath } from './memo';\nimport {\n inkReach, withAnimatedVertexColors, resolveNodeFill, resolveNodeStroke, type PaintedFill, type PaintedStroke,\n} from './nodePaint';\n\ntype PaintData = { fill?: Fill | null; stroke?: Stroke | null; vertexColors?: number[] };\n\nfunction pathCommand(\n path: Path,\n declared: PaintedFill | null,\n strokeSpec: PaintedStroke | null,\n vertexColors: number[] | undefined,\n): DrawCommand {\n const box = boundsOfPath(path);\n const fill = declared && commandFill(declared, box);\n const stroke = strokeSpec && commandStroke(strokeSpec, box);\n return {\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(fill && vertexColors ? { vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n}\n\n/** One part's command: its own paint where it names one, else the node's. */\nfunction partCommand(part: DerivedPart, d: PaintData | null): DrawCommand {\n const strokeSpec = resolveNodeStroke(part.stroke === undefined ? d?.stroke : part.stroke);\n const declared = resolveNodeFill(\n part.fill === undefined ? d?.fill : part.fill,\n strokeSpec === null ? DEFAULT_SHAPE_FILL : null,\n );\n return pathCommand(part.path, declared, strokeSpec, undefined);\n}\n\n/**\n * Built-in painter for nodes whose geometry is computed from other nodes'\n * poses. The path itself arrives on the paint context — see\n * {@link NodePaintCtx.derivedPath}.\n *\n * Fill and stroke resolve as `kit:path`'s do, but against the **derived path's\n * own bounds**, not the pose. `kit:path` projects its path into the pose frame\n * first, so there the two boxes are the same one; here the path is already\n * absolute and the pose is typically a zero-sized placeholder, which would\n * collapse a box-relative gradient onto a point.\n *\n * Not memoized on {@link PAINT_SLOT} on purpose: that key is `(node, pose,\n * data)` and cannot see `ctx.derivedPath`, so it would serve one caller's\n * commands to a caller that passed a different path.\n *\n * Rotation still comes from the pose — `wrapNodeOutput` turns the output about\n * the pose AABB's center, which for a zero-sized pose is its origin.\n *\n * A path made by `pathFromParts` paints one command per part, each with the\n * part's own fill and stroke where it names them.\n */\nexport const DERIVED_PAINTER: NodeShapeEntry = {\n id: 'kit:derived',\n // `dependsOn` alone is not enough: the two fields are independent optionals,\n // and `kit:add` restores a node with its `dependsOn` intact when its\n // `derivePath` key is missing from the registry. Claiming that node would paint\n // nothing where the authored appearance is what the warning promises.\n matches: derivesPath,\n paint: (node, _pose, ctx) => {\n const path = ctx?.derivedPath;\n if (path == null) return [];\n const d = node.data as PaintData | null;\n const parts = derivedPartsOf(path);\n if (parts !== undefined) return parts.map((part) => partCommand(part, d));\n const strokeSpec = resolveNodeStroke(d?.stroke);\n const declared = resolveNodeFill(d?.fill, strokeSpec === null ? DEFAULT_SHAPE_FILL : null);\n return withAnimatedVertexColors([pathCommand(path, declared, strokeSpec, d?.vertexColors)], ctx);\n },\n // The derived path *is* the silhouette, and it is already absolute — the\n // pose is a placeholder, so nothing here can be recovered from it. Without\n // a `ctx` this reports null, which `shapeCoversPoint` reads as \"no\n // opinion\": picking then falls back to that zero-sized pose.\n silhouette: (_node, _pose, ctx) => ctx?.derivedPath ?? null,\n ink: (node, _pose, ctx) => {\n const d = node.data as { fill?: Fill | null; stroke?: Stroke | null } | null;\n const stroke = resolveNodeStroke(d?.stroke);\n return {\n filled: resolveNodeFill(d?.fill, stroke === null ? DEFAULT_SHAPE_FILL : null) !== null,\n ...inkReach(stroke, ctx?.scale),\n };\n },\n};\n"],"mappings":";;;;;;;;AAcA,SAAS,YACP,MACA,UACA,YACA,cACa;CACb,MAAM,MAAM,aAAa,IAAI;CAC7B,MAAM,OAAO,YAAY,YAAY,UAAU,GAAG;CAClD,MAAM,SAAS,cAAc,cAAc,YAAY,GAAG;CAC1D,OAAO;EACL,MAAM;EACN;EACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACvB,GAAI,QAAQ,eAAe,EAAE,aAAa,IAAI,CAAC;EAC/C,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B;AACF;;AAGA,SAAS,YAAY,MAAmB,GAAkC;CACxE,MAAM,aAAa,kBAAkB,KAAK,WAAW,SAAY,GAAG,SAAS,KAAK,MAAM;CACxF,MAAM,WAAW,gBACf,KAAK,SAAS,SAAY,GAAG,OAAO,KAAK,MACzC,eAAe,OAAO,qBAAqB,IAC7C;CACA,OAAO,YAAY,KAAK,MAAM,UAAU,YAAY,MAAS;AAC/D;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAkC;CAC7C,IAAI;CAKJ,SAAS;CACT,QAAQ,MAAM,OAAO,QAAQ;EAC3B,MAAM,OAAO,KAAK;EAClB,IAAI,QAAQ,MAAM,OAAO,CAAC;EAC1B,MAAM,IAAI,KAAK;EACf,MAAM,QAAQ,eAAe,IAAI;EACjC,IAAI,UAAU,QAAW,OAAO,MAAM,KAAK,SAAS,YAAY,MAAM,CAAC,CAAC;EACxE,MAAM,aAAa,kBAAkB,GAAG,MAAM;EAC9C,MAAM,WAAW,gBAAgB,GAAG,MAAM,eAAe,OAAO,qBAAqB,IAAI;EACzF,OAAO,yBAAyB,CAAC,YAAY,MAAM,UAAU,YAAY,GAAG,YAAY,CAAC,GAAG,GAAG;CACjG;CAKA,aAAa,OAAO,OAAO,QAAQ,KAAK,eAAe;CACvD,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,IAAI,KAAK;EACf,MAAM,SAAS,kBAAkB,GAAG,MAAM;EAC1C,OAAO;GACL,QAAQ,gBAAgB,GAAG,MAAM,WAAW,OAAO,qBAAqB,IAAI,MAAM;GAClF,GAAG,SAAS,QAAQ,KAAK,KAAK;EAChC;CACF;AACF"}
1
+ {"version":3,"file":"derivedPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/derivedPainter.ts"],"sourcesContent":["import { boundsOfPath, type Path } from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { NodeInkCtx, NodeShapeEntry } from 'canvas/NodeShape';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DEFAULT_SHAPE_FILL } from '../../util/paint';\nimport { derivedPartsOf, type DerivedPart } from 'core/scene/derivedParts';\nimport { derivesPath } from './memo';\nimport { strokedBounds } from './strokedBounds';\nimport {\n inkReach, withAnimatedVertexColors, resolveNodeFill, resolveNodeStroke, type PaintedFill, type PaintedStroke,\n} from './nodePaint';\n\ntype PaintData = { fill?: Fill | null; stroke?: Stroke | null; vertexColors?: number[] };\n\nfunction pathCommand(\n path: Path,\n declared: PaintedFill | null,\n strokeSpec: PaintedStroke | null,\n vertexColors: number[] | undefined,\n): DrawCommand {\n const box = boundsOfPath(path);\n const fill = declared && commandFill(declared, box);\n const stroke = strokeSpec && commandStroke(strokeSpec, box);\n return {\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(fill && vertexColors ? { vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n}\n\n/** The stroked box of the path on `ctx`, or `null` with no path or no stroke. */\nexport function derivedDrawnBounds(node: { data?: unknown }, ctx: NodeInkCtx | undefined): Bounds | null {\n const path = ctx?.derivedPath;\n const stroke = resolveNodeStroke((node.data as PaintData | null)?.stroke);\n return path == null || stroke === null ? null : strokedBounds(path, stroke, ctx?.scale ?? 1, ctx?.screen);\n}\n\n/** One part's command: its own paint where it names one, else the node's. */\nfunction partCommand(part: DerivedPart, d: PaintData | null): DrawCommand {\n const strokeSpec = resolveNodeStroke(part.stroke === undefined ? d?.stroke : part.stroke);\n const declared = resolveNodeFill(\n part.fill === undefined ? d?.fill : part.fill,\n strokeSpec === null ? DEFAULT_SHAPE_FILL : null,\n );\n return pathCommand(part.path, declared, strokeSpec, undefined);\n}\n\n/**\n * Built-in painter for nodes whose geometry is computed from other nodes'\n * poses. The path itself arrives on the paint context — see\n * {@link NodePaintCtx.derivedPath}.\n *\n * Fill and stroke resolve as `kit:path`'s do, but against the **derived path's\n * own bounds**, not the pose. `kit:path` projects its path into the pose frame\n * first, so there the two boxes are the same one; here the path is already\n * absolute and the pose is typically a zero-sized placeholder, which would\n * collapse a box-relative gradient onto a point.\n *\n * Not memoized on {@link PAINT_SLOT} on purpose: that key is `(node, pose,\n * data)` and cannot see `ctx.derivedPath`, so it would serve one caller's\n * commands to a caller that passed a different path.\n *\n * Rotation still comes from the pose — `wrapNodeOutput` turns the output about\n * the pose AABB's center, which for a zero-sized pose is its origin.\n *\n * A path made by `pathFromParts` paints one command per part, each with the\n * part's own fill and stroke where it names them.\n */\nexport const DERIVED_PAINTER: NodeShapeEntry = {\n id: 'kit:derived',\n // `dependsOn` alone is not enough: the two fields are independent optionals,\n // and `kit:add` restores a node with its `dependsOn` intact when its\n // `derivePath` key is missing from the registry. Claiming that node would paint\n // nothing where the authored appearance is what the warning promises.\n matches: derivesPath,\n paint: (node, _pose, ctx) => {\n const path = ctx?.derivedPath;\n if (path == null) return [];\n const d = node.data as PaintData | null;\n const parts = derivedPartsOf(path);\n if (parts !== undefined) return parts.map((part) => partCommand(part, d));\n const strokeSpec = resolveNodeStroke(d?.stroke);\n const declared = resolveNodeFill(d?.fill, strokeSpec === null ? DEFAULT_SHAPE_FILL : null);\n return withAnimatedVertexColors([pathCommand(path, declared, strokeSpec, d?.vertexColors)], ctx);\n },\n // The derived path *is* the silhouette, and it is already absolute — the\n // pose is a placeholder, so nothing here can be recovered from it. Without\n // a `ctx` this reports null, which `shapeCoversPoint` reads as \"no\n // opinion\": picking then falls back to that zero-sized pose.\n silhouette: (_node, _pose, ctx) => ctx?.derivedPath ?? null,\n drawnBounds: (node, _pose, ctx) => derivedDrawnBounds(node, ctx),\n ink: (node, _pose, ctx) => {\n const d = node.data as { fill?: Fill | null; stroke?: Stroke | null } | null;\n const stroke = resolveNodeStroke(d?.stroke);\n return {\n filled: resolveNodeFill(d?.fill, stroke === null ? DEFAULT_SHAPE_FILL : null) !== null,\n ...inkReach(stroke, ctx?.scale),\n };\n },\n};\n"],"mappings":";;;;;;;;;AAgBA,SAAS,YACP,MACA,UACA,YACA,cACa;CACb,MAAM,MAAM,aAAa,IAAI;CAC7B,MAAM,OAAO,YAAY,YAAY,UAAU,GAAG;CAClD,MAAM,SAAS,cAAc,cAAc,YAAY,GAAG;CAC1D,OAAO;EACL,MAAM;EACN;EACA,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACvB,GAAI,QAAQ,eAAe,EAAE,aAAa,IAAI,CAAC;EAC/C,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC7B;AACF;;AAGA,SAAgB,mBAAmB,MAA0B,KAA4C;CACvG,MAAM,OAAO,KAAK;CAClB,MAAM,SAAS,kBAAmB,KAAK,MAA2B,MAAM;CACxE,OAAO,QAAQ,QAAQ,WAAW,OAAO,OAAO,cAAc,MAAM,QAAQ,KAAK,SAAS,GAAG,KAAK,MAAM;AAC1G;;AAGA,SAAS,YAAY,MAAmB,GAAkC;CACxE,MAAM,aAAa,kBAAkB,KAAK,WAAW,SAAY,GAAG,SAAS,KAAK,MAAM;CACxF,MAAM,WAAW,gBACf,KAAK,SAAS,SAAY,GAAG,OAAO,KAAK,MACzC,eAAe,OAAO,qBAAqB,IAC7C;CACA,OAAO,YAAY,KAAK,MAAM,UAAU,YAAY,MAAS;AAC/D;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAkC;CAC7C,IAAI;CAKJ,SAAS;CACT,QAAQ,MAAM,OAAO,QAAQ;EAC3B,MAAM,OAAO,KAAK;EAClB,IAAI,QAAQ,MAAM,OAAO,CAAC;EAC1B,MAAM,IAAI,KAAK;EACf,MAAM,QAAQ,eAAe,IAAI;EACjC,IAAI,UAAU,QAAW,OAAO,MAAM,KAAK,SAAS,YAAY,MAAM,CAAC,CAAC;EACxE,MAAM,aAAa,kBAAkB,GAAG,MAAM;EAC9C,MAAM,WAAW,gBAAgB,GAAG,MAAM,eAAe,OAAO,qBAAqB,IAAI;EACzF,OAAO,yBAAyB,CAAC,YAAY,MAAM,UAAU,YAAY,GAAG,YAAY,CAAC,GAAG,GAAG;CACjG;CAKA,aAAa,OAAO,OAAO,QAAQ,KAAK,eAAe;CACvD,cAAc,MAAM,OAAO,QAAQ,mBAAmB,MAAM,GAAG;CAC/D,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,IAAI,KAAK;EACf,MAAM,SAAS,kBAAkB,GAAG,MAAM;EAC1C,OAAO;GACL,QAAQ,gBAAgB,GAAG,MAAM,WAAW,OAAO,qBAAqB,IAAI,MAAM;GAClF,GAAG,SAAS,QAAQ,KAAK,KAAK;EAChC;CACF;AACF"}
@@ -1,9 +1,9 @@
1
1
  import { nodeMemo } from "../../core/scene/nodeMemo.js";
2
2
  import { PAINT_SLOT } from "./memo.js";
3
3
  import { inkReach, pathPaintOf, resolveNodeStroke, withAnimatedVertexColors } from "./nodePaint.js";
4
- import { DERIVED_PAINTER } from "./derivedPainter.js";
5
- import { paintsPose, poseBoxBounds } from "./poseBox.js";
6
4
  import { strokedPoseBox } from "./strokedBounds.js";
5
+ import { DERIVED_PAINTER, derivedDrawnBounds } from "./derivedPainter.js";
6
+ import { paintsPose, poseBoxBounds } from "./poseBox.js";
7
7
  import { commandFill, commandStroke } from "@weasel-js/renderer";
8
8
  import { pathInPoseFrame } from "@weasel-js/geom";
9
9
 
@@ -43,6 +43,7 @@ var PATH_PAINTER = {
43
43
  },
44
44
  bounds: (node, pose, ctx) => poseBoxBounds(pose, resolveNodeStroke(node.data.stroke), ctx),
45
45
  drawnBounds: (node, pose, ctx) => {
46
+ if (ctx?.derivedPath != null) return derivedDrawnBounds(node, ctx);
46
47
  const d = node.data;
47
48
  return strokedPoseBox(node, pose, resolveNodeStroke(d.stroke), ctx, (at) => pathInPoseFrame(d.path, at));
48
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pathPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/pathPainter.ts"],"sourcesContent":["import type {\n Path\n} from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { pathInPoseFrame } from '@weasel-js/geom';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DERIVED_PAINTER } from './derivedPainter';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, pathPaintOf, withAnimatedVertexColors, resolveNodeStroke } from './nodePaint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { strokedPoseBox } from './strokedBounds';\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const PATH_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:path',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { path?: Path } | null;\n return d?.path != null;\n },\n // Memoized: a pure function of `(data, pose)`, and `pathInPoseFrame`\n // allocates on every call but the identity (`kind: 'rect'`) branch. See\n // PAINT_SLOT.\n paint: (node, pose, ctx) => ctx?.derivedPath != null\n // A pose override's path stands in for `data.path`, already where it is drawn.\n ? DERIVED_PAINTER.paint(node, pose, ctx)\n : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as {\n path: Path;\n fill?: Fill | null;\n stroke?: Stroke | null;\n vertexColors?: number[];\n };\n const projected = pathInPoseFrame(d.path, pose);\n const { fill: declared, stroke: strokeSpec } = pathPaintOf(d);\n // The pose is baked into `projected` rather than emitted as a transform,\n // so a box-relative gradient has to be baked onto the same box here or\n // it would arrive in the renderer referring to a frame that never exists.\n const fill = declared && commandFill(declared, pose);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n const cmd: DrawCommand = {\n kind: 'path',\n path: projected,\n ...(fill ? { fill } : {}),\n ...(fill && d.vertexColors ? { vertexColors: d.vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n return [cmd];\n }), ctx),\n silhouette: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return ctx.derivedPath;\n const d = node.data as { path: Path };\n return pathInPoseFrame(d.path, pose);\n },\n ink: (node, _pose, ctx) => {\n const d = node.data as {\n fill?: Fill | null;\n stroke?: Stroke | null;\n };\n const { fill, stroke } = pathPaintOf(d);\n return { filled: fill !== null, ...inkReach(stroke ?? null, ctx?.scale) };\n },\n // `pathInPoseFrame` fits the path's own bounds to the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveNodeStroke((node.data as { stroke?: Stroke | null }).stroke), ctx),\n drawnBounds: (node, pose, ctx) => {\n const d = node.data as { path: Path; stroke?: Stroke | null };\n return strokedPoseBox(node, pose, resolveNodeStroke(d.stroke), ctx, (at) => pathInPoseFrame(d.path, at));\n },\n};\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,eAAkD;CAC7D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EAEnC,OADU,KAAK,MACL,QAAQ;CACpB;CAIA,QAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe,OAE5C,gBAAgB,MAAM,MAAM,MAAM,GAAG,IACrC,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAClE,MAAM,IAAI,KAAK;EAMf,MAAM,YAAY,gBAAgB,EAAE,MAAM,IAAI;EAC9C,MAAM,EAAE,MAAM,UAAU,QAAQ,eAAe,YAAY,CAAC;EAI5D,MAAM,OAAO,YAAY,YAAY,UAAU,IAAI;EACnD,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAQ3D,OAAO,CAAC;GANN,MAAM;GACN,MAAM;GACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;GACjE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAErB,CAAG;CACb,CAAC,GAAG,GAAG;CACP,aAAa,MAAM,MAAM,QAAQ;EAC/B,IAAI,KAAK,eAAe,MAAM,OAAO,IAAI;EACzC,MAAM,IAAI,KAAK;EACf,OAAO,gBAAgB,EAAE,MAAM,IAAI;CACrC;CACA,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,IAAI,KAAK;EAIf,MAAM,EAAE,MAAM,WAAW,YAAY,CAAC;EACtC,OAAO;GAAE,QAAQ,SAAS;GAAM,GAAG,SAAS,UAAU,MAAM,KAAK,KAAK;EAAE;CAC1E;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,kBAAmB,KAAK,KAAoC,MAAM,GAAG,GAAG;CAC9F,cAAc,MAAM,MAAM,QAAQ;EAChC,MAAM,IAAI,KAAK;EACf,OAAO,eAAe,MAAM,MAAM,kBAAkB,EAAE,MAAM,GAAG,MAAM,OAAO,gBAAgB,EAAE,MAAM,EAAE,CAAC;CACzG;AACF"}
1
+ {"version":3,"file":"pathPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/pathPainter.ts"],"sourcesContent":["import type {\n Path\n} from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { pathInPoseFrame } from '@weasel-js/geom';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DERIVED_PAINTER, derivedDrawnBounds } from './derivedPainter';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, pathPaintOf, withAnimatedVertexColors, resolveNodeStroke } from './nodePaint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { strokedPoseBox } from './strokedBounds';\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const PATH_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:path',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { path?: Path } | null;\n return d?.path != null;\n },\n // Memoized: a pure function of `(data, pose)`, and `pathInPoseFrame`\n // allocates on every call but the identity (`kind: 'rect'`) branch. See\n // PAINT_SLOT.\n paint: (node, pose, ctx) => ctx?.derivedPath != null\n // A pose override's path stands in for `data.path`, already where it is drawn.\n ? DERIVED_PAINTER.paint(node, pose, ctx)\n : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as {\n path: Path;\n fill?: Fill | null;\n stroke?: Stroke | null;\n vertexColors?: number[];\n };\n const projected = pathInPoseFrame(d.path, pose);\n const { fill: declared, stroke: strokeSpec } = pathPaintOf(d);\n // The pose is baked into `projected` rather than emitted as a transform,\n // so a box-relative gradient has to be baked onto the same box here or\n // it would arrive in the renderer referring to a frame that never exists.\n const fill = declared && commandFill(declared, pose);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n const cmd: DrawCommand = {\n kind: 'path',\n path: projected,\n ...(fill ? { fill } : {}),\n ...(fill && d.vertexColors ? { vertexColors: d.vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n return [cmd];\n }), ctx),\n silhouette: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return ctx.derivedPath;\n const d = node.data as { path: Path };\n return pathInPoseFrame(d.path, pose);\n },\n ink: (node, _pose, ctx) => {\n const d = node.data as {\n fill?: Fill | null;\n stroke?: Stroke | null;\n };\n const { fill, stroke } = pathPaintOf(d);\n return { filled: fill !== null, ...inkReach(stroke ?? null, ctx?.scale) };\n },\n // `pathInPoseFrame` fits the path's own bounds to the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveNodeStroke((node.data as { stroke?: Stroke | null }).stroke), ctx),\n drawnBounds: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return derivedDrawnBounds(node, ctx);\n const d = node.data as { path: Path; stroke?: Stroke | null };\n return strokedPoseBox(node, pose, resolveNodeStroke(d.stroke), ctx, (at) => pathInPoseFrame(d.path, at));\n },\n};\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,eAAkD;CAC7D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EAEnC,OADU,KAAK,MACL,QAAQ;CACpB;CAIA,QAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe,OAE5C,gBAAgB,MAAM,MAAM,MAAM,GAAG,IACrC,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAClE,MAAM,IAAI,KAAK;EAMf,MAAM,YAAY,gBAAgB,EAAE,MAAM,IAAI;EAC9C,MAAM,EAAE,MAAM,UAAU,QAAQ,eAAe,YAAY,CAAC;EAI5D,MAAM,OAAO,YAAY,YAAY,UAAU,IAAI;EACnD,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAQ3D,OAAO,CAAC;GANN,MAAM;GACN,MAAM;GACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;GACjE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAErB,CAAG;CACb,CAAC,GAAG,GAAG;CACP,aAAa,MAAM,MAAM,QAAQ;EAC/B,IAAI,KAAK,eAAe,MAAM,OAAO,IAAI;EACzC,MAAM,IAAI,KAAK;EACf,OAAO,gBAAgB,EAAE,MAAM,IAAI;CACrC;CACA,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,IAAI,KAAK;EAIf,MAAM,EAAE,MAAM,WAAW,YAAY,CAAC;EACtC,OAAO;GAAE,QAAQ,SAAS;GAAM,GAAG,SAAS,UAAU,MAAM,KAAK,KAAK;EAAE;CAC1E;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,kBAAmB,KAAK,KAAoC,MAAM,GAAG,GAAG;CAC9F,cAAc,MAAM,MAAM,QAAQ;EAChC,IAAI,KAAK,eAAe,MAAM,OAAO,mBAAmB,MAAM,GAAG;EACjE,MAAM,IAAI,KAAK;EACf,OAAO,eAAe,MAAM,MAAM,kBAAkB,EAAE,MAAM,GAAG,MAAM,OAAO,gBAAgB,EAAE,MAAM,EAAE,CAAC;CACzG;AACF"}
@@ -2,8 +2,8 @@ import { nodeMemo } from "../../core/scene/nodeMemo.js";
2
2
  import { DEFAULT_SHAPE_FILL } from "../../util/paint.js";
3
3
  import { PAINT_SLOT } from "./memo.js";
4
4
  import { inkReach, resolveNodeFill, resolveNodeStroke, withAnimatedVertexColors } from "./nodePaint.js";
5
- import { paintsPose, poseBoxBounds } from "./poseBox.js";
6
5
  import { outsetPoseBox, strokedPoseBox } from "./strokedBounds.js";
6
+ import { paintsPose, poseBoxBounds } from "./poseBox.js";
7
7
  import { commandFill, commandStroke } from "@weasel-js/renderer";
8
8
  import { ellipsePath, regularPolygonPath, starPath } from "@weasel-js/geom";
9
9
 
@@ -26,6 +26,18 @@ var OVERLAY_ROLE_STROKES = {
26
26
  lock: {
27
27
  paint: { color: "#d9480f" },
28
28
  width: 1.5
29
+ },
30
+ /** Where a dragged item would land. */
31
+ insert: {
32
+ paint: { color: "#1f8fff" },
33
+ width: 3,
34
+ cap: "round"
35
+ },
36
+ /** Where a dragged item is over but cannot land. */
37
+ refused: {
38
+ paint: { color: "#ff5d5d" },
39
+ width: 3,
40
+ cap: "round"
29
41
  }
30
42
  };
31
43
 
@@ -1 +1 @@
1
- {"version":3,"file":"overlayRoleStrokes.js","names":[],"sources":["../../src/canvas/overlayRoleStrokes.ts"],"sourcesContent":["import type { CommandPaint, CommandStroke } from '@weasel-js/paint';\n\n/** How each `polyline` overlay role reads. An action publishes the run and\n * the word; this table is the whole of the paint, for the dispatcher overlay\n * and for any tool previewing the same kind of run between gestures. */\nexport const OVERLAY_ROLE_STROKES: Readonly<Record<string, CommandStroke & { paint: CommandPaint }>> = {\n cut: { paint: { color: '#e23b3b' }, width: 1, dash: [6, 4] },\n connector: { paint: { color: '#7ba7c7' }, width: 2, dash: [4, 4] },\n falloff: { paint: { color: 'rgba(52, 120, 246, 0.28)' }, width: 10, cap: 'round', join: 'round' },\n measure: { paint: { color: '#1f8fff' }, width: 1 },\n lock: { paint: { color: '#d9480f' }, width: 1.5 },\n};\n"],"mappings":";;;;AAKA,IAAa,uBAA0F;CACrG,KAAK;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CAC3D,WAAW;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CACjE,SAAS;EAAE,OAAO,EAAE,OAAO,2BAA2B;EAAG,OAAO;EAAI,KAAK;EAAS,MAAM;CAAQ;CAChG,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAE;CACjD,MAAM;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAI;AAClD"}
1
+ {"version":3,"file":"overlayRoleStrokes.js","names":[],"sources":["../../src/canvas/overlayRoleStrokes.ts"],"sourcesContent":["import type { CommandPaint, CommandStroke } from '@weasel-js/paint';\n\n/** How each `polyline` overlay role reads. An action publishes the run and\n * the word; this table is the whole of the paint, for the dispatcher overlay\n * and for any tool previewing the same kind of run between gestures. */\nexport const OVERLAY_ROLE_STROKES: Readonly<Record<string, CommandStroke & { paint: CommandPaint }>> = {\n cut: { paint: { color: '#e23b3b' }, width: 1, dash: [6, 4] },\n connector: { paint: { color: '#7ba7c7' }, width: 2, dash: [4, 4] },\n falloff: { paint: { color: 'rgba(52, 120, 246, 0.28)' }, width: 10, cap: 'round', join: 'round' },\n measure: { paint: { color: '#1f8fff' }, width: 1 },\n lock: { paint: { color: '#d9480f' }, width: 1.5 },\n /** Where a dragged item would land. */\n insert: { paint: { color: '#1f8fff' }, width: 3, cap: 'round' },\n /** Where a dragged item is over but cannot land. */\n refused: { paint: { color: '#ff5d5d' }, width: 3, cap: 'round' },\n};\n"],"mappings":";;;;AAKA,IAAa,uBAA0F;CACrG,KAAK;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CAC3D,WAAW;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CACjE,SAAS;EAAE,OAAO,EAAE,OAAO,2BAA2B;EAAG,OAAO;EAAI,KAAK;EAAS,MAAM;CAAQ;CAChG,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAE;CACjD,MAAM;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAI;;CAEhD,QAAQ;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,KAAK;CAAQ;;CAE9D,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,KAAK;CAAQ;AACjE"}
@@ -1,7 +1,13 @@
1
+ import { commandFill, commandStroke } from "@weasel-js/renderer";
2
+ import { boundsOfPath } from "@weasel-js/geom";
1
3
  import { createEmitter } from "@weasel-js/registry";
2
4
 
3
5
  //#region src/canvas/pathOverlay.ts
4
6
  /**
7
+ * Paths that are no node, drawn over the scene: a region a tool would take,
8
+ * the result an operation would make before it is applied.
9
+ */
10
+ /**
5
11
  * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for
6
12
  * one a gesture has taken, and `'remove'` for one it would delete.
7
13
  */
@@ -42,17 +48,31 @@ function createPathOverlay({ id, label = "Transient paths", roles }) {
42
48
  label,
43
49
  space: "world",
44
50
  subscribe: changed.subscribe,
45
- draw: () => current.map(({ path, role = "chrome" }) => {
51
+ draw: () => current.flatMap(({ path, role = "chrome", paint }) => {
46
52
  const style = styles[role] ?? styles["chrome"];
47
- return {
53
+ const outline = style.stroke ? { stroke: {
54
+ paint: style.stroke,
55
+ width: { px: 1 }
56
+ } } : {};
57
+ if (!paint) return [{
48
58
  kind: "path",
49
59
  path,
50
60
  ...style.fill ? { fill: { paint: style.fill } } : {},
51
- ...style.stroke ? { stroke: {
52
- paint: style.stroke,
53
- width: { px: 1 }
54
- } } : {}
55
- };
61
+ ...outline
62
+ }];
63
+ const box = boundsOfPath(path);
64
+ const fill = paint.fill && commandFill(paint.fill, box);
65
+ const stroke = paint.stroke && commandStroke(paint.stroke, box);
66
+ return [{
67
+ kind: "path",
68
+ path,
69
+ ...fill ? { fill } : {},
70
+ ...stroke ? { stroke } : {}
71
+ }, ...style.stroke ? [{
72
+ kind: "path",
73
+ path,
74
+ ...outline
75
+ }] : []];
56
76
  })
57
77
  }
58
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"pathOverlay.js","names":[],"sources":["../../src/canvas/pathOverlay.ts"],"sourcesContent":["/**\n * Paths that are no node, drawn over the scene: a region a tool would take,\n * the result an operation would make before it is applied.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { CommandPaint } from '@weasel-js/paint';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { OverlayRole } from '@weasel-js/routing';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { RenderLayer } from 'core/layers/render';\n\n/** One path to draw, in world coordinates, and what it is. */\nexport interface TransientPath {\n path: Path;\n /** Omitted reads as `'chrome'`. */\n role?: OverlayRole;\n}\n\n/** How one role reads. The stroke is one screen pixel wide at any zoom. */\nexport interface PathOverlayStyle {\n fill?: CommandPaint;\n stroke?: CommandPaint;\n}\n\n/**\n * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for\n * one a gesture has taken, and `'remove'` for one it would delete.\n */\nexport const PATH_OVERLAY_ROLES: Readonly<Record<string, PathOverlayStyle>> = {\n chrome: { fill: { color: 'rgba(164, 139, 212, 0.18)' }, stroke: { color: '#a48bd4' } },\n picked: { fill: { color: 'rgba(164, 139, 212, 0.45)' }, stroke: { color: '#7d5fc0' } },\n remove: { fill: { color: 'rgba(226, 59, 59, 0.35)' }, stroke: { color: '#e23b3b' } },\n};\n\n/** The paths an overlay holds, and the layer that draws them. */\nexport interface PathOverlay {\n get(): readonly TransientPath[];\n /** Replace every path. */\n set(paths: readonly TransientPath[]): void;\n clear(): void;\n /** A world-space layer drawing the paths in order, repainting on each change. */\n layer: RenderLayer<unknown>;\n}\n\n/** Options for {@link createPathOverlay}. */\nexport interface PathOverlayOptions {\n /** The layer's id; see `RenderLayer.id`. */\n id: string;\n label?: string;\n /** Styles per role, merged over {@link PATH_OVERLAY_ROLES}. A role with\n * none paints as `'chrome'`. */\n roles?: Readonly<Record<string, PathOverlayStyle>>;\n}\n\n/** A store of transient paths and the layer that draws it; hand the layer to\n * a tool's `overlay` or a canvas's `layers`. */\nexport function createPathOverlay({ id, label = 'Transient paths', roles }: PathOverlayOptions): PathOverlay {\n const styles = { ...PATH_OVERLAY_ROLES, ...roles };\n const changed = createEmitter();\n let current: readonly TransientPath[] = [];\n const set = (paths: readonly TransientPath[]): void => {\n if (paths.length === 0 && current.length === 0) return;\n current = paths;\n changed.emit();\n };\n return {\n get: () => current,\n set,\n clear: () => set([]),\n layer: {\n id,\n label,\n space: 'world',\n subscribe: changed.subscribe,\n draw: () =>\n current.map(({ path, role = 'chrome' }): DrawCommand => {\n const style = styles[role] ?? styles['chrome']!;\n return {\n kind: 'path',\n path,\n ...(style.fill ? { fill: { paint: style.fill } } : {}),\n ...(style.stroke ? { stroke: { paint: style.stroke, width: { px: 1 } } } : {}),\n };\n }),\n },\n };\n}\n"],"mappings":";;;;;;;AA4BA,IAAa,qBAAiE;CAC5E,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,0BAA0B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;AACrF;;;AAwBA,SAAgB,kBAAkB,EAAE,IAAI,QAAQ,mBAAmB,SAA0C;CAC3G,MAAM,SAAS;EAAE,GAAG;EAAoB,GAAG;CAAM;CACjD,MAAM,UAAU,cAAc;CAC9B,IAAI,UAAoC,CAAC;CACzC,MAAM,OAAO,UAA0C;EACrD,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG;EAChD,UAAU;EACV,QAAQ,KAAK;CACf;CACA,OAAO;EACL,WAAW;EACX;EACA,aAAa,IAAI,CAAC,CAAC;EACnB,OAAO;GACL;GACA;GACA,OAAO;GACP,WAAW,QAAQ;GACnB,YACE,QAAQ,KAAK,EAAE,MAAM,OAAO,eAA4B;IACtD,MAAM,QAAQ,OAAO,SAAS,OAAO;IACrC,OAAO;KACL,MAAM;KACN;KACA,GAAI,MAAM,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,KAAK,EAAE,IAAI,CAAC;KACpD,GAAI,MAAM,SAAS,EAAE,QAAQ;MAAE,OAAO,MAAM;MAAQ,OAAO,EAAE,IAAI,EAAE;KAAE,EAAE,IAAI,CAAC;IAC9E;GACF,CAAC;EACL;CACF;AACF"}
1
+ {"version":3,"file":"pathOverlay.js","names":[],"sources":["../../src/canvas/pathOverlay.ts"],"sourcesContent":["/**\n * Paths that are no node, drawn over the scene: a region a tool would take,\n * the result an operation would make before it is applied.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport type { CommandPaint } from '@weasel-js/paint';\nimport { commandFill, commandStroke, type DrawCommand } from '@weasel-js/renderer';\nimport type { OverlayRole } from '@weasel-js/routing';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { PathPaint } from './nodeShapes/nodePaint';\n\n/** One path to draw, in world coordinates, and what it is. */\nexport interface TransientPath {\n path: Path;\n /** Omitted reads as `'chrome'`. */\n role?: OverlayRole;\n /** Paint the path wears, drawn in place of the role's fill and under the\n * role's outline: a result shown as it will look once made. */\n paint?: PathPaint;\n}\n\n/** How one role reads. The stroke is one screen pixel wide at any zoom. */\nexport interface PathOverlayStyle {\n fill?: CommandPaint;\n stroke?: CommandPaint;\n}\n\n/**\n * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for\n * one a gesture has taken, and `'remove'` for one it would delete.\n */\nexport const PATH_OVERLAY_ROLES: Readonly<Record<string, PathOverlayStyle>> = {\n chrome: { fill: { color: 'rgba(164, 139, 212, 0.18)' }, stroke: { color: '#a48bd4' } },\n picked: { fill: { color: 'rgba(164, 139, 212, 0.45)' }, stroke: { color: '#7d5fc0' } },\n remove: { fill: { color: 'rgba(226, 59, 59, 0.35)' }, stroke: { color: '#e23b3b' } },\n};\n\n/** The paths an overlay holds, and the layer that draws them. */\nexport interface PathOverlay {\n get(): readonly TransientPath[];\n /** Replace every path. */\n set(paths: readonly TransientPath[]): void;\n clear(): void;\n /** A world-space layer drawing the paths in order, repainting on each change. */\n layer: RenderLayer<unknown>;\n}\n\n/** Options for {@link createPathOverlay}. */\nexport interface PathOverlayOptions {\n /** The layer's id; see `RenderLayer.id`. */\n id: string;\n label?: string;\n /** Styles per role, merged over {@link PATH_OVERLAY_ROLES}. A role with\n * none paints as `'chrome'`. */\n roles?: Readonly<Record<string, PathOverlayStyle>>;\n}\n\n/** A store of transient paths and the layer that draws it; hand the layer to\n * a tool's `overlay` or a canvas's `layers`. */\nexport function createPathOverlay({ id, label = 'Transient paths', roles }: PathOverlayOptions): PathOverlay {\n const styles = { ...PATH_OVERLAY_ROLES, ...roles };\n const changed = createEmitter();\n let current: readonly TransientPath[] = [];\n const set = (paths: readonly TransientPath[]): void => {\n if (paths.length === 0 && current.length === 0) return;\n current = paths;\n changed.emit();\n };\n return {\n get: () => current,\n set,\n clear: () => set([]),\n layer: {\n id,\n label,\n space: 'world',\n subscribe: changed.subscribe,\n draw: () =>\n current.flatMap(({ path, role = 'chrome', paint }): DrawCommand[] => {\n const style = styles[role] ?? styles['chrome']!;\n const outline = style.stroke ? { stroke: { paint: style.stroke, width: { px: 1 } } } : {};\n if (!paint) return [{ kind: 'path', path, ...(style.fill ? { fill: { paint: style.fill } } : {}), ...outline }];\n const box = boundsOfPath(path);\n const fill = paint.fill && commandFill(paint.fill, box);\n const stroke = paint.stroke && commandStroke(paint.stroke, box);\n return [\n { kind: 'path', path, ...(fill ? { fill } : {}), ...(stroke ? { stroke } : {}) },\n ...(style.stroke ? [{ kind: 'path', path, ...outline } as DrawCommand] : []),\n ];\n }),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,IAAa,qBAAiE;CAC5E,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,0BAA0B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;AACrF;;;AAwBA,SAAgB,kBAAkB,EAAE,IAAI,QAAQ,mBAAmB,SAA0C;CAC3G,MAAM,SAAS;EAAE,GAAG;EAAoB,GAAG;CAAM;CACjD,MAAM,UAAU,cAAc;CAC9B,IAAI,UAAoC,CAAC;CACzC,MAAM,OAAO,UAA0C;EACrD,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG;EAChD,UAAU;EACV,QAAQ,KAAK;CACf;CACA,OAAO;EACL,WAAW;EACX;EACA,aAAa,IAAI,CAAC,CAAC;EACnB,OAAO;GACL;GACA;GACA,OAAO;GACP,WAAW,QAAQ;GACnB,YACE,QAAQ,SAAS,EAAE,MAAM,OAAO,UAAU,YAA2B;IACnE,MAAM,QAAQ,OAAO,SAAS,OAAO;IACrC,MAAM,UAAU,MAAM,SAAS,EAAE,QAAQ;KAAE,OAAO,MAAM;KAAQ,OAAO,EAAE,IAAI,EAAE;IAAE,EAAE,IAAI,CAAC;IACxF,IAAI,CAAC,OAAO,OAAO,CAAC;KAAE,MAAM;KAAQ;KAAM,GAAI,MAAM,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,KAAK,EAAE,IAAI,CAAC;KAAI,GAAG;IAAQ,CAAC;IAC9G,MAAM,MAAM,aAAa,IAAI;IAC7B,MAAM,OAAO,MAAM,QAAQ,YAAY,MAAM,MAAM,GAAG;IACtD,MAAM,SAAS,MAAM,UAAU,cAAc,MAAM,QAAQ,GAAG;IAC9D,OAAO,CACL;KAAE,MAAM;KAAQ;KAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;KAAI,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;IAAG,GAC/E,GAAI,MAAM,SAAS,CAAC;KAAE,MAAM;KAAQ;KAAM,GAAG;IAAQ,CAAgB,IAAI,CAAC,CAC5E;GACF,CAAC;EACL;CACF;AACF"}
@@ -25,6 +25,7 @@ function debugSnapshotArgs(args) {
25
25
  const width = size.width / Math.abs(view.scale.x);
26
26
  const height = size.height / Math.abs(view.scale.y);
27
27
  return {
28
+ annotations: true,
28
29
  ...rest,
29
30
  sourceRect: {
30
31
  x: view.scale.x < 0 ? view.x - width : view.x,
@@ -1 +1 @@
1
- {"version":3,"file":"renderDebugSnapshot.js","names":[],"sources":["../../src/canvas/renderDebugSnapshot.ts"],"sourcesContent":["/**\n * `renderDebugSnapshot` — the canvas as its debug overlay showed it, scene and\n * overlay in one raster, for attaching to a bug report.\n */\nimport { renderSceneToPixels, type RasterImage, type RenderSceneToPixelsArgs } from './renderSceneToPixels';\nimport { buildDebugOverlayCommands } from '../debug/createDebugOverlayLayer';\nimport type { DebugConfig, DebugSnapshot } from '../debug/types';\nimport type { View } from '../core/viewport/view';\nimport { mat3 } from '@weasel-js/geom';\n\n/** The canvas to reproduce — its camera, its size, and what its debug overlay\n * had recorded — plus how to rasterize the scene under it. */\nexport interface RenderDebugSnapshotArgs<TData, TLayer extends string, TPose>\n extends Omit<RenderSceneToPixelsArgs<TData, TLayer, TPose>, 'sourceRect' | 'scale' | 'overlay'> {\n /** The camera the canvas was looking through. */\n view: View;\n /** The canvas's size in CSS pixels. */\n size: { width: number; height: number };\n /** Output pixels per CSS pixel. Default 1; pass `devicePixelRatio` for what\n * the screen showed. */\n pixelRatio?: number;\n /** What the overlay draws — a canvas's `getDebug()?.snapshot()`. */\n debug: DebugSnapshot;\n /** Which overlay features to draw, as on `<Canvas debug>`. The frame panel\n * shows the recorded paint cost but no rate: a still image has no frames. */\n config: DebugConfig;\n}\n\n/**\n * Rasterize the scene through `view` at `size` and paint the debug overlay\n * over it, the way the canvas composed them. Built on `renderSceneToPixels`,\n * so the scene pixels are the screen's pixels at that resolution, and\n * synchronous like it: `await warmRender({ render: debugSnapshotArgs(args) })`\n * first. Encode the result with `rasterToPng` to attach it somewhere.\n */\nexport function renderDebugSnapshot<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RasterImage {\n return renderSceneToPixels(debugSnapshotArgs(args));\n}\n\n/** What {@link renderDebugSnapshot} hands `renderSceneToPixels`: the source\n * rect and scale that reproduce the canvas, with the overlay composed on top. */\nexport function debugSnapshotArgs<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RenderSceneToPixelsArgs<TData, TLayer, TPose> {\n const { view, size, debug, config, pixelRatio = 1, ...rest } = args;\n const overlay = buildDebugOverlayCommands(debug, config, view, size);\n const width = size.width / Math.abs(view.scale.x);\n const height = size.height / Math.abs(view.scale.y);\n return {\n ...rest,\n sourceRect: {\n x: view.scale.x < 0 ? view.x - width : view.x,\n y: view.scale.y < 0 ? view.y - height : view.y,\n width,\n height,\n },\n scale: { x: view.scale.x * pixelRatio, y: view.scale.y * pixelRatio },\n overlay: pixelRatio === 1\n ? overlay\n : [{ kind: 'group', transform: mat3.scaled(mat3.identity(), pixelRatio, pixelRatio), children: overlay }],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmCA,SAAgB,oBACd,MACa;CACb,OAAO,oBAAoB,kBAAkB,IAAI,CAAC;AACpD;;;AAIA,SAAgB,kBACd,MAC+C;CAC/C,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,aAAa,GAAG,GAAG,SAAS;CAC/D,MAAM,UAAU,0BAA0B,OAAO,QAAQ,MAAM,IAAI;CACnE,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC;CAChD,MAAM,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,MAAM,CAAC;CAClD,OAAO;EACL,GAAG;EACH,YAAY;GACV,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,QAAQ,KAAK;GAC5C,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,SAAS,KAAK;GAC7C;GACA;EACF;EACA,OAAO;GAAE,GAAG,KAAK,MAAM,IAAI;GAAY,GAAG,KAAK,MAAM,IAAI;EAAW;EACpE,SAAS,eAAe,IACpB,UACA,CAAC;GAAE,MAAM;GAAS,WAAW,KAAK,OAAO,KAAK,SAAS,GAAG,YAAY,UAAU;GAAG,UAAU;EAAQ,CAAC;CAC5G;AACF"}
1
+ {"version":3,"file":"renderDebugSnapshot.js","names":[],"sources":["../../src/canvas/renderDebugSnapshot.ts"],"sourcesContent":["/**\n * `renderDebugSnapshot` — the canvas as its debug overlay showed it, scene and\n * overlay in one raster, for attaching to a bug report.\n */\nimport { renderSceneToPixels, type RasterImage, type RenderSceneToPixelsArgs } from './renderSceneToPixels';\nimport { buildDebugOverlayCommands } from '../debug/createDebugOverlayLayer';\nimport type { DebugConfig, DebugSnapshot } from '../debug/types';\nimport type { View } from '../core/viewport/view';\nimport { mat3 } from '@weasel-js/geom';\n\n/** The canvas to reproduce — its camera, its size, and what its debug overlay\n * had recorded — plus how to rasterize the scene under it. */\nexport interface RenderDebugSnapshotArgs<TData, TLayer extends string, TPose>\n extends Omit<RenderSceneToPixelsArgs<TData, TLayer, TPose>, 'sourceRect' | 'scale' | 'overlay'> {\n /** The camera the canvas was looking through. */\n view: View;\n /** The canvas's size in CSS pixels. */\n size: { width: number; height: number };\n /** Output pixels per CSS pixel. Default 1; pass `devicePixelRatio` for what\n * the screen showed. */\n pixelRatio?: number;\n /** What the overlay draws — a canvas's `getDebug()?.snapshot()`. */\n debug: DebugSnapshot;\n /** Which overlay features to draw, as on `<Canvas debug>`. The frame panel\n * shows the recorded paint cost but no rate: a still image has no frames. */\n config: DebugConfig;\n /** Draw the scene's annotation layers. Default `true`, as the canvas did. */\n annotations?: boolean;\n}\n\n/**\n * Rasterize the scene through `view` at `size` and paint the debug overlay\n * over it, the way the canvas composed them. Built on `renderSceneToPixels`,\n * so the scene pixels are the screen's pixels at that resolution, and\n * synchronous like it: `await warmRender({ render: debugSnapshotArgs(args) })`\n * first. Encode the result with `rasterToPng` to attach it somewhere.\n */\nexport function renderDebugSnapshot<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RasterImage {\n return renderSceneToPixels(debugSnapshotArgs(args));\n}\n\n/** What {@link renderDebugSnapshot} hands `renderSceneToPixels`: the source\n * rect and scale that reproduce the canvas, with the overlay composed on top. */\nexport function debugSnapshotArgs<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RenderSceneToPixelsArgs<TData, TLayer, TPose> {\n const { view, size, debug, config, pixelRatio = 1, ...rest } = args;\n const overlay = buildDebugOverlayCommands(debug, config, view, size);\n const width = size.width / Math.abs(view.scale.x);\n const height = size.height / Math.abs(view.scale.y);\n return {\n annotations: true,\n ...rest,\n sourceRect: {\n x: view.scale.x < 0 ? view.x - width : view.x,\n y: view.scale.y < 0 ? view.y - height : view.y,\n width,\n height,\n },\n scale: { x: view.scale.x * pixelRatio, y: view.scale.y * pixelRatio },\n overlay: pixelRatio === 1\n ? overlay\n : [{ kind: 'group', transform: mat3.scaled(mat3.identity(), pixelRatio, pixelRatio), children: overlay }],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqCA,SAAgB,oBACd,MACa;CACb,OAAO,oBAAoB,kBAAkB,IAAI,CAAC;AACpD;;;AAIA,SAAgB,kBACd,MAC+C;CAC/C,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,aAAa,GAAG,GAAG,SAAS;CAC/D,MAAM,UAAU,0BAA0B,OAAO,QAAQ,MAAM,IAAI;CACnE,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC;CAChD,MAAM,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,MAAM,CAAC;CAClD,OAAO;EACL,aAAa;EACb,GAAG;EACH,YAAY;GACV,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,QAAQ,KAAK;GAC5C,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,SAAS,KAAK;GAC7C;GACA;EACF;EACA,OAAO;GAAE,GAAG,KAAK,MAAM,IAAI;GAAY,GAAG,KAAK,MAAM,IAAI;EAAW;EACpE,SAAS,eAAe,IACpB,UACA,CAAC;GAAE,MAAM;GAAS,WAAW,KAAK,OAAO,KAAK,SAAS,GAAG,YAAY,UAAU;GAAG,UAAU;EAAQ,CAAC;CAC5G;AACF"}
@@ -1,3 +1,6 @@
1
+ import { sceneLayerKey } from "./sceneLayerPaint.js";
2
+ import { CANVAS_CONTEXT_ATTRIBUTES } from "./contextAttributes.js";
3
+ import { annotationLayerIds } from "../core/scene/annotation.js";
1
4
  import { defaultDrawOne } from "./defaultDrawOne.js";
2
5
  import { buildSceneViewCommands } from "./sceneViewRender.js";
3
6
  import { renderNeeds } from "./renderNeeds.js";
@@ -70,7 +73,8 @@ function planPixelRender(args) {
70
73
  color: args.background
71
74
  } }
72
75
  });
73
- commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor, void 0, args.colorOverrides));
76
+ const layers = args.annotations ? void 0 : { layerVisibility: Object.fromEntries([...annotationLayerIds(args.scene.layers)].map((id) => [sceneLayerKey(id), false])) };
77
+ commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor, layers, args.colorOverrides));
74
78
  if (args.overlay) commands.push(...args.overlay);
75
79
  return {
76
80
  width,
@@ -111,10 +115,7 @@ function createRasterSession(options = {}) {
111
115
  canvas = (options.createCanvas ?? defaultCreateCanvas)(width, height);
112
116
  canvas.width = width;
113
117
  canvas.height = height;
114
- gl = canvas.getContext("webgl2", {
115
- preserveDrawingBuffer: true,
116
- stencil: true
117
- }) ?? void 0;
118
+ gl = canvas.getContext("webgl2", CANVAS_CONTEXT_ATTRIBUTES) ?? void 0;
118
119
  } else {
119
120
  if (canvas.width < width) canvas.width = width;
120
121
  if (canvas.height < height) canvas.height = height;