@things-factory/figure-ui 10.1.75 → 10.1.76
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/store.js +39 -0
- package/dist-client/graphql/store.js.map +1 -0
- package/dist-client/modeller/ai-proposal.d.ts +51 -0
- package/dist-client/modeller/ai-proposal.js +64 -0
- package/dist-client/modeller/ai-proposal.js.map +1 -0
- package/dist-client/modeller/align.d.ts +30 -0
- package/dist-client/modeller/align.js +75 -0
- package/dist-client/modeller/align.js.map +1 -0
- package/dist-client/modeller/canvas.d.ts +72 -0
- package/dist-client/modeller/canvas.js +379 -0
- package/dist-client/modeller/canvas.js.map +1 -0
- package/dist-client/modeller/clips.d.ts +39 -0
- package/dist-client/modeller/clips.js +58 -0
- package/dist-client/modeller/clips.js.map +1 -0
- package/dist-client/modeller/controls.d.ts +28 -0
- package/dist-client/modeller/controls.js +28 -0
- package/dist-client/modeller/controls.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +203 -0
- package/dist-client/modeller/editor.js +1526 -0
- package/dist-client/modeller/editor.js.map +1 -0
- package/dist-client/modeller/fields.d.ts +45 -0
- package/dist-client/modeller/fields.js +83 -0
- package/dist-client/modeller/fields.js.map +1 -0
- package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
- package/dist-client/modeller/figure-capability-panel.js +141 -0
- package/dist-client/modeller/figure-capability-panel.js.map +1 -0
- package/dist-client/modeller/figure-clips.d.ts +31 -0
- package/dist-client/modeller/figure-clips.js +268 -0
- package/dist-client/modeller/figure-clips.js.map +1 -0
- package/dist-client/modeller/figure-controls.d.ts +36 -0
- package/dist-client/modeller/figure-controls.js +226 -0
- package/dist-client/modeller/figure-controls.js.map +1 -0
- package/dist-client/modeller/figure-facts.d.ts +35 -0
- package/dist-client/modeller/figure-facts.js +55 -0
- package/dist-client/modeller/figure-facts.js.map +1 -0
- package/dist-client/modeller/figure-first-save.d.ts +20 -0
- package/dist-client/modeller/figure-first-save.js +118 -0
- 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-links.d.ts +77 -0
- package/dist-client/modeller/figure-links.js +649 -0
- package/dist-client/modeller/figure-links.js.map +1 -0
- package/dist-client/modeller/figure-material-panel.d.ts +39 -0
- package/dist-client/modeller/figure-material-panel.js +323 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -0
- package/dist-client/modeller/figure-parts.d.ts +69 -0
- package/dist-client/modeller/figure-parts.js +559 -0
- package/dist-client/modeller/figure-parts.js.map +1 -0
- package/dist-client/modeller/figure-preview.d.ts +34 -0
- package/dist-client/modeller/figure-preview.js +346 -0
- package/dist-client/modeller/figure-preview.js.map +1 -0
- package/dist-client/modeller/figure-properties.d.ts +45 -0
- package/dist-client/modeller/figure-properties.js +239 -0
- package/dist-client/modeller/figure-properties.js.map +1 -0
- package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
- package/dist-client/modeller/figure-recipe-catalog.js +399 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/figure-verdict.d.ts +64 -0
- package/dist-client/modeller/figure-verdict.js +787 -0
- package/dist-client/modeller/figure-verdict.js.map +1 -0
- package/dist-client/modeller/first-save.d.ts +17 -0
- package/dist-client/modeller/first-save.js +26 -0
- 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/gizmo.d.ts +72 -0
- package/dist-client/modeller/gizmo.js +111 -0
- package/dist-client/modeller/gizmo.js.map +1 -0
- package/dist-client/modeller/help.d.ts +18 -0
- package/dist-client/modeller/help.js +37 -0
- package/dist-client/modeller/help.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +19 -0
- package/dist-client/modeller/joint-drag.js +97 -0
- package/dist-client/modeller/joint-drag.js.map +1 -0
- package/dist-client/modeller/joint-fan.d.ts +9 -0
- package/dist-client/modeller/joint-fan.js +276 -0
- package/dist-client/modeller/joint-fan.js.map +1 -0
- package/dist-client/modeller/keys.d.ts +22 -0
- package/dist-client/modeller/keys.js +26 -0
- package/dist-client/modeller/keys.js.map +1 -0
- package/dist-client/modeller/links.d.ts +267 -0
- package/dist-client/modeller/links.js +480 -0
- package/dist-client/modeller/links.js.map +1 -0
- package/dist-client/modeller/live.d.ts +37 -0
- package/dist-client/modeller/live.js +68 -0
- package/dist-client/modeller/live.js.map +1 -0
- package/dist-client/modeller/maturity.d.ts +120 -0
- package/dist-client/modeller/maturity.js +313 -0
- 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/occupancy.d.ts +18 -0
- package/dist-client/modeller/occupancy.js +24 -0
- package/dist-client/modeller/occupancy.js.map +1 -0
- package/dist-client/modeller/palette.d.ts +7 -0
- package/dist-client/modeller/palette.js +46 -0
- package/dist-client/modeller/palette.js.map +1 -0
- package/dist-client/modeller/preview-model.d.ts +47 -0
- package/dist-client/modeller/preview-model.js +68 -0
- package/dist-client/modeller/preview-model.js.map +1 -0
- package/dist-client/modeller/product-session.d.ts +510 -0
- package/dist-client/modeller/product-session.js +157 -0
- package/dist-client/modeller/product-session.js.map +1 -0
- package/dist-client/modeller/recipe-source.d.ts +8 -0
- package/dist-client/modeller/recipe-source.js +14 -0
- package/dist-client/modeller/recipe-source.js.map +1 -0
- package/dist-client/modeller/recipes.d.ts +37 -0
- package/dist-client/modeller/recipes.js +103 -0
- package/dist-client/modeller/recipes.js.map +1 -0
- package/dist-client/modeller/refusals.d.ts +68 -0
- package/dist-client/modeller/refusals.js +142 -0
- package/dist-client/modeller/refusals.js.map +1 -0
- package/dist-client/modeller/renderer.d.ts +40 -0
- package/dist-client/modeller/renderer.js +73 -0
- package/dist-client/modeller/renderer.js.map +1 -0
- package/dist-client/modeller/stage-model.d.ts +39 -0
- package/dist-client/modeller/stage-model.js +147 -0
- package/dist-client/modeller/stage-model.js.map +1 -0
- package/dist-client/modeller/thumbnail.d.ts +3 -0
- package/dist-client/modeller/thumbnail.js +75 -0
- 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-modeller-page.d.ts +102 -0
- package/dist-client/pages/figure-modeller-page.js +548 -0
- 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/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
|
@@ -4,11 +4,11 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
-
import type {
|
|
7
|
+
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
8
|
|
|
9
|
-
import { clipsOf, poseAt, trackEdit, undrivenOf, withKey, withoutKey, type Clip, type ClipTrack } from './
|
|
9
|
+
import { clipsOf, poseAt, trackEdit, undrivenOf, withKey, withoutKey, type Clip, type ClipTrack } from './clips.js'
|
|
10
10
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
11
|
-
import { HelpToggles } from './
|
|
11
|
+
import { HelpToggles } from './help.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 클립 — controls played by keys over time. A clip is one clock; each track plays one
|
|
@@ -16,8 +16,8 @@ import { HelpToggles } from './v3-help.js'
|
|
|
16
16
|
* is added at the scrubbed moment with the control's value there, so a pose is made with the controls' sliders and
|
|
17
17
|
* then kept as a key. Every change is one part edit.
|
|
18
18
|
*/
|
|
19
|
-
@customElement('figure-
|
|
20
|
-
export class
|
|
19
|
+
@customElement('figure-clips')
|
|
20
|
+
export class FigureClips extends localize(i18next)(LitElement) {
|
|
21
21
|
private readonly help = new HelpToggles(this)
|
|
22
22
|
static styles = [SidePanelStyles, css`
|
|
23
23
|
:host {
|
|
@@ -91,7 +91,7 @@ export class FigureV3Clips extends localize(i18next)(LitElement) {
|
|
|
91
91
|
}
|
|
92
92
|
`]
|
|
93
93
|
|
|
94
|
-
@property({ attribute: false }) asset?:
|
|
94
|
+
@property({ attribute: false }) asset?: FigureAsset
|
|
95
95
|
/** The values the editor holds now, by input id -- what a new key takes. */
|
|
96
96
|
@property({ attribute: false }) values: Record<string, number> = {}
|
|
97
97
|
@property({ type: Boolean }) locked = false
|
|
@@ -184,7 +184,7 @@ export class FigureV3Clips extends localize(i18next)(LitElement) {
|
|
|
184
184
|
</div>`
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
private renderAdd(asset:
|
|
187
|
+
private renderAdd(asset: FigureAsset, clips: Clip[]) {
|
|
188
188
|
const free = undrivenOf(asset)
|
|
189
189
|
if (!free.length) return clips.length ? nothing : html`<p hint>${i18next.t('figure.text.no-controls')}</p>`
|
|
190
190
|
const a = this.adding
|
|
@@ -241,13 +241,13 @@ export class FigureV3Clips extends localize(i18next)(LitElement) {
|
|
|
241
241
|
if (!changes.length) return
|
|
242
242
|
if (live) {
|
|
243
243
|
const values = Object.fromEntries(changes.map(c => [c.input, c.value]))
|
|
244
|
-
this.dispatchEvent(new CustomEvent('
|
|
244
|
+
this.dispatchEvent(new CustomEvent('states-live', { detail: { values }, bubbles: true, composed: true }))
|
|
245
245
|
return
|
|
246
246
|
}
|
|
247
|
-
this.dispatchEvent(new CustomEvent('
|
|
247
|
+
this.dispatchEvent(new CustomEvent('states', { detail: { changes }, bubbles: true, composed: true }))
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
private edit(edit: unknown) {
|
|
251
|
-
this.dispatchEvent(new CustomEvent('
|
|
251
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit }, bubbles: true, composed: true }))
|
|
252
252
|
}
|
|
253
253
|
}
|
|
@@ -4,9 +4,9 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
-
import type {
|
|
7
|
+
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
8
8
|
|
|
9
|
-
import { controlsOf, poseAsDefault, poseDiffers, type Control } from './
|
|
9
|
+
import { controlsOf, poseAsDefault, poseDiffers, type Control } from './controls.js'
|
|
10
10
|
import { dialShape } from './angle-dial.js'
|
|
11
11
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
12
12
|
|
|
@@ -15,12 +15,12 @@ import { SidePanelStyles } from './side-panel-styles.js'
|
|
|
15
15
|
* with a slider to try it, its name, range and travel time, and a button that keeps the pose being looked at as the
|
|
16
16
|
* pose it stands in when placed.
|
|
17
17
|
*
|
|
18
|
-
* Trying a value is an input change (`
|
|
18
|
+
* Trying a value is an input change (`state`, not saved as the default); naming, the range and the travel time are
|
|
19
19
|
* one `set-state-input` part edit each; keeping the pose is one `set-state-defaults` edit. A control is made with
|
|
20
20
|
* a motion on a part (「움직임」), which is where it gets its meaning.
|
|
21
21
|
*/
|
|
22
|
-
@customElement('figure-
|
|
23
|
-
export class
|
|
22
|
+
@customElement('figure-controls')
|
|
23
|
+
export class FigureControls extends localize(i18next)(LitElement) {
|
|
24
24
|
static styles = [SidePanelStyles, css`
|
|
25
25
|
:host {
|
|
26
26
|
display: block;
|
|
@@ -96,7 +96,7 @@ export class FigureV3Controls extends localize(i18next)(LitElement) {
|
|
|
96
96
|
`]
|
|
97
97
|
|
|
98
98
|
/** The asset being edited. */
|
|
99
|
-
@property({ attribute: false }) asset?:
|
|
99
|
+
@property({ attribute: false }) asset?: FigureAsset
|
|
100
100
|
/** The values the editor holds now, by input id. */
|
|
101
101
|
@property({ attribute: false }) values: Record<string, number> = {}
|
|
102
102
|
@property({ type: Boolean }) locked = false
|
|
@@ -193,7 +193,7 @@ export class FigureV3Controls extends localize(i18next)(LitElement) {
|
|
|
193
193
|
private drag(c: Control, value: number) {
|
|
194
194
|
if (!Number.isFinite(value)) return
|
|
195
195
|
this.live = { ...this.live, [c.id]: value }
|
|
196
|
-
this.dispatchEvent(new CustomEvent('
|
|
196
|
+
this.dispatchEvent(new CustomEvent('state-live', { detail: { input: c.id, value }, bubbles: true, composed: true }))
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/** Try a value: the pose being looked at, clamped to the range. Not saved as the default. The slider sends it on
|
|
@@ -203,10 +203,10 @@ export class FigureV3Controls extends localize(i18next)(LitElement) {
|
|
|
203
203
|
const clamped = Math.min(Math.max(value, c.min), c.max)
|
|
204
204
|
const { [c.id]: _released, ...rest } = this.live
|
|
205
205
|
this.live = rest
|
|
206
|
-
this.dispatchEvent(new CustomEvent('
|
|
206
|
+
this.dispatchEvent(new CustomEvent('state', { detail: { input: c.id, unit: c.unit, value: clamped }, bubbles: true, composed: true }))
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
private edit(edit: unknown) {
|
|
210
|
-
this.dispatchEvent(new CustomEvent('
|
|
210
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit }, bubbles: true, composed: true }))
|
|
211
211
|
}
|
|
212
212
|
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { compileFigureAsset, parseFigureAsset, figureOccupiedBoxesOf, figureWritersOf } from '@hatiolab/figure-model'
|
|
2
|
+
import type { FigureAsset, FigureFace } from '@hatiolab/figure-model'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
* What the
|
|
5
|
+
* What the properties tab reads, kept apart from the element so it can be tested without a browser.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export type Placement = 'floor' | 'ceiling' | 'center' | 'wall'
|
|
9
9
|
|
|
10
10
|
/** What the properties tab reads off the asset. Absent parts of the asset read as absent, never as a default. */
|
|
11
|
-
export interface
|
|
11
|
+
export interface FigureFigureFacts {
|
|
12
12
|
name: string
|
|
13
13
|
parts: string[]
|
|
14
14
|
declared: boolean
|
|
15
15
|
placement?: Placement
|
|
16
|
-
mounting?: { part: string; face:
|
|
16
|
+
mounting?: { part: string; face: FigureFace }
|
|
17
17
|
/**
|
|
18
18
|
* Where the figure meets its plane, as its parts stand: the part whose bottom is lowest for a floor, whose top is
|
|
19
19
|
* highest for a ceiling, whose back is furthest back for a wall. What the mounting face is offered as before one is
|
|
20
20
|
* chosen -- the first part in the list offered a conveyor's bed, 700 mm up on its legs (walk 1, 2026-09-26).
|
|
21
21
|
*/
|
|
22
|
-
suggested?: { part: string; face:
|
|
22
|
+
suggested?: { part: string; face: FigureFace }
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -29,8 +29,8 @@ export interface V3FigureFacts {
|
|
|
29
29
|
* records the face only as that node, so this is where the screen learns which one is chosen. Parts are the placed
|
|
30
30
|
* bodies (`place@1`), in the order the model lists them.
|
|
31
31
|
*/
|
|
32
|
-
export function
|
|
33
|
-
const asset =
|
|
32
|
+
export function figureFigureFacts(source: string): FigureFigureFacts {
|
|
33
|
+
const asset = parseFigureAsset(source) as FigureAsset & { document: { model: { nodes: Array<{ id: string; op: string }> } } }
|
|
34
34
|
const parts = asset.document.model.nodes.filter(node => node.op === 'place@1').map(node => node.id)
|
|
35
35
|
const feature = (asset.occupancy?.contact as { surface?: { feature?: string } } | undefined)?.surface?.feature
|
|
36
36
|
const found = feature?.match(/^(.+)\.mount\.(left|right|bottom|top|back|front)\.value$/)
|
|
@@ -39,17 +39,17 @@ export function v3FigureFacts(source: string): V3FigureFacts {
|
|
|
39
39
|
parts,
|
|
40
40
|
declared: !!asset.occupancy,
|
|
41
41
|
placement: asset.occupancy?.placement as Placement | undefined,
|
|
42
|
-
mounting: found ? { part: found[1], face: found[2] as
|
|
42
|
+
mounting: found ? { part: found[1], face: found[2] as FigureFace } : undefined,
|
|
43
43
|
suggested: suggestedMountOf(asset, parts)
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function suggestedMountOf(asset:
|
|
47
|
+
function suggestedMountOf(asset: FigureAsset, parts: string[]): { part: string; face: FigureFace } | undefined {
|
|
48
48
|
const placement = (asset.occupancy?.placement ?? 'floor') as Placement
|
|
49
49
|
if (placement === 'center' || !parts.length) return undefined
|
|
50
50
|
let boxes
|
|
51
51
|
try {
|
|
52
|
-
boxes =
|
|
52
|
+
boxes = figureOccupiedBoxesOf(compileFigureAsset(asset).evaluate().geometry, figureWritersOf(asset)).filter(b => parts.includes(b.part))
|
|
53
53
|
} catch {
|
|
54
54
|
return undefined
|
|
55
55
|
}
|
|
@@ -7,14 +7,14 @@ import { CommonPopupStyles, FormFieldStyles } from '@operato/styles'
|
|
|
7
7
|
import '@material/web/button/filled-button.js'
|
|
8
8
|
import '@material/web/button/text-button.js'
|
|
9
9
|
|
|
10
|
-
import { firstSaveRefusalOf, releasableType, typeOf, type FirstSave } from './
|
|
10
|
+
import { firstSaveRefusalOf, releasableType, typeOf, type FirstSave } from './first-save.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The first save of a new figure: its type and its name, asked once, in the suite's popup look.
|
|
14
14
|
* Closes with the answer, or with nothing when cancelled -- then nothing is saved.
|
|
15
15
|
*/
|
|
16
|
-
@customElement('figure-
|
|
17
|
-
export class
|
|
16
|
+
@customElement('figure-first-save')
|
|
17
|
+
export class FigureFirstSave extends LitElement {
|
|
18
18
|
static styles = [
|
|
19
19
|
FormFieldStyles,
|
|
20
20
|
CommonPopupStyles,
|
|
@@ -210,7 +210,7 @@ export class FigureHistoryPanel extends localize(i18next)(LitElement) {
|
|
|
210
210
|
/** 저장하지 않은 것이 있나 — 있으면 발행을 막는다. */
|
|
211
211
|
@property({ type: Boolean }) dirty = false
|
|
212
212
|
/**
|
|
213
|
-
* 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 —
|
|
213
|
+
* 행의 타입. 판정 하나(`type-is-placeholder`)가 이것을 본다 — 자산은 타입을 원본에 적지 않아서, 타입을 바꾸면
|
|
214
214
|
* 원본도 dirty 도 그대로다. 그래서 이 값이 바뀌면 판정을 다시 묻는다. 안 그러면 타입을 정한 뒤에도 「임시 타입」
|
|
215
215
|
* 판정이 남아 발행 단추가 잠긴 채다.
|
|
216
216
|
*/
|
|
@@ -4,8 +4,8 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
4
4
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
5
5
|
|
|
6
6
|
import { i18next, localize } from '@operato/i18n'
|
|
7
|
-
import {
|
|
8
|
-
import type {
|
|
7
|
+
import { FIGURE_FACES } from '@hatiolab/figure-model'
|
|
8
|
+
import type { FigureAsset, FigureAuthoringAction, FigureDimensionSource, FigureFace } from '@hatiolab/figure-model'
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
acceptsOf,
|
|
@@ -43,17 +43,17 @@ import {
|
|
|
43
43
|
type Axis,
|
|
44
44
|
type Follow,
|
|
45
45
|
type MotionForm
|
|
46
|
-
} from './
|
|
46
|
+
} from './links.js'
|
|
47
47
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
48
|
-
import { HelpToggles } from './
|
|
48
|
+
import { HelpToggles } from './help.js'
|
|
49
49
|
|
|
50
|
-
const FACES = Object.keys(
|
|
50
|
+
const FACES = Object.keys(FIGURE_FACES) as FigureFace[]
|
|
51
51
|
|
|
52
52
|
const freshFasten = () => ({
|
|
53
|
-
face: 'bottom' as
|
|
53
|
+
face: 'bottom' as FigureFace | 'centre',
|
|
54
54
|
toPlane: false,
|
|
55
55
|
target: '',
|
|
56
|
-
targetFace: 'top' as
|
|
56
|
+
targetFace: 'top' as FigureFace,
|
|
57
57
|
facing: 'meet' as 'meet' | 'flush',
|
|
58
58
|
gap: 0,
|
|
59
59
|
twist: 0,
|
|
@@ -62,7 +62,7 @@ const freshFasten = () => ({
|
|
|
62
62
|
})
|
|
63
63
|
const freshMotion = (): MotionForm => ({ kind: 'slide', axis: 'x', id: '', min: 0, max: 500, follow: '', multiplier: 1, offset: 0, travel: '', travelPlus: 0 })
|
|
64
64
|
const freshRow = () => ({ axis: '' as Axis | '', pitch: 200, fills: true, count: 5 })
|
|
65
|
-
const freshPlace = () => ({ role: 'grip' as 'seat' | 'grip', face: 'top' as
|
|
65
|
+
const freshPlace = () => ({ role: 'grip' as 'seat' | 'grip', face: 'top' as FigureFace, label: '', accepts: '', capacity: 1 })
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* What a part is tied to — its size response, what it is fastened to, how it moves.
|
|
@@ -73,12 +73,12 @@ const freshPlace = () => ({ role: 'grip' as 'seat' | 'grip', face: 'top' as V3Fa
|
|
|
73
73
|
* parts that come away — this is where they do it. Motion is a joint: a slide or a turn driven by a control.
|
|
74
74
|
*
|
|
75
75
|
* The sections are dimensions, placement and joint, in that order. Every
|
|
76
|
-
* change is one figure-model authoring command, sent as `
|
|
76
|
+
* change is one figure-model authoring command, sent as `command` and run by the editor through the session — one
|
|
77
77
|
* step of the undo history. Choices the command would refuse (a round body fastened, a size following an instance
|
|
78
78
|
* size the figure does not declare) are not offered, so the author is not sent into a refusal.
|
|
79
79
|
*/
|
|
80
|
-
@customElement('figure-
|
|
81
|
-
export class
|
|
80
|
+
@customElement('figure-links')
|
|
81
|
+
export class FigureLinks extends localize(i18next)(LitElement) {
|
|
82
82
|
private readonly help = new HelpToggles(this)
|
|
83
83
|
static styles = [SidePanelStyles, css`
|
|
84
84
|
:host {
|
|
@@ -110,7 +110,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
110
110
|
`]
|
|
111
111
|
|
|
112
112
|
/** The asset being edited. */
|
|
113
|
-
@property({ attribute: false }) asset?:
|
|
113
|
+
@property({ attribute: false }) asset?: FigureAsset
|
|
114
114
|
/** The selected part. */
|
|
115
115
|
@property() partId = ''
|
|
116
116
|
/** The part's own extent along its axes now, in mm — what a dimension is fixed at when it stops following. */
|
|
@@ -166,7 +166,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/** Where things are held on this part: a seat things rest on, or a grip things are held in. */
|
|
169
|
-
private renderPlaces(asset:
|
|
169
|
+
private renderPlaces(asset: FigureAsset) {
|
|
170
170
|
const faces = placeFacesOf(asset, this.partId)
|
|
171
171
|
const p = this.place
|
|
172
172
|
const face = faces.includes(p.face) ? p.face : faces[0]!
|
|
@@ -196,7 +196,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
196
196
|
<option value="grip" ?selected=${p.role === 'grip'}>${roleName('grip')}</option>
|
|
197
197
|
<option value="seat" ?selected=${p.role === 'seat'}>${roleName('seat')}</option>
|
|
198
198
|
</select>
|
|
199
|
-
<select @change=${(e: Event) => (this.place = { ...p, face: (e.target as HTMLSelectElement).value as
|
|
199
|
+
<select @change=${(e: Event) => (this.place = { ...p, face: (e.target as HTMLSelectElement).value as FigureFace })}>
|
|
200
200
|
${faces.map(one => html`<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}
|
|
201
201
|
</select>
|
|
202
202
|
</div>
|
|
@@ -230,12 +230,12 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/** The figure holds things: `Capacity` and `Holdable`, the ladder's middle rung. One part edit, one undo step. */
|
|
233
|
-
private declareHolding(asset:
|
|
233
|
+
private declareHolding(asset: FigureAsset) {
|
|
234
234
|
const names = [...new Set([...(asset.document.capabilities.names ?? []), 'Capacity', 'Holdable'])]
|
|
235
|
-
this.dispatchEvent(new CustomEvent('
|
|
235
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-capabilities', names } }, bubbles: true, composed: true }))
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
private renderDimensions(asset:
|
|
238
|
+
private renderDimensions(asset: FigureAsset) {
|
|
239
239
|
const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one))
|
|
240
240
|
const round = followableAxes(asset, this.partId).length === 2
|
|
241
241
|
/* One step for the whole part (figure-model follow-size): the size is declared from the parts if there is none. */
|
|
@@ -292,12 +292,12 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
292
292
|
private followPlus(axis: Axis, now: Follow, plus: number) {
|
|
293
293
|
if (now.of !== 'instance' && now.of !== 'part') return
|
|
294
294
|
if ((now.plus ?? 0) === plus) return
|
|
295
|
-
const source:
|
|
295
|
+
const source: FigureDimensionSource = now.of === 'instance' ? { of: 'instance', axis: now.axis } : { of: 'part', part: now.part, axis: now.axis }
|
|
296
296
|
const times = now.of === 'instance' ? now.times : (now.times ?? 1)
|
|
297
297
|
this.command({ kind: 'link-dimension', part: this.partId, axis, source, ...(times !== 1 ? { times } : {}), ...(plus ? { plus } : {}), replace: true })
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
private follow(asset:
|
|
300
|
+
private follow(asset: FigureAsset, axis: Axis, choice: string, now: Follow) {
|
|
301
301
|
const following = now.of !== 'own'
|
|
302
302
|
/* The measure as it stands: a dimension stops following at it, and a share is taken of it. */
|
|
303
303
|
const current = dimensionNow(asset, this.partId, axis) ?? this.dims?.[AXES.indexOf(axis)] ?? 100
|
|
@@ -315,7 +315,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
315
315
|
this.command({ kind: 'link-dimension', part: this.partId, axis, source: { of: 'instance', axis: a as Axis }, times, replace: following })
|
|
316
316
|
return
|
|
317
317
|
}
|
|
318
|
-
const source:
|
|
318
|
+
const source: FigureDimensionSource = of === 'instance' ? { of: 'instance', axis: a as Axis } : { of: 'part', part: a!, axis: b as Axis }
|
|
319
319
|
this.command({ kind: 'link-dimension', part: this.partId, axis, source, replace: following })
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -323,7 +323,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
323
323
|
* Positions worked out from other values, each with the way to
|
|
324
324
|
* keep it where it is. This is the way out the release gate names when a resize leaves a part in the air.
|
|
325
325
|
*/
|
|
326
|
-
private renderPositions(asset:
|
|
326
|
+
private renderPositions(asset: FigureAsset) {
|
|
327
327
|
return positionsWorkedOut(asset, this.partId).map(
|
|
328
328
|
axis => html`<div pair>
|
|
329
329
|
<span>${axis.toUpperCase()} · ${i18next.t(positionIsShare(asset, this.partId, axis) ? 'figure.label.position-in-proportion' : 'figure.label.position-worked-out')}</span>
|
|
@@ -343,14 +343,14 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
343
343
|
* A new aim for the fastening: where on the face is filled in as where the part stands now, so fastening it keeps it
|
|
344
344
|
* there (`placeKeptOf`). The author changes it from there.
|
|
345
345
|
*/
|
|
346
|
-
private aimAt(asset:
|
|
346
|
+
private aimAt(asset: FigureAsset, f: ReturnType<typeof freshFasten>) {
|
|
347
347
|
const theirFaces = f.target ? facesOf(asset, f.target) : FACES
|
|
348
348
|
const targetFace = theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0]!
|
|
349
349
|
const aim = f.toPlane ? ('plane' as const) : f.target ? { part: f.target, face: targetFace } : undefined
|
|
350
350
|
this.fasten = { ...f, ...(aim ? placeKeptOf(asset, this.partId, aim, !!f.twist) : { align: {}, alignMm: {} }) }
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
private renderFastening(asset:
|
|
353
|
+
private renderFastening(asset: FigureAsset) {
|
|
354
354
|
const held = heldBy(asset, this.partId)
|
|
355
355
|
const f = this.fasten
|
|
356
356
|
/* Not a part that hangs from this one: fastening a frame to its own roller would close a loop. */
|
|
@@ -359,8 +359,8 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
359
359
|
/* On the mounting plane only the faces it takes: a bottom or a top on the floor, a back on a wall. */
|
|
360
360
|
const plane = mountingPlaneOf(asset)
|
|
361
361
|
/* Its outer faces, and its centre -- a roller spins about its middle (ADR-0093); the mounting plane takes a face. */
|
|
362
|
-
const mineFaces: Array<
|
|
363
|
-
...(facesOf(asset, this.partId).filter(face => FACES.includes(face as
|
|
362
|
+
const mineFaces: Array<FigureFace | 'centre'> = [
|
|
363
|
+
...(facesOf(asset, this.partId).filter(face => FACES.includes(face as FigureFace) && (!f.toPlane || plane.faces.includes(face as FigureFace))) as FigureFace[]),
|
|
364
364
|
...(f.toPlane ? [] : (['centre'] as const))
|
|
365
365
|
]
|
|
366
366
|
const myFace = mineFaces.includes(f.face) ? f.face : mineFaces[0]!
|
|
@@ -379,7 +379,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
379
379
|
: nothing}
|
|
380
380
|
<div form>
|
|
381
381
|
<span>${i18next.t('figure.label.my-face')}</span>
|
|
382
|
-
<select @change=${(e: Event) => (this.fasten = { ...f, face: (e.target as HTMLSelectElement).value as
|
|
382
|
+
<select @change=${(e: Event) => (this.fasten = { ...f, face: (e.target as HTMLSelectElement).value as FigureFace | 'centre' })}>
|
|
383
383
|
${mineFaces.map(face => html`<option value=${face} ?selected=${face === myFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
|
|
384
384
|
</select>
|
|
385
385
|
</div>
|
|
@@ -396,7 +396,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
396
396
|
${targets.map(one => html`<option value=${one} ?selected=${one === f.target}>${one}</option>`)}
|
|
397
397
|
</select>
|
|
398
398
|
<span>${i18next.t('figure.label.target-face')}</span>
|
|
399
|
-
<select @change=${(e: Event) => this.aimAt(asset, { ...f, targetFace: (e.target as HTMLSelectElement).value as
|
|
399
|
+
<select @change=${(e: Event) => this.aimAt(asset, { ...f, targetFace: (e.target as HTMLSelectElement).value as FigureFace })}>
|
|
400
400
|
${theirFaces.map(face => html`<option value=${face} ?selected=${face === targetFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
|
|
401
401
|
</select>
|
|
402
402
|
<span>${i18next.t('figure.label.facing-meet')}</span>
|
|
@@ -426,7 +426,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
426
426
|
twist: f.twist,
|
|
427
427
|
...(align ? { align } : {}),
|
|
428
428
|
replace: !!held
|
|
429
|
-
} as
|
|
429
|
+
} as FigureAuthoringAction)}
|
|
430
430
|
>
|
|
431
431
|
<md-icon>link</md-icon>${i18next.t('figure.button.fasten')}
|
|
432
432
|
</button>
|
|
@@ -468,7 +468,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
468
468
|
* face, copies a pitch apart filling the parent's length, or a count spread over it. Once made, the row's own section
|
|
469
469
|
* (the editor's) shows it and takes it away.
|
|
470
470
|
*/
|
|
471
|
-
private renderRow(asset:
|
|
471
|
+
private renderRow(asset: FigureAsset) {
|
|
472
472
|
const axes = rowAxesOf(asset, this.partId)
|
|
473
473
|
if (!axes.length) return nothing
|
|
474
474
|
const r = this.row
|
|
@@ -497,7 +497,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
497
497
|
<button
|
|
498
498
|
act
|
|
499
499
|
?disabled=${this.locked || !ok}
|
|
500
|
-
@click=${() => this.command({ kind: 'make-repeat', part: this.partId, axis, pitch: r.pitch, ...(r.fills ? {} : { count: r.count }) } as
|
|
500
|
+
@click=${() => this.command({ kind: 'make-repeat', part: this.partId, axis, pitch: r.pitch, ...(r.fills ? {} : { count: r.count }) } as FigureAuthoringAction)}
|
|
501
501
|
>
|
|
502
502
|
<md-icon>view_week</md-icon>${i18next.t('figure.button.make-repeat')}
|
|
503
503
|
</button>
|
|
@@ -508,12 +508,12 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
508
508
|
* When the part is drawn: always, or while a control is at least a value -- a lamp lit while the gate is open. One
|
|
509
509
|
* part edit (`set-visibility`), one step. Offered once the figure has a control; a motion is what makes one.
|
|
510
510
|
*/
|
|
511
|
-
private renderShown(asset:
|
|
511
|
+
private renderShown(asset: FigureAsset) {
|
|
512
512
|
const controls = visibilityControlsOf(asset)
|
|
513
513
|
if (!controls.length) return nothing
|
|
514
514
|
const now = shownWhenOf(asset, this.partId)
|
|
515
515
|
const send = (shownWhen: { input: string; atLeast: number } | null) =>
|
|
516
|
-
this.dispatchEvent(new CustomEvent('
|
|
516
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit: { kind: 'set-visibility', target: this.partId, shownWhen } }, bubbles: true, composed: true }))
|
|
517
517
|
const first = (id: string) => {
|
|
518
518
|
const c = controls.find(one => one.id === id)!
|
|
519
519
|
return Math.round((c.min + (c.max - c.min) * 0.01) * 1000) / 1000
|
|
@@ -547,7 +547,7 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
547
547
|
</div>`
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
private renderMotion(asset:
|
|
550
|
+
private renderMotion(asset: FigureAsset) {
|
|
551
551
|
const now = motionOf(asset, this.partId)
|
|
552
552
|
const joint = asset.joints?.find(j => j.body1.part === this.partId)
|
|
553
553
|
if (now && !(this.editingMotion && joint)) {
|
|
@@ -663,8 +663,8 @@ export class FigureV3Links extends localize(i18next)(LitElement) {
|
|
|
663
663
|
this.motion = kind === 'slide' ? { ...this.motion, kind, min: 0, max: 500 } : { ...this.motion, kind, min: -90, max: 90 }
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
private command(action:
|
|
667
|
-
this.dispatchEvent(new CustomEvent('
|
|
666
|
+
private command(action: FigureAuthoringAction) {
|
|
667
|
+
this.dispatchEvent(new CustomEvent('command', { detail: { action }, bubbles: true, composed: true }))
|
|
668
668
|
}
|
|
669
669
|
}
|
|
670
670
|
|
|
@@ -2,9 +2,9 @@ import { css, html, LitElement, nothing, svg } from 'lit'
|
|
|
2
2
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
3
|
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
|
-
import { MATERIAL_PRESETS, SEGMENT_PRESETS,
|
|
5
|
+
import { MATERIAL_PRESETS, SEGMENT_PRESETS, figureTrianglesOf } from '@hatiolab/figure-model'
|
|
6
6
|
import { activePalette } from '@hatiolab/things-scene'
|
|
7
|
-
import type {
|
|
7
|
+
import type { FigureAssetGeometry, FigureMaterial, FigurePartEdit } from '@hatiolab/figure-model'
|
|
8
8
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
9
9
|
|
|
10
10
|
/** The purpose each palette role is for, by its token. */
|
|
@@ -27,20 +27,20 @@ const ROUND = new Set(['cylinder', 'ellipticCylinder', 'sphere', 'frustum'])
|
|
|
27
27
|
const DEFAULT_SEGMENTS = 12
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* 재질 — a
|
|
30
|
+
* 재질 — a part's material.
|
|
31
31
|
*
|
|
32
|
-
* An earlier editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The
|
|
32
|
+
* An earlier editor showed the colour role as a raw token (`palette.dark`) in a select and nothing else. The material
|
|
33
33
|
* holds the colour role, a preset, transparency and a glow; a round body also has how finely its rim is drawn. This
|
|
34
34
|
* shows those: painted colours and lit colours as two groups of swatches, each named for what it
|
|
35
35
|
* is for.
|
|
36
36
|
*
|
|
37
|
-
* Not here, because a
|
|
37
|
+
* Not here, because a material has no place for them: flat shading and casting
|
|
38
38
|
* shadows. The glow's colour comes from the lit colours.
|
|
39
39
|
*
|
|
40
|
-
* Every change leaves as one `
|
|
40
|
+
* Every change leaves as one `part-edit`; the editor runs it through the session, one step of the undo history.
|
|
41
41
|
*/
|
|
42
|
-
@customElement('figure-
|
|
43
|
-
export class
|
|
42
|
+
@customElement('figure-material-panel')
|
|
43
|
+
export class FigureMaterialPanel extends localize(i18next)(LitElement) {
|
|
44
44
|
static styles = [SidePanelStyles, css`
|
|
45
45
|
:host {
|
|
46
46
|
display: block;
|
|
@@ -154,7 +154,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
154
154
|
`]
|
|
155
155
|
|
|
156
156
|
/** The selected body as evaluated — its material, its segments and its shape. */
|
|
157
|
-
@property({ attribute: false }) body?:
|
|
157
|
+
@property({ attribute: false }) body?: FigureAssetGeometry & { id: string }
|
|
158
158
|
/** The asset's palette, token → colour. */
|
|
159
159
|
@property({ attribute: false }) palette: Record<string, string> = {}
|
|
160
160
|
@property({ type: Boolean }) locked = false
|
|
@@ -188,7 +188,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
188
188
|
<label>${i18next.t('figure.label.preset')}</label>
|
|
189
189
|
<select
|
|
190
190
|
?disabled=${this.locked}
|
|
191
|
-
@change=${(e: Event) => this.put({ preset: ((e.target as HTMLSelectElement).value || undefined) as
|
|
191
|
+
@change=${(e: Event) => this.put({ preset: ((e.target as HTMLSelectElement).value || undefined) as FigureMaterial['preset'] })}
|
|
192
192
|
>
|
|
193
193
|
<option value="" ?selected=${!material.preset}>${i18next.t('figure.label.none')}</option>
|
|
194
194
|
${MATERIAL_PRESETS.map(
|
|
@@ -229,7 +229,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
229
229
|
</section>`
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
private renderSegments(part:
|
|
232
|
+
private renderSegments(part: FigureAssetGeometry & { id: string }) {
|
|
233
233
|
const provider = (part as { shape?: { provider?: string } }).shape?.provider ?? ''
|
|
234
234
|
if (!ROUND.has(provider)) return nothing
|
|
235
235
|
const current = part.segments ?? DEFAULT_SEGMENTS
|
|
@@ -246,7 +246,7 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
246
246
|
>
|
|
247
247
|
${silhouette(preset)}
|
|
248
248
|
<b>${preset}</b>
|
|
249
|
-
<small>${i18next.t('figure.text.n-triangles', { count:
|
|
249
|
+
<small>${i18next.t('figure.text.n-triangles', { count: figureTrianglesOf({ ...part, segments: preset }) })}</small>
|
|
250
250
|
</button>`
|
|
251
251
|
)}
|
|
252
252
|
</div>
|
|
@@ -289,10 +289,10 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
289
289
|
this.put({ emissive: intensity > 0 ? { token: material.emissive?.token ?? material.token, intensity, on: true } : undefined }, live)
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
/** A material change: recorded, or -- while a slider is dragged -- only drawn (`
|
|
293
|
-
private put(patch: Partial<
|
|
294
|
-
const material:
|
|
295
|
-
for (const key of Object.keys(material) as (keyof
|
|
292
|
+
/** A material change: recorded, or -- while a slider is dragged -- only drawn (`part-live`). */
|
|
293
|
+
private put(patch: Partial<FigureMaterial>, live = false) {
|
|
294
|
+
const material: FigureMaterial = { ...this.body!.material, ...patch }
|
|
295
|
+
for (const key of Object.keys(material) as (keyof FigureMaterial)[]) if (material[key] === undefined) delete material[key]
|
|
296
296
|
/* The colours this material names that the asset does not carry yet. */
|
|
297
297
|
const offered = this.offered()
|
|
298
298
|
const palette = Object.fromEntries(
|
|
@@ -300,13 +300,13 @@ export class FigureV3Material extends localize(i18next)(LitElement) {
|
|
|
300
300
|
.filter((token): token is string => !!token && this.palette[token] === undefined && offered[token] !== undefined)
|
|
301
301
|
.map(token => [token, offered[token]!])
|
|
302
302
|
)
|
|
303
|
-
const edit:
|
|
304
|
-
if (live) this.dispatchEvent(new CustomEvent('
|
|
303
|
+
const edit: FigurePartEdit = { kind: 'set-material', target: this.body!.id, material, ...(Object.keys(palette).length ? { palette } : {}) }
|
|
304
|
+
if (live) this.dispatchEvent(new CustomEvent('part-live', { detail: { edit }, bubbles: true, composed: true }))
|
|
305
305
|
else this.edit(edit)
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
private edit(edit:
|
|
309
|
-
this.dispatchEvent(new CustomEvent('
|
|
308
|
+
private edit(edit: FigurePartEdit) {
|
|
309
|
+
this.dispatchEvent(new CustomEvent('part-edit', { detail: { edit }, bubbles: true, composed: true }))
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
312
|
|
|
@@ -4,23 +4,23 @@ import { customElement, property } from 'lit/decorators.js'
|
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
5
|
import { ScrollbarStyles } from '@operato/styles'
|
|
6
6
|
import { PART_LIMIT } from '@hatiolab/figure-model'
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import { partLabelOf } from './
|
|
7
|
+
import type { FigurePrimitiveCreation } from '@hatiolab/figure-model'
|
|
8
|
+
import type { FigureBudgetLine } from './maturity.js'
|
|
9
|
+
import { partLabelOf } from './fields.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* 도형 팔레트와 부품 목록 —
|
|
12
|
+
* 도형 팔레트와 부품 목록 — 자산의 것.
|
|
13
13
|
*
|
|
14
14
|
* 팔레트는 목록 위에, 눈 단추는 늘 자리를 차지하고, 조작은 고른 줄에서만 보인다.
|
|
15
15
|
*
|
|
16
|
-
* 부품의 도형은
|
|
16
|
+
* 부품의 도형은 평가가 준 `provider` 로 읽는다. 「숨김」은 여기서도 **작업대의 것**이다 — 저장되지 않고, 자산의 `visibility`(상태에 따라
|
|
17
17
|
* 보이고 안 보이는 것)와는 다른 것이다. 그것은 속성 패널의 몫이다.
|
|
18
18
|
*
|
|
19
19
|
* 이 컴포넌트는 정본을 만지지 않는다. 무엇을 하겠다는 것만 알린다.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
/** 한 줄 —
|
|
23
|
-
export interface
|
|
22
|
+
/** 한 줄 — 평가에서 이 패널이 읽는 것만. */
|
|
23
|
+
export interface FigurePartRow {
|
|
24
24
|
id: string
|
|
25
25
|
provider: string
|
|
26
26
|
/** 작업대에서 잠깐 가린 것. 저장되지 않는다. */
|
|
@@ -50,8 +50,8 @@ const PROVIDER_ICON: Record<string, string> = {
|
|
|
50
50
|
/*
|
|
51
51
|
The palette. A plate and a wall are boxes of a shape a person reaches for, so they are starting sizes, not kinds of their own.
|
|
52
52
|
*/
|
|
53
|
-
export type
|
|
54
|
-
const RECIPES: Array<{ kind:
|
|
53
|
+
export type FigurePaletteKind = FigurePrimitiveCreation['kind'] | 'plate' | 'wall'
|
|
54
|
+
const RECIPES: Array<{ kind: FigurePaletteKind; icon: string; label: string }> = [
|
|
55
55
|
{ kind: 'box', icon: 'deployed_code', label: 'figure.label.primitive-cube' },
|
|
56
56
|
{ kind: 'cylinder', icon: 'database', label: 'figure.label.primitive-cylinder' },
|
|
57
57
|
{ kind: 'rounded-box', icon: 'crop_square', label: 'figure.label.primitive-rounded-box' },
|
|
@@ -64,8 +64,8 @@ const RECIPES: Array<{ kind: V3PaletteKind; icon: string; label: string }> = [
|
|
|
64
64
|
{ kind: 'hollow-box', icon: 'inventory_2', label: 'figure.label.primitive-hollow-box' }
|
|
65
65
|
]
|
|
66
66
|
|
|
67
|
-
@customElement('figure-
|
|
68
|
-
export class
|
|
67
|
+
@customElement('figure-parts')
|
|
68
|
+
export class FigureParts extends localize(i18next)(LitElement) {
|
|
69
69
|
static styles = [
|
|
70
70
|
ScrollbarStyles,
|
|
71
71
|
css`
|
|
@@ -363,8 +363,8 @@ export class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
363
363
|
`
|
|
364
364
|
]
|
|
365
365
|
|
|
366
|
-
@property({ type: Array }) parts:
|
|
367
|
-
/** 고른 부품의 id. 자리(index)가 아니다 —
|
|
366
|
+
@property({ type: Array }) parts: FigurePartRow[] = []
|
|
367
|
+
/** 고른 부품의 id. 자리(index)가 아니다 — 부품은 id 로 안정하게 가리킨다. */
|
|
368
368
|
@property({ type: String }) selected = ''
|
|
369
369
|
/** 편집할 수 없을 때(불러오는 중 · 저장 중) 팔레트와 조작을 잠근다. */
|
|
370
370
|
@property({ type: Boolean }) locked = false
|
|
@@ -372,7 +372,7 @@ export class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
372
372
|
* 이 도형이 치르는 값 — 삼각형 · 묶음 · 한 화면에 몇 대 · 등급. 부품을 더하는 바로 그 자리에서 보여야 로우폴리의
|
|
373
373
|
* 기준이 저작을 이끈다. 등급이 D 면 무엇이 넘쳤는지를 적는다. 누르면 성숙도 탭이 열린다.
|
|
374
374
|
*/
|
|
375
|
-
@property({ attribute: false }) budget?:
|
|
375
|
+
@property({ attribute: false }) budget?: FigureBudgetLine
|
|
376
376
|
/**
|
|
377
377
|
* 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가
|
|
378
378
|
* 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.
|
|
@@ -502,7 +502,7 @@ export class FigureV3Parts extends localize(i18next)(LitElement) {
|
|
|
502
502
|
`
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
-
private renderRow(part:
|
|
505
|
+
private renderRow(part: FigurePartRow, index: number) {
|
|
506
506
|
const selected = part.id === this.selected
|
|
507
507
|
const off = !!part.hidden
|
|
508
508
|
return html`
|