@things-factory/figure-ui 10.1.75 → 10.1.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/figure-assistant.ts +3 -3
- package/client/graphql/{v3-store.ts → store.ts} +6 -6
- package/client/modeller/{v3-ai-proposal.ts → ai-proposal.ts} +8 -8
- package/client/modeller/{v3-align.ts → align.ts} +14 -14
- package/client/modeller/{v3-canvas.ts → canvas.ts} +13 -13
- package/client/modeller/{v3-clips.ts → clips.ts} +5 -5
- package/client/modeller/{v3-controls.ts → controls.ts} +2 -2
- package/client/modeller/{v3-editor.ts → editor.ts} +117 -117
- package/client/modeller/{figure-v3-capability.ts → figure-capability-panel.ts} +7 -7
- package/client/modeller/{figure-v3-clips.ts → figure-clips.ts} +10 -10
- package/client/modeller/{figure-v3-controls.ts → figure-controls.ts} +9 -9
- package/client/modeller/{v3-figure-facts.ts → figure-facts.ts} +11 -11
- package/client/modeller/{figure-v3-first-save.ts → figure-first-save.ts} +3 -3
- package/client/modeller/figure-history-panel.ts +1 -1
- package/client/modeller/{figure-v3-links.ts → figure-links.ts} +35 -35
- package/client/modeller/{figure-v3-material.ts → figure-material-panel.ts} +20 -20
- package/client/modeller/{figure-v3-parts.ts → figure-parts.ts} +15 -15
- package/client/modeller/{figure-v3-preview.ts → figure-preview.ts} +14 -14
- package/client/modeller/{figure-v3-properties.ts → figure-properties.ts} +25 -25
- package/client/modeller/{figure-v3-recipe-catalog.ts → figure-recipe-catalog.ts} +10 -10
- package/client/modeller/{figure-v3-verdict.ts → figure-verdict.ts} +24 -24
- package/client/modeller/gate-findings.ts +2 -2
- package/client/modeller/{v3-gizmo.ts → gizmo.ts} +11 -11
- package/client/modeller/{v3-joint-drag.ts → joint-drag.ts} +11 -11
- package/client/modeller/{v3-joint-fan.ts → joint-fan.ts} +13 -13
- package/client/modeller/{v3-links.ts → links.ts} +59 -59
- package/client/modeller/{v3-live.ts → live.ts} +9 -9
- package/client/modeller/{v3-maturity.ts → maturity.ts} +65 -65
- package/client/modeller/modeller-route.ts +1 -1
- package/client/modeller/{v3-occupancy.ts → occupancy.ts} +6 -6
- package/client/modeller/{v3-palette.ts → palette.ts} +3 -3
- package/client/modeller/{v3-preview-model.ts → preview-model.ts} +8 -8
- package/client/modeller/{v3-product-session.ts → product-session.ts} +25 -25
- package/client/modeller/{v3-recipe-source.ts → recipe-source.ts} +2 -2
- package/client/modeller/{v3-recipes.ts → recipes.ts} +9 -9
- package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
- package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
- package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
- package/client/route.ts +1 -5
- package/dist-client/figure-assistant.js +3 -3
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/store.d.ts +3 -0
- package/dist-client/graphql/{v3-store.js → store.js} +6 -6
- package/dist-client/graphql/store.js.map +1 -0
- package/dist-client/modeller/{v3-ai-proposal.d.ts → ai-proposal.d.ts} +7 -7
- package/dist-client/modeller/{v3-ai-proposal.js → ai-proposal.js} +1 -1
- package/dist-client/modeller/ai-proposal.js.map +1 -0
- package/dist-client/modeller/{v3-align.d.ts → align.d.ts} +8 -8
- package/dist-client/modeller/{v3-align.js → align.js} +6 -6
- package/dist-client/modeller/align.js.map +1 -0
- package/dist-client/modeller/{v3-canvas.d.ts → canvas.d.ts} +7 -7
- package/dist-client/modeller/{v3-canvas.js → canvas.js} +20 -20
- package/dist-client/modeller/canvas.js.map +1 -0
- package/dist-client/modeller/{v3-clips.d.ts → clips.d.ts} +4 -4
- package/dist-client/modeller/{v3-clips.js → clips.js} +1 -1
- package/dist-client/modeller/clips.js.map +1 -0
- package/dist-client/modeller/{v3-controls.d.ts → controls.d.ts} +2 -2
- package/dist-client/modeller/{v3-controls.js → controls.js} +1 -1
- package/dist-client/modeller/controls.js.map +1 -0
- package/dist-client/modeller/{v3-editor.d.ts → editor.d.ts} +19 -19
- package/dist-client/modeller/{v3-editor.js → editor.js} +103 -103
- package/dist-client/modeller/editor.js.map +1 -0
- package/dist-client/modeller/{v3-fields.js → fields.js} +1 -1
- package/dist-client/modeller/fields.js.map +1 -0
- package/dist-client/modeller/{figure-v3-capability.d.ts → figure-capability-panel.d.ts} +4 -4
- package/dist-client/modeller/{figure-v3-capability.js → figure-capability-panel.js} +12 -12
- package/dist-client/modeller/figure-capability-panel.js.map +1 -0
- package/dist-client/modeller/{figure-v3-clips.d.ts → figure-clips.d.ts} +4 -4
- package/dist-client/modeller/{figure-v3-clips.js → figure-clips.js} +16 -16
- package/dist-client/modeller/figure-clips.js.map +1 -0
- package/dist-client/modeller/{figure-v3-controls.d.ts → figure-controls.d.ts} +5 -5
- package/dist-client/modeller/{figure-v3-controls.js → figure-controls.js} +16 -16
- package/dist-client/modeller/figure-controls.js.map +1 -0
- package/dist-client/modeller/{v3-figure-facts.d.ts → figure-facts.d.ts} +5 -5
- package/dist-client/modeller/{v3-figure-facts.js → figure-facts.js} +5 -5
- package/dist-client/modeller/figure-facts.js.map +1 -0
- package/dist-client/modeller/{figure-v3-first-save.d.ts → figure-first-save.d.ts} +1 -1
- package/dist-client/modeller/{figure-v3-first-save.js → figure-first-save.js} +11 -11
- package/dist-client/modeller/figure-first-save.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +1 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/{figure-v3-links.d.ts → figure-links.d.ts} +5 -5
- package/dist-client/modeller/{figure-v3-links.js → figure-links.js} +24 -24
- package/dist-client/modeller/figure-links.js.map +1 -0
- package/dist-client/modeller/{figure-v3-material.d.ts → figure-material-panel.d.ts} +9 -9
- package/dist-client/modeller/{figure-v3-material.js → figure-material-panel.js} +19 -19
- package/dist-client/modeller/figure-material-panel.js.map +1 -0
- package/dist-client/modeller/{figure-v3-parts.d.ts → figure-parts.d.ts} +12 -12
- package/dist-client/modeller/{figure-v3-parts.js → figure-parts.js} +14 -14
- package/dist-client/modeller/figure-parts.js.map +1 -0
- package/dist-client/modeller/{figure-v3-preview.d.ts → figure-preview.d.ts} +4 -4
- package/dist-client/modeller/{figure-v3-preview.js → figure-preview.js} +21 -21
- package/dist-client/modeller/figure-preview.js.map +1 -0
- package/dist-client/modeller/{figure-v3-properties.d.ts → figure-properties.d.ts} +8 -8
- package/dist-client/modeller/{figure-v3-properties.js → figure-properties.js} +25 -25
- package/dist-client/modeller/figure-properties.js.map +1 -0
- package/dist-client/modeller/{figure-v3-recipe-catalog.d.ts → figure-recipe-catalog.d.ts} +4 -4
- package/dist-client/modeller/{figure-v3-recipe-catalog.js → figure-recipe-catalog.js} +21 -21
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/{figure-v3-verdict.d.ts → figure-verdict.d.ts} +2 -2
- package/dist-client/modeller/{figure-v3-verdict.js → figure-verdict.js} +26 -26
- package/dist-client/modeller/figure-verdict.js.map +1 -0
- package/dist-client/modeller/{v3-first-save.js → first-save.js} +1 -1
- package/dist-client/modeller/first-save.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +2 -2
- package/dist-client/modeller/gate-findings.js +2 -2
- package/dist-client/modeller/gate-findings.js.map +1 -1
- package/dist-client/modeller/{v3-gizmo.d.ts → gizmo.d.ts} +5 -5
- package/dist-client/modeller/{v3-gizmo.js → gizmo.js} +3 -3
- package/dist-client/modeller/gizmo.js.map +1 -0
- package/dist-client/modeller/{v3-help.js → help.js} +1 -1
- package/dist-client/modeller/help.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +19 -0
- package/dist-client/modeller/{v3-joint-drag.js → joint-drag.js} +4 -4
- package/dist-client/modeller/joint-drag.js.map +1 -0
- package/dist-client/modeller/{v3-joint-fan.d.ts → joint-fan.d.ts} +3 -3
- package/dist-client/modeller/{v3-joint-fan.js → joint-fan.js} +6 -6
- package/dist-client/modeller/joint-fan.js.map +1 -0
- package/dist-client/modeller/{v3-keys.js → keys.js} +1 -1
- package/dist-client/modeller/keys.js.map +1 -0
- package/dist-client/modeller/{v3-links.d.ts → links.d.ts} +48 -48
- package/dist-client/modeller/{v3-links.js → links.js} +15 -15
- package/dist-client/modeller/links.js.map +1 -0
- package/dist-client/modeller/{v3-live.d.ts → live.d.ts} +6 -6
- package/dist-client/modeller/{v3-live.js → live.js} +5 -5
- package/dist-client/modeller/live.js.map +1 -0
- package/dist-client/modeller/{v3-maturity.d.ts → maturity.d.ts} +26 -26
- package/dist-client/modeller/{v3-maturity.js → maturity.js} +28 -28
- package/dist-client/modeller/maturity.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +1 -1
- package/dist-client/modeller/modeller-route.js +1 -1
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/{v3-occupancy.d.ts → occupancy.d.ts} +4 -4
- package/dist-client/modeller/{v3-occupancy.js → occupancy.js} +6 -6
- package/dist-client/modeller/occupancy.js.map +1 -0
- package/dist-client/modeller/palette.d.ts +7 -0
- package/dist-client/modeller/{v3-palette.js → palette.js} +1 -1
- package/dist-client/modeller/palette.js.map +1 -0
- package/dist-client/modeller/{v3-preview-model.d.ts → preview-model.d.ts} +3 -3
- package/dist-client/modeller/{v3-preview-model.js → preview-model.js} +6 -6
- package/dist-client/modeller/preview-model.js.map +1 -0
- package/dist-client/modeller/{v3-product-session.d.ts → product-session.d.ts} +56 -56
- package/dist-client/modeller/{v3-product-session.js → product-session.js} +7 -7
- package/dist-client/modeller/product-session.js.map +1 -0
- package/dist-client/modeller/{v3-recipe-source.d.ts → recipe-source.d.ts} +2 -2
- package/dist-client/modeller/{v3-recipe-source.js → recipe-source.js} +3 -3
- package/dist-client/modeller/recipe-source.js.map +1 -0
- package/dist-client/modeller/{v3-recipes.d.ts → recipes.d.ts} +7 -7
- package/dist-client/modeller/{v3-recipes.js → recipes.js} +4 -4
- package/dist-client/modeller/recipes.js.map +1 -0
- package/dist-client/modeller/{v3-refusals.js → refusals.js} +1 -1
- package/dist-client/modeller/refusals.js.map +1 -0
- package/dist-client/modeller/{v3-renderer.d.ts → renderer.d.ts} +6 -6
- package/dist-client/modeller/{v3-renderer.js → renderer.js} +13 -13
- package/dist-client/modeller/renderer.js.map +1 -0
- package/dist-client/modeller/{v3-stage-model.d.ts → stage-model.d.ts} +7 -7
- package/dist-client/modeller/{v3-stage-model.js → stage-model.js} +5 -5
- package/dist-client/modeller/stage-model.js.map +1 -0
- package/dist-client/modeller/thumbnail.d.ts +3 -0
- package/dist-client/modeller/{v3-thumbnail.js → thumbnail.js} +6 -6
- package/dist-client/modeller/thumbnail.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/{figure-v3-modeller-page.d.ts → figure-modeller-page.d.ts} +10 -10
- package/dist-client/pages/{figure-v3-modeller-page.js → figure-modeller-page.js} +42 -42
- package/dist-client/pages/figure-modeller-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +1 -5
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/docs/product-cutover.md +25 -0
- package/package.json +5 -5
- package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
- package/test/{v3-align.test.ts → align.test.ts} +8 -8
- package/test/browser/modeller-page.js +30 -30
- package/test/browser/modeller.check.mjs +14 -14
- package/test/browser/serve.mjs +1 -1
- package/test/browser/thumbnail-page.js +6 -6
- package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
- package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
- package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
- package/test/figure-assistant-surface.test.ts +3 -3
- package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
- package/test/fixtures/joint-column.json +2 -2
- package/test/fixtures/joint-roller.json +2 -2
- package/test/fixtures/robot-arm-6dof.json +2 -2
- package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
- package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
- package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
- package/test/joints.ts +6 -6
- package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
- package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
- package/test/{v3-links.test.ts → links.test.ts} +201 -201
- package/test/{v3-live.test.ts → live.test.ts} +18 -18
- package/test/{v3-material.test.ts → material.test.ts} +12 -12
- package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
- package/test/modeller-route.test.ts +5 -6
- package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
- package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +5 -5
- package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
- package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
- package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
- package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
- package/test/{v3-recipes.test.ts → recipes.test.ts} +9 -9
- package/test/{v3-refusals.test.ts → refusals.test.ts} +43 -43
- package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
- package/test/renderer.test.ts +24 -0
- package/test/resolve-ts-siblings.mjs +1 -1
- package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
- package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
- package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
- package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
- package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
- package/test/{v3-view.test.ts → view.test.ts} +2 -2
- package/things-factory.config.js +2 -3
- package/translations/en.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -2
- package/dist-client/graphql/v3-store.d.ts +0 -3
- package/dist-client/graphql/v3-store.js.map +0 -1
- package/dist-client/modeller/figure-v3-capability.js.map +0 -1
- package/dist-client/modeller/figure-v3-clips.js.map +0 -1
- package/dist-client/modeller/figure-v3-controls.js.map +0 -1
- package/dist-client/modeller/figure-v3-first-save.js.map +0 -1
- package/dist-client/modeller/figure-v3-links.js.map +0 -1
- package/dist-client/modeller/figure-v3-material.js.map +0 -1
- package/dist-client/modeller/figure-v3-parts.js.map +0 -1
- package/dist-client/modeller/figure-v3-preview.js.map +0 -1
- package/dist-client/modeller/figure-v3-properties.js.map +0 -1
- package/dist-client/modeller/figure-v3-recipe-catalog.js.map +0 -1
- package/dist-client/modeller/figure-v3-verdict.js.map +0 -1
- package/dist-client/modeller/v3-ai-proposal.js.map +0 -1
- package/dist-client/modeller/v3-align.js.map +0 -1
- package/dist-client/modeller/v3-canvas.js.map +0 -1
- package/dist-client/modeller/v3-clips.js.map +0 -1
- package/dist-client/modeller/v3-controls.js.map +0 -1
- package/dist-client/modeller/v3-editor.js.map +0 -1
- package/dist-client/modeller/v3-fields.js.map +0 -1
- package/dist-client/modeller/v3-figure-facts.js.map +0 -1
- package/dist-client/modeller/v3-first-save.js.map +0 -1
- package/dist-client/modeller/v3-gizmo.js.map +0 -1
- package/dist-client/modeller/v3-help.js.map +0 -1
- package/dist-client/modeller/v3-joint-drag.d.ts +0 -19
- package/dist-client/modeller/v3-joint-drag.js.map +0 -1
- package/dist-client/modeller/v3-joint-fan.js.map +0 -1
- package/dist-client/modeller/v3-keys.js.map +0 -1
- package/dist-client/modeller/v3-links.js.map +0 -1
- package/dist-client/modeller/v3-live.js.map +0 -1
- package/dist-client/modeller/v3-maturity.js.map +0 -1
- package/dist-client/modeller/v3-occupancy.js.map +0 -1
- package/dist-client/modeller/v3-palette.d.ts +0 -7
- package/dist-client/modeller/v3-palette.js.map +0 -1
- package/dist-client/modeller/v3-preview-model.js.map +0 -1
- package/dist-client/modeller/v3-product-session.js.map +0 -1
- package/dist-client/modeller/v3-recipe-source.js.map +0 -1
- package/dist-client/modeller/v3-recipes.js.map +0 -1
- package/dist-client/modeller/v3-refusals.js.map +0 -1
- package/dist-client/modeller/v3-renderer.js.map +0 -1
- package/dist-client/modeller/v3-stage-model.js.map +0 -1
- package/dist-client/modeller/v3-thumbnail.d.ts +0 -3
- package/dist-client/modeller/v3-thumbnail.js.map +0 -1
- package/dist-client/pages/figure-v3-modeller-page.js.map +0 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +0 -1
- package/dist-client/tsconfig.fui.tsbuildinfo +0 -1
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +0 -1
- package/docs/v3-product-cutover.md +0 -25
- package/test/v3-renderer.test.ts +0 -24
- /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
- /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
- /package/client/modeller/{v3-help.ts → help.ts} +0 -0
- /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
- /package/client/modeller/{v3-refusals.ts → refusals.ts} +0 -0
- /package/dist-client/modeller/{v3-fields.d.ts → fields.d.ts} +0 -0
- /package/dist-client/modeller/{v3-first-save.d.ts → first-save.d.ts} +0 -0
- /package/dist-client/modeller/{v3-help.d.ts → help.d.ts} +0 -0
- /package/dist-client/modeller/{v3-keys.d.ts → keys.d.ts} +0 -0
- /package/dist-client/modeller/{v3-refusals.d.ts → refusals.d.ts} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as THREE from 'three'
|
|
2
|
-
import {
|
|
2
|
+
import { buildFigureGeometry } from '@hatiolab/things-scene'
|
|
3
3
|
|
|
4
|
-
/** Evaluated
|
|
5
|
-
export interface
|
|
4
|
+
/** Evaluated boundary: graph values are resolved before reaching the renderer. */
|
|
5
|
+
export interface FigureRenderPart {
|
|
6
6
|
id: string
|
|
7
7
|
shape: { provider: string; dimensions: Record<string, number>; profile?: Array<{x:number;y:number}> }
|
|
8
8
|
pose: {r:number[][];t:number[]}
|
|
@@ -15,12 +15,12 @@ export interface V3RenderPart {
|
|
|
15
15
|
*
|
|
16
16
|
* Two are left out. A scalar body carries no shape or pose — it is a number the graph produced, not something
|
|
17
17
|
* on screen. A body the asset hides is evaluated, placed and gated exactly as a shown one, and excluded from
|
|
18
|
-
* rendering, from shadows and from pointer picking (
|
|
18
|
+
* rendering, from shadows and from pointer picking (designer's ruling 2026-09-22); drawing it would make
|
|
19
19
|
* "hidden" mean nothing in the modeller while it means something on a board.
|
|
20
20
|
*/
|
|
21
|
-
export function
|
|
21
|
+
export function drawableFigureParts(geometry: readonly unknown[]): FigureRenderPart[] {
|
|
22
22
|
return geometry.filter(
|
|
23
|
-
(body): body is
|
|
23
|
+
(body): body is FigureRenderPart =>
|
|
24
24
|
!!body && typeof body === 'object' && 'shape' in body && 'pose' in body && (body as { visible?: boolean }).visible !== false
|
|
25
25
|
)
|
|
26
26
|
}
|
|
@@ -29,16 +29,16 @@ export function drawableV3Parts(geometry: readonly unknown[]): V3RenderPart[] {
|
|
|
29
29
|
* The mesh for one body — things-scene's builder, not a copy of it.
|
|
30
30
|
*
|
|
31
31
|
* The modeller kept its own provider switch and it knew four providers; figures use eleven. A body the
|
|
32
|
-
* board drew was refused here with "Unsupported
|
|
32
|
+
* board drew was refused here with "Unsupported provider: roundedBoxXZ" (2026-09-24, the first canvas check of
|
|
33
33
|
* ADR-0087 step 4). One builder, and the canvas an author looks at shows what a board will show.
|
|
34
34
|
*/
|
|
35
|
-
function geometry(part:
|
|
36
|
-
const built =
|
|
37
|
-
if (!built) throw Error('Unsupported
|
|
35
|
+
function geometry(part: FigureRenderPart): THREE.BufferGeometry {
|
|
36
|
+
const built = buildFigureGeometry(part.shape as never, part.segments ?? 12) as THREE.BufferGeometry | undefined
|
|
37
|
+
if (!built) throw Error('Unsupported provider: ' + part.shape.provider)
|
|
38
38
|
return built
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function
|
|
41
|
+
export function disposeFigureMeshes(group:THREE.Group):void {
|
|
42
42
|
for(const child of [...group.children]){
|
|
43
43
|
if(child instanceof THREE.Mesh){child.geometry.dispose();const materials=Array.isArray(child.material)?child.material:[child.material];materials.forEach(m=>m.dispose())}
|
|
44
44
|
group.remove(child)
|
|
@@ -46,15 +46,15 @@ export function disposeV3Meshes(group:THREE.Group):void {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/** Build first, then swap; failed rendering must not erase the previous scene. No old meshes survive a successful swap. */
|
|
49
|
-
export function
|
|
49
|
+
export function replaceFigureMeshes(group:THREE.Group,parts:readonly FigureRenderPart[],palette:Record<string,string>):void {
|
|
50
50
|
const staged=new THREE.Group(),ids=new Set<string>()
|
|
51
51
|
try{
|
|
52
52
|
for(const part of parts){
|
|
53
|
-
if(ids.has(part.id))throw Error('Duplicate
|
|
53
|
+
if(ids.has(part.id))throw Error('Duplicate geometry ID: '+part.id);ids.add(part.id)
|
|
54
54
|
const color=palette[part.material.token]
|
|
55
55
|
if(!color||!/^#[0-9a-f]{6}$/i.test(color))throw Error('Unresolved material token: '+part.material.token)
|
|
56
56
|
const {r,t}=part.pose
|
|
57
|
-
if(r.length!==3||r.some(row=>row.length!==3||row.some(x=>!Number.isFinite(x)))||t.length!==3||t.some(x=>!Number.isFinite(x)))throw Error('Invalid
|
|
57
|
+
if(r.length!==3||r.some(row=>row.length!==3||row.some(x=>!Number.isFinite(x)))||t.length!==3||t.some(x=>!Number.isFinite(x)))throw Error('Invalid pose: '+part.id)
|
|
58
58
|
const emissive=part.material.emissive
|
|
59
59
|
if(emissive?.on&&!/^#[0-9a-f]{6}$/i.test(palette[emissive.token]??''))throw Error('Unresolved emissive token')
|
|
60
60
|
const g=geometry(part)
|
|
@@ -63,8 +63,8 @@ export function replaceV3Meshes(group:THREE.Group,parts:readonly V3RenderPart[],
|
|
|
63
63
|
mesh.matrixAutoUpdate=false;mesh.matrix.set(r[0][0],r[0][1],r[0][2],t[0],r[1][0],r[1][1],r[1][2],t[1],r[2][0],r[2][1],r[2][2],t[2],0,0,0,1)
|
|
64
64
|
staged.add(mesh)
|
|
65
65
|
}
|
|
66
|
-
}catch(error){
|
|
67
|
-
|
|
66
|
+
}catch(error){disposeFigureMeshes(staged);throw error}
|
|
67
|
+
disposeFigureMeshes(group)
|
|
68
68
|
for(const mesh of [...staged.children])group.add(mesh)
|
|
69
69
|
group.updateMatrixWorld(true)
|
|
70
70
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as THREE from 'three'
|
|
2
|
-
import {
|
|
2
|
+
import { buildFigureGeometry } from '@hatiolab/things-scene'
|
|
3
3
|
|
|
4
|
-
import type { GizmoResult } from './
|
|
5
|
-
import type {
|
|
4
|
+
import type { GizmoResult } from './gizmo.js'
|
|
5
|
+
import type { FigureRenderPart } from './renderer.js'
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
|
-
*
|
|
8
|
+
* bodies <-> scene components, in one place.
|
|
9
9
|
*
|
|
10
10
|
* The modeller stands on a things-scene board in editing mode: each body figure-model evaluates is a
|
|
11
|
-
* `figure-
|
|
11
|
+
* `figure-part` component, and what the scene's gizmo writes back comes here to become a change of the part's
|
|
12
12
|
* inputs. The two vocabularies:
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* (asset frame, mm, y up) scene component
|
|
15
15
|
* ──────────────────────────────── ─────────────────────────────────────────────
|
|
16
16
|
* body box extent x · y · z width · depth · height
|
|
17
17
|
* body box centre x left + width/2 - board width/2
|
|
@@ -39,11 +39,11 @@ export interface BodyFrame {
|
|
|
39
39
|
|
|
40
40
|
const round = (n: number) => Math.round(n * 1e6) / 1e6
|
|
41
41
|
|
|
42
|
-
export function frameOf(part:
|
|
43
|
-
/* The box things-scene's `figure-
|
|
42
|
+
export function frameOf(part: FigureRenderPart): BodyFrame | null {
|
|
43
|
+
/* The box things-scene's `figure-part` centres the body on: the same builder, measured the same way. */
|
|
44
44
|
let built: THREE.BufferGeometry | undefined
|
|
45
45
|
try {
|
|
46
|
-
built =
|
|
46
|
+
built = buildFigureGeometry(part.shape as never, part.segments) as THREE.BufferGeometry | undefined
|
|
47
47
|
} catch {
|
|
48
48
|
return null
|
|
49
49
|
}
|
|
@@ -58,14 +58,14 @@ export function frameOf(part: V3RenderPart): BodyFrame | null {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** The colours a body is drawn in, from the palette the modeller resolved (the asset's, over which the theme wins). */
|
|
61
|
-
function colours(part:
|
|
61
|
+
function colours(part: FigureRenderPart, palette: Record<string, string>) {
|
|
62
62
|
const m = part.material
|
|
63
63
|
const glow = m.emissive?.on ? { color: palette[m.emissive.token] ?? palette[m.token] ?? '#9a9a9a', intensity: m.emissive.intensity } : undefined
|
|
64
64
|
return { color: palette[m.token] ?? '#9a9a9a', preset: m.preset, transparent: !!m.transparent, ...(glow ? { glow } : {}) }
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** The component state that draws a body where figure-model put it. */
|
|
68
|
-
export function componentOf(part:
|
|
68
|
+
export function componentOf(part: FigureRenderPart, palette: Record<string, string>, board: Board): Record<string, unknown> | null {
|
|
69
69
|
const frame = frameOf(part)
|
|
70
70
|
if (!frame) return null
|
|
71
71
|
const { centre, extent, rotation, origin } = frame
|
|
@@ -75,7 +75,7 @@ export function componentOf(part: V3RenderPart, palette: Record<string, string>,
|
|
|
75
75
|
depth = extent.y || 1,
|
|
76
76
|
height = extent.z || 1
|
|
77
77
|
return {
|
|
78
|
-
type: 'figure-
|
|
78
|
+
type: 'figure-part',
|
|
79
79
|
name: part.id,
|
|
80
80
|
shape: part.shape,
|
|
81
81
|
...(part.segments !== undefined ? { segments: part.segments } : {}),
|
|
@@ -99,7 +99,7 @@ const num = (v: unknown, fallback = 0) => (typeof v === 'number' && Number.isFin
|
|
|
99
99
|
* order) and how much each of its own axes grew. Only what changed is given; a component the gizmo did not move gives
|
|
100
100
|
* nothing.
|
|
101
101
|
*/
|
|
102
|
-
export function gizmoResultOf(part:
|
|
102
|
+
export function gizmoResultOf(part: FigureRenderPart, after: Record<string, unknown>, board: Board): GizmoResult {
|
|
103
103
|
const frame = frameOf(part)
|
|
104
104
|
if (!frame) return {}
|
|
105
105
|
const { centre, extent, rotation, origin } = frame
|
|
@@ -130,7 +130,7 @@ export function gizmoResultOf(part: V3RenderPart, after: Record<string, unknown>
|
|
|
130
130
|
* A drag as amounts, for a part whose place is worked out (`nudge-part`): how far its origin moved and how far each of
|
|
131
131
|
* its angles turned (rigid@1's order, degrees). Nothing on an axis that did not change.
|
|
132
132
|
*/
|
|
133
|
-
export function nudgeOf(part:
|
|
133
|
+
export function nudgeOf(part: FigureRenderPart, result: GizmoResult): { by: Partial<Record<'x' | 'y' | 'z', number>>; turn: Partial<Record<'x' | 'y' | 'z', number>> } {
|
|
134
134
|
const axes = ['x', 'y', 'z'] as const
|
|
135
135
|
const by: Partial<Record<'x' | 'y' | 'z', number>> = {}
|
|
136
136
|
const turn: Partial<Record<'x' | 'y' | 'z', number>> = {}
|
|
@@ -163,7 +163,7 @@ export function changedKeys(current: Record<string, unknown>, next: Record<strin
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/** A board wide and deep enough for the figure, with room around it; its size only grows while a figure is edited. */
|
|
166
|
-
export function boardFor(parts: readonly
|
|
166
|
+
export function boardFor(parts: readonly FigureRenderPart[], at: Board & { depth?: number } = { width: 0, height: 0 }): Board & { depth: number } {
|
|
167
167
|
const box = new THREE.Box3()
|
|
168
168
|
for (const part of parts) {
|
|
169
169
|
const frame = frameOf(part)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './canvas.js'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { FigureRenderPart } from './renderer.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* 카드에 쓸 그림을 찍는다 — **숨은 자리에
|
|
6
|
+
* 카드에 쓸 그림을 찍는다 — **숨은 자리에 캔버스를 하나 더 세워서.**
|
|
7
7
|
*
|
|
8
8
|
* ## 왜 보이는 캔버스를 찍지 않나
|
|
9
9
|
*
|
|
@@ -33,7 +33,7 @@ const HEIGHT = 360
|
|
|
33
33
|
const PATIENCE = 5000
|
|
34
34
|
|
|
35
35
|
interface Shooter extends HTMLElement {
|
|
36
|
-
parts?:
|
|
36
|
+
parts?: FigureRenderPart[]
|
|
37
37
|
palette?: Record<string, string>
|
|
38
38
|
context?: string
|
|
39
39
|
viewOnly?: boolean
|
|
@@ -55,8 +55,8 @@ function within<T>(work: Promise<T>, ms: number): Promise<T | undefined> {
|
|
|
55
55
|
return Promise.race([work, delay(ms).then(() => undefined)])
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export async function
|
|
59
|
-
parts:
|
|
58
|
+
export async function captureFigureThumbnail(
|
|
59
|
+
parts: FigureRenderPart[],
|
|
60
60
|
palette: Record<string, string>
|
|
61
61
|
): Promise<string | undefined> {
|
|
62
62
|
if (!parts.length) return undefined
|
|
@@ -66,11 +66,11 @@ export async function captureV3Thumbnail(
|
|
|
66
66
|
WebGL context 는 브라우저가 8~16개에서 끊으므로 찍은 뒤 반드시 버린다(아래 finally).
|
|
67
67
|
*/
|
|
68
68
|
const host = document.createElement('div')
|
|
69
|
-
host.setAttribute('figure-
|
|
69
|
+
host.setAttribute('figure-thumbnail-stage', '')
|
|
70
70
|
host.style.cssText = `position: fixed; left: -10000px; top: 0; width: ${WIDTH}px; height: ${HEIGHT}px; pointer-events: none;`
|
|
71
71
|
document.body.appendChild(host)
|
|
72
72
|
|
|
73
|
-
const shooter = document.createElement('figure-
|
|
73
|
+
const shooter = document.createElement('figure-canvas') as Shooter
|
|
74
74
|
shooter.parts = parts
|
|
75
75
|
shooter.palette = palette
|
|
76
76
|
shooter.context = 'thumbnail'
|
|
@@ -429,11 +429,11 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
429
429
|
말이 알려 준다.
|
|
430
430
|
|
|
431
431
|
색칠한 배지로 두지 않는다. 옛 draw call 한도 3 으로 재던 때는 카탈로그 거의 모두가 0 점이라
|
|
432
|
-
붉은 덩어리가 카드마다 섰고 아무것도 가르지 못했다.
|
|
433
|
-
여유라(`
|
|
432
|
+
붉은 덩어리가 카드마다 섰고 아무것도 가르지 못했다. 이 모델은 「한 화면 N 대」 목표에 견준
|
|
433
|
+
여유라(`figureScoreOf`, 2026-09-24) 퍼지지만, 숫자는 조용히 적고 낮은 것에만 **점 하나**를 붙인다.
|
|
434
434
|
|
|
435
435
|
아직 재지 않은 것을 0 으로 보여 주지 않는다 — 0 은 「가볍다」는 뜻이 되어 버린다.
|
|
436
|
-
등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` · `
|
|
436
|
+
등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` · `figureScoreOf` 가 정하고,
|
|
437
437
|
화면이 그것을 베끼면 두 벌이 되어 목록과 저작면이 다른 등급을 말한다.
|
|
438
438
|
*/
|
|
439
439
|
renderer: (value: number | null) =>
|
|
@@ -586,7 +586,7 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
586
586
|
return { total: total || 0, records: items || [] }
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
/** The modeller that can edit this figure, by its format; a new figure starts in
|
|
589
|
+
/** The modeller that can edit this figure, by its format; a new figure starts in (`modeller-route.ts`). */
|
|
590
590
|
private openModeller(figure?: Figure) {
|
|
591
591
|
navigate(modellerPathOf(figure))
|
|
592
592
|
}
|
|
@@ -3,41 +3,41 @@ import { customElement, state } from 'lit/decorators.js'
|
|
|
3
3
|
import { navigate, PageView } from '@operato/shell'
|
|
4
4
|
import { getPathInfo } from '@operato/utils'
|
|
5
5
|
import { modellerPathOf } from '../modeller/modeller-route.js'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { createFigureProductSession } from '../modeller/product-session.js'
|
|
7
|
+
import { figureProductStore } from '../graphql/store.js'
|
|
8
8
|
import { i18next } from '@operato/i18n'
|
|
9
9
|
import { fetchFigure, fetchFigureTypeNames, updateFigure } from '../graphql/index.js'
|
|
10
|
-
import { availableRecipes, recipeGuide } from '../modeller/
|
|
11
|
-
import type { RecipeTemplate } from '../modeller/
|
|
12
|
-
import { occupancyFromParts } from '../modeller/
|
|
13
|
-
import type {
|
|
14
|
-
import '../modeller/
|
|
10
|
+
import { availableRecipes, recipeGuide } from '../modeller/recipe-source.js'
|
|
11
|
+
import type { RecipeTemplate } from '../modeller/recipes.js'
|
|
12
|
+
import { occupancyFromParts } from '../modeller/occupancy.js'
|
|
13
|
+
import type { FigureAuthoringAction } from '@hatiolab/figure-model'
|
|
14
|
+
import '../modeller/editor.js'
|
|
15
15
|
import '@material/web/icon/icon.js'
|
|
16
16
|
import '../modeller/figure-history-panel.js'
|
|
17
|
-
import '../modeller/figure-
|
|
18
|
-
import '../modeller/figure-
|
|
19
|
-
import type {
|
|
17
|
+
import '../modeller/figure-verdict.js'
|
|
18
|
+
import '../modeller/figure-properties.js'
|
|
19
|
+
import type { FigureBudgetLine } from '../modeller/maturity.js'
|
|
20
20
|
import { OxPrompt } from '@operato/popup'
|
|
21
21
|
import { openPopup } from '@operato/layout'
|
|
22
|
-
import '../modeller/figure-
|
|
22
|
+
import '../modeller/figure-first-save.js'
|
|
23
23
|
import { activePalette } from '@hatiolab/things-scene'
|
|
24
24
|
import {
|
|
25
25
|
clearAssistantSurface,
|
|
26
26
|
notifyAssistantSurface,
|
|
27
27
|
provideAssistantSurface
|
|
28
28
|
} from '@things-factory/ai-assistant/dist-client/index.js'
|
|
29
|
-
import type { FirstSave } from '../modeller/
|
|
30
|
-
import { placementRefusalOf } from '../modeller/
|
|
31
|
-
import { takePendingNewFigure } from '../modeller/
|
|
29
|
+
import type { FirstSave } from '../modeller/first-save.js'
|
|
30
|
+
import { placementRefusalOf } from '../modeller/refusals.js'
|
|
31
|
+
import { takePendingNewFigure } from '../modeller/ai-proposal.js'
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* The
|
|
34
|
+
* The product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
35
35
|
*
|
|
36
|
-
* The one modeller since ADR-0087 step 5 (2026-09-24): it answers to `figure-modeller`, and to `figure-
|
|
36
|
+
* The one modeller since ADR-0087 step 5 (2026-09-24): it answers to `figure-modeller`, and to `figure-modeller`
|
|
37
37
|
* as an alias for links made before the switch.
|
|
38
38
|
*/
|
|
39
|
-
@customElement('figure-
|
|
40
|
-
export class
|
|
39
|
+
@customElement('figure-modeller-page')
|
|
40
|
+
export class FigureModellerPage extends PageView {
|
|
41
41
|
static styles = css`
|
|
42
42
|
:host {
|
|
43
43
|
display: flex;
|
|
@@ -50,7 +50,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
50
50
|
*/
|
|
51
51
|
overflow: hidden;
|
|
52
52
|
}
|
|
53
|
-
figure-
|
|
53
|
+
figure-editor {
|
|
54
54
|
flex: 1;
|
|
55
55
|
min-width: 0;
|
|
56
56
|
min-height: 0;
|
|
@@ -92,7 +92,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
92
92
|
color: var(--md-sys-color-error);
|
|
93
93
|
}
|
|
94
94
|
`
|
|
95
|
-
private readonly session =
|
|
95
|
+
private readonly session = createFigureProductSession(figureProductStore)
|
|
96
96
|
private routeId: string | undefined
|
|
97
97
|
private discardApproved = false
|
|
98
98
|
@state() private revision = 0
|
|
@@ -107,7 +107,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
107
107
|
* 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.
|
|
108
108
|
*
|
|
109
109
|
* 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서
|
|
110
|
-
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새
|
|
110
|
+
* `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 도형은 공간을 선언하기 전에는 전부
|
|
111
111
|
* 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 그래서 발행 패널을 건다.
|
|
112
112
|
*/
|
|
113
113
|
@state() private row: { state?: 'draft' | 'released'; version: number; type?: string; category?: string; tags?: string[] } = {
|
|
@@ -118,7 +118,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
118
118
|
@state() private commandError = ''
|
|
119
119
|
/** The verdict's count, handed to the editor's verdict tab. */
|
|
120
120
|
@state() private instances?: number
|
|
121
|
-
@state() private budget?:
|
|
121
|
+
@state() private budget?: FigureBudgetLine
|
|
122
122
|
/**
|
|
123
123
|
* The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is
|
|
124
124
|
* kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.
|
|
@@ -219,8 +219,8 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
219
219
|
/**
|
|
220
220
|
* 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
221
221
|
*
|
|
222
|
-
* 새
|
|
223
|
-
* 행만 바뀐다 —
|
|
222
|
+
* 새 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
223
|
+
* 행만 바뀐다 — 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
224
224
|
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
225
225
|
*/
|
|
226
226
|
private renameType = async (event: Event) => {
|
|
@@ -253,14 +253,14 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
253
253
|
}
|
|
254
254
|
/** A command from the properties tab — rename, declare the room, the mounting face. */
|
|
255
255
|
private onCommand = (event: Event) => {
|
|
256
|
-
const action = (event as CustomEvent).detail?.action as
|
|
256
|
+
const action = (event as CustomEvent).detail?.action as FigureAuthoringAction | undefined
|
|
257
257
|
if (action) this.runCommand(() => action)
|
|
258
258
|
}
|
|
259
259
|
/**
|
|
260
260
|
* One step of the session's history, whichever part of the side asked for it — so undo takes it back like any
|
|
261
261
|
* other edit, and it is saved with the figure.
|
|
262
262
|
*/
|
|
263
|
-
private runCommand(actionOf: () =>
|
|
263
|
+
private runCommand(actionOf: () => FigureAuthoringAction) {
|
|
264
264
|
try {
|
|
265
265
|
this.session.command(actionOf())
|
|
266
266
|
this.commandError = ''
|
|
@@ -348,12 +348,12 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
348
348
|
}
|
|
349
349
|
connectedCallback() {
|
|
350
350
|
super.connectedCallback()
|
|
351
|
-
window.addEventListener('figure-
|
|
351
|
+
window.addEventListener('figure-apply', this.applyAI)
|
|
352
352
|
window.addEventListener('beforeunload', this.holdUnsaved)
|
|
353
353
|
}
|
|
354
354
|
disconnectedCallback() {
|
|
355
355
|
this.withdrawAssistant()
|
|
356
|
-
window.removeEventListener('figure-
|
|
356
|
+
window.removeEventListener('figure-apply', this.applyAI)
|
|
357
357
|
window.removeEventListener('beforeunload', this.holdUnsaved)
|
|
358
358
|
super.disconnectedCallback()
|
|
359
359
|
}
|
|
@@ -429,7 +429,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
429
429
|
/** The first save's question, over the names this domain already uses. */
|
|
430
430
|
private askFirstSave = async (name: string): Promise<FirstSave | undefined> => {
|
|
431
431
|
const taken = await fetchFigureTypeNames().catch(() => [] as string[])
|
|
432
|
-
return openPopup<FirstSave>(html`<figure-
|
|
432
|
+
return openPopup<FirstSave>(html`<figure-first-save .type=${this.row.type ?? ''} .name=${name} .taken=${taken}></figure-first-save>`, {
|
|
433
433
|
backdrop: true,
|
|
434
434
|
size: 'small',
|
|
435
435
|
title: i18next.t('figure.title.save-new-figure')
|
|
@@ -450,16 +450,16 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
450
450
|
render() {
|
|
451
451
|
if (this.error) return html`<p role="alert" class="error">${this.error}</p>`
|
|
452
452
|
const view = this.session.read()
|
|
453
|
-
return html`<figure-
|
|
453
|
+
return html`<figure-editor .session=${this.session} .templates=${this.templates} .guideOf=${this.guideOf} .thumbnailOf=${this.thumbnailOf} .firstSave=${this.askFirstSave} .revision=${this.revision} .instances=${this.instances} .budget=${this.budget} @changed=${this.changed}>
|
|
454
454
|
${this.commandError ? html`<p slot="notice" fit-error role="alert">${this.commandError}</p>` : ''}
|
|
455
|
-
<figure-
|
|
455
|
+
<figure-properties
|
|
456
456
|
slot="figure"
|
|
457
457
|
.source=${this.draftSource()}
|
|
458
458
|
.figureId=${view.id}
|
|
459
459
|
.figureCategory=${this.row.category}
|
|
460
460
|
.figureTags=${this.row.tags}
|
|
461
|
-
@
|
|
462
|
-
@
|
|
461
|
+
@command=${this.onCommand}
|
|
462
|
+
@catalog=${this.onCatalog}
|
|
463
463
|
>
|
|
464
464
|
<label slot="type" type-field>
|
|
465
465
|
<span>${i18next.t('figure.label.figure-type')}</span>
|
|
@@ -472,8 +472,8 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
472
472
|
/>`}
|
|
473
473
|
${this.typeError ? html`<small role="alert">${this.typeError}</small>` : ''}
|
|
474
474
|
</label>
|
|
475
|
-
</figure-
|
|
476
|
-
<figure-
|
|
475
|
+
</figure-properties>
|
|
476
|
+
<figure-verdict
|
|
477
477
|
slot="verdict"
|
|
478
478
|
.source=${this.draftSource()}
|
|
479
479
|
.figureId=${view.id}
|
|
@@ -483,7 +483,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
483
483
|
this.budget = event.detail.budget
|
|
484
484
|
}}
|
|
485
485
|
@fit-base-to-parts=${this.fitToParts}
|
|
486
|
-
></figure-
|
|
486
|
+
></figure-verdict>
|
|
487
487
|
<figure-history-panel
|
|
488
488
|
slot="history"
|
|
489
489
|
.figureId=${view.id}
|
|
@@ -495,7 +495,7 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
495
495
|
@figure-released=${this.released}
|
|
496
496
|
@figure-reverted=${this.reverted}
|
|
497
497
|
></figure-history-panel>
|
|
498
|
-
</figure-
|
|
498
|
+
</figure-editor>`
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
}
|
package/client/route.ts
CHANGED
|
@@ -4,12 +4,8 @@ export default function route(page: string) {
|
|
|
4
4
|
import('./pages/figure-list-page')
|
|
5
5
|
return page
|
|
6
6
|
|
|
7
|
-
/*
|
|
8
|
-
One modeller since ADR-0087 step 5. `figure-v3-modeller` is kept as an alias for links made before the switch.
|
|
9
|
-
*/
|
|
10
7
|
case 'figure-modeller':
|
|
11
|
-
|
|
12
|
-
import('./pages/figure-v3-modeller-page')
|
|
8
|
+
import('./pages/figure-modeller-page')
|
|
13
9
|
return page
|
|
14
10
|
}
|
|
15
11
|
}
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A proposal is a list of the modeller's own commands (ADR-0094). Running it applies them in the modeller as one step
|
|
6
6
|
* of its history: a revision on the figure open there, at the version the model saw; a new figure on a new figure the
|
|
7
|
-
* modeller opens. The outcome is said in words on every branch (`
|
|
7
|
+
* modeller opens. The outcome is said in words on every branch (`ai-proposal.ts`).
|
|
8
8
|
*/
|
|
9
9
|
import { i18next } from '@operato/i18n';
|
|
10
10
|
import { navigate } from '@operato/shell';
|
|
11
11
|
import { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js';
|
|
12
12
|
import { modellerPathOf } from './modeller/modeller-route.js';
|
|
13
|
-
import { pendNewFigure, runFigureProposal } from './modeller/
|
|
13
|
+
import { pendNewFigure, runFigureProposal } from './modeller/ai-proposal.js';
|
|
14
14
|
export const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate';
|
|
15
15
|
registerProposalHandler(FIGURE_CANDIDATE_TOOL, async (proposal, context) => {
|
|
16
16
|
const outcome = await runFigureProposal(proposal, {
|
|
17
17
|
offer(detail) {
|
|
18
|
-
window.dispatchEvent(new CustomEvent('figure-
|
|
18
|
+
window.dispatchEvent(new CustomEvent('figure-apply', { detail }));
|
|
19
19
|
return detail;
|
|
20
20
|
},
|
|
21
21
|
openNew(steps) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-assistant.js","sourceRoot":"","sources":["../client/figure-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA0B,MAAM,
|
|
1
|
+
{"version":3,"file":"figure-assistant.js","sourceRoot":"","sources":["../client/figure-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,mDAAmD,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA0B,MAAM,2BAA2B,CAAA;AAEpG,MAAM,CAAC,MAAM,qBAAqB,GAAG,wBAAwB,CAAA;AAE7D,uBAAuB,CAAC,qBAAqB,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IACzE,MAAM,OAAO,GAAG,MAAM,iBAAiB,CACrC,QAAQ,EACR;QACE,KAAK,CAAC,MAAyB;YAC7B,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;YACjE,OAAO,MAAM,CAAA;QACf,CAAC;QACD,OAAO,CAAC,KAAK;YACX,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;YACnC,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAA;YAC1B,OAAO,MAAM,CAAA;QACf,CAAC;KACF,EACD,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CACxC,CAAA;IACD,IAAI,OAAO,CAAC,IAAI;QAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACjG,OAAO,OAAO,CAAA;AAChB,CAAC,CAAC,CAAA","sourcesContent":["/*\n * What the AI dock does with a figure proposal - figure-ui declares it because figure-service declares the tool\n * (`proposeFigureCandidate`, design/ai/shared-dock.md §4).\n *\n * A proposal is a list of the modeller's own commands (ADR-0094). Running it applies them in the modeller as one step\n * of its history: a revision on the figure open there, at the version the model saw; a new figure on a new figure the\n * modeller opens. The outcome is said in words on every branch (`ai-proposal.ts`).\n */\nimport { i18next } from '@operato/i18n'\nimport { navigate } from '@operato/shell'\nimport { registerProposalHandler } from '@things-factory/ai-assistant/dist-client/index.js'\n\nimport { modellerPathOf } from './modeller/modeller-route.js'\nimport { pendNewFigure, runFigureProposal, type FigureApplyDetail } from './modeller/ai-proposal.js'\n\nexport const FIGURE_CANDIDATE_TOOL = 'proposeFigureCandidate'\n\nregisterProposalHandler(FIGURE_CANDIDATE_TOOL, async (proposal, context) => {\n const outcome = await runFigureProposal(\n proposal,\n {\n offer(detail: FigureApplyDetail) {\n window.dispatchEvent(new CustomEvent('figure-apply', { detail }))\n return detail\n },\n openNew(steps) {\n const opened = pendNewFigure(steps)\n navigate(modellerPathOf())\n return opened\n }\n },\n (key, params) => i18next.t(key, params)\n )\n if (outcome.note) context.notify(outcome.verdict === 'accepted' ? 'info' : 'error', outcome.note)\n return outcome\n})\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import gql from 'graphql-tag';
|
|
2
2
|
import { client } from '@operato/graphql';
|
|
3
3
|
import { fetchFigure } from './index.js';
|
|
4
|
-
/**
|
|
5
|
-
export const
|
|
4
|
+
/** source is sent intact. No conversion into the old parts/base authoring representation. */
|
|
5
|
+
export const figureProductStore = {
|
|
6
6
|
async load(id) {
|
|
7
7
|
const record = await fetchFigure(id);
|
|
8
8
|
if (!record?.source)
|
|
@@ -12,8 +12,8 @@ export const v3ProductStore = {
|
|
|
12
12
|
async save(request) {
|
|
13
13
|
const response = await client.mutate({
|
|
14
14
|
mutation: gql `
|
|
15
|
-
mutation
|
|
16
|
-
|
|
15
|
+
mutation SaveFigure($source: String!, $id: String, $expectedSource: String, $creationId: String, $thumbnail: String, $type: String) {
|
|
16
|
+
saveFigure(
|
|
17
17
|
source: $source
|
|
18
18
|
id: $id
|
|
19
19
|
expectedSource: $expectedSource
|
|
@@ -30,10 +30,10 @@ export const v3ProductStore = {
|
|
|
30
30
|
});
|
|
31
31
|
if (response.errors?.length)
|
|
32
32
|
throw Error(response.errors.map(e => e.message).join('\n'));
|
|
33
|
-
const result = response.data?.
|
|
33
|
+
const result = response.data?.saveFigure;
|
|
34
34
|
if (!result?.id || !result.source)
|
|
35
35
|
throw Error('서버가 저장 결과를 돌려주지 않았습니다.');
|
|
36
36
|
return result;
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
//# sourceMappingURL=
|
|
39
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../client/graphql/store.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,kBAAkB,GAAuB;IACpD,KAAK,CAAC,IAAI,CAAC,EAAE;QACX,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAA;QACpD,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;IACjD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,OAAO;QAChB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;;;;;;;;OAcZ;YACD,SAAS,EAAE,OAAO;SACnB,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,UAA6C,CAAA;QAC3E,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC,wBAAwB,CAAC,CAAA;QACxE,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA","sourcesContent":["import gql from 'graphql-tag'\nimport { client } from '@operato/graphql'\nimport { fetchFigure } from './index.js'\nimport type { FigureProductStore, FigureProductRecord } from '../modeller/product-session.js'\n\n/** source is sent intact. No conversion into the old parts/base authoring representation. */\nexport const figureProductStore: FigureProductStore = {\n async load(id) {\n const record = await fetchFigure(id)\n if (!record?.source) throw Error('Figure 정본이 없습니다.')\n return { id: record.id, source: record.source }\n },\n async save(request) {\n const response = await client.mutate({\n mutation: gql`\n mutation SaveFigure($source: String!, $id: String, $expectedSource: String, $creationId: String, $thumbnail: String, $type: String) {\n saveFigure(\n source: $source\n id: $id\n expectedSource: $expectedSource\n creationId: $creationId\n thumbnail: $thumbnail\n type: $type\n ) {\n id\n source\n }\n }\n `,\n variables: request\n })\n if (response.errors?.length) throw Error(response.errors.map(e => e.message).join('\\n'))\n const result = response.data?.saveFigure as FigureProductRecord | undefined\n if (!result?.id || !result.source) throw Error('서버가 저장 결과를 돌려주지 않았습니다.')\n return result\n }\n}\n"]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FigureProposalStep } from '@hatiolab/figure-model';
|
|
2
2
|
/** What the card carries, as `proposeFigureCandidate` returned it. */
|
|
3
3
|
export interface FigureProposalCard {
|
|
4
|
-
steps?:
|
|
4
|
+
steps?: FigureProposalStep[];
|
|
5
5
|
create?: boolean;
|
|
6
6
|
figureId?: string;
|
|
7
7
|
context?: string;
|
|
8
8
|
revision?: number;
|
|
9
9
|
}
|
|
10
|
-
/** The `figure-
|
|
10
|
+
/** The `figure-apply` event's detail. The page that takes it sets `applied`, and `error` when refused. */
|
|
11
11
|
export interface FigureApplyDetail {
|
|
12
|
-
steps:
|
|
12
|
+
steps: FigureProposalStep[];
|
|
13
13
|
create: boolean;
|
|
14
14
|
context?: string;
|
|
15
15
|
revision?: number;
|
|
@@ -22,7 +22,7 @@ export interface FigureProposalTarget {
|
|
|
22
22
|
/** Offers the proposal to a modeller on screen. It comes back untouched when none took it. */
|
|
23
23
|
offer(detail: FigureApplyDetail): FigureApplyDetail;
|
|
24
24
|
/** Opens a new figure in the modeller and applies the steps there; resolves once the page has, or has not. */
|
|
25
|
-
openNew(steps:
|
|
25
|
+
openNew(steps: FigureProposalStep[]): Promise<{
|
|
26
26
|
applied: boolean;
|
|
27
27
|
error?: string;
|
|
28
28
|
code?: string;
|
|
@@ -35,14 +35,14 @@ export interface FigureProposalOutcome {
|
|
|
35
35
|
note?: string;
|
|
36
36
|
}
|
|
37
37
|
export declare function runFigureProposal(card: FigureProposalCard, target: FigureProposalTarget, say: Say): Promise<FigureProposalOutcome>;
|
|
38
|
-
export declare function pendNewFigure(steps:
|
|
38
|
+
export declare function pendNewFigure(steps: FigureProposalStep[], ms?: number): Promise<{
|
|
39
39
|
applied: boolean;
|
|
40
40
|
error?: string;
|
|
41
41
|
code?: string;
|
|
42
42
|
}>;
|
|
43
43
|
/** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */
|
|
44
44
|
export declare function takePendingNewFigure(): {
|
|
45
|
-
steps:
|
|
45
|
+
steps: FigureProposalStep[];
|
|
46
46
|
settle: (result: {
|
|
47
47
|
applied: boolean;
|
|
48
48
|
error?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-proposal.js","sourceRoot":"","sources":["../../client/modeller/ai-proposal.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * An AI proposal reaching the modeller (ADR-0094). The dock's card carries the modeller's own commands; running it\n * applies them as one step of the modeller's history, undone whole, on the version the model saw. Nothing half made\n * is applied, and every outcome is said: applied, refused with the reason, or not possible here.\n *\n * Kept free of Lit, i18n and the shell so node tests read it directly; the handler and the page pass in what they own.\n */\nimport type { FigureProposalStep } from '@hatiolab/figure-model'\n\n/** What the card carries, as `proposeFigureCandidate` returned it. */\nexport interface FigureProposalCard {\n steps?: FigureProposalStep[]\n create?: boolean\n figureId?: string\n context?: string\n revision?: number\n}\n\n/** The `figure-apply` event's detail. The page that takes it sets `applied`, and `error` when refused. */\nexport interface FigureApplyDetail {\n steps: FigureProposalStep[]\n create: boolean\n context?: string\n revision?: number\n applied?: boolean\n error?: string\n code?: string\n}\n\nexport type Say = (key: string, params?: Record<string, string>) => string\n\nexport interface FigureProposalTarget {\n /** Offers the proposal to a modeller on screen. It comes back untouched when none took it. */\n offer(detail: FigureApplyDetail): FigureApplyDetail\n /** Opens a new figure in the modeller and applies the steps there; resolves once the page has, or has not. */\n openNew(steps: FigureProposalStep[]): Promise<{ applied: boolean; error?: string; code?: string }>\n}\n\nexport interface FigureProposalOutcome {\n verdict: 'accepted' | 'refused' | 'unavailable'\n errorCode?: string\n applied?: number\n note?: string\n}\n\n/** The workspace refuses a request made on another version; said as what happened, not as the kernel's code. */\nconst STALE = new Set(['REVISION_CONFLICT', 'CONTEXT_CONFLICT'])\n\nfunction refused(code: string | undefined, error: string | undefined, say: Say): FigureProposalOutcome {\n const note =\n code && STALE.has(code)\n ? say('figure.text.proposal-stale')\n : code === 'NEW_FIGURE_UNSAVED'\n ? (error ?? '')\n : say('figure.text.proposal-refused', { reason: error ?? '' })\n return { verdict: 'refused', errorCode: code ?? 'refused', note }\n}\n\nexport async function runFigureProposal(card: FigureProposalCard, target: FigureProposalTarget, say: Say): Promise<FigureProposalOutcome> {\n const steps = card?.steps\n if (!Array.isArray(steps) || !steps.length) return { verdict: 'unavailable', errorCode: 'no-steps' }\n const create = card.create === true\n const accepted = (): FigureProposalOutcome => ({\n verdict: 'accepted',\n applied: steps.length,\n note: say('figure.text.proposal-applied', { n: String(steps.length) })\n })\n\n const detail = target.offer({ steps, create, context: card.context, revision: card.revision })\n if (detail.applied) return accepted()\n if (detail.applied === false) return refused(detail.code, detail.error, say)\n\n /* A revision belongs to the figure the model saw; with that modeller gone there is nowhere true to apply it. */\n if (!create) return { verdict: 'unavailable', errorCode: 'modeller-not-open', note: say('figure.text.proposal-needs-modeller') }\n\n const opened = await target.openNew(steps)\n if (opened.applied) return accepted()\n return opened.code === 'not-opened'\n ? { verdict: 'unavailable', errorCode: 'not-opened', note: say('figure.text.proposal-not-opened') }\n : refused(opened.code, opened.error, say)\n}\n\n/*\n A new figure's proposal handed to the modeller page across a navigation: the handler leaves it here and opens the\n modeller on a new figure; the page takes it once it has started that figure. Left untaken, it lapses -- a later new\n figure must not receive steps nobody is waiting for.\n*/\nlet pending: { steps: FigureProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined\n\nexport function pendNewFigure(steps: FigureProposalStep[], ms = 10_000): Promise<{ applied: boolean; error?: string; code?: string }> {\n pending?.settle({ applied: false, code: 'not-opened' })\n return new Promise(resolve => {\n const mine = {\n steps,\n settle: (result: { applied: boolean; error?: string; code?: string }) => {\n clearTimeout(timer)\n if (pending === mine) pending = undefined\n resolve(result)\n }\n }\n const timer = setTimeout(() => mine.settle({ applied: false, code: 'not-opened' }), ms)\n pending = mine\n })\n}\n\n/** For the page, on a new figure it has just started: the steps waiting for it, and where to say how it went. */\nexport function takePendingNewFigure(): { steps: FigureProposalStep[]; settle: (result: { applied: boolean; error?: string; code?: string }) => void } | undefined {\n const taken = pending\n pending = undefined\n return taken\n}\n"]}
|