@things-factory/figure-ui 10.1.14 → 10.1.16
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/modeller/figure-ai-target.ts +12 -0
- package/client/modeller/figure-canvas.ts +18 -2
- package/client/modeller/figure-inspector.ts +48 -7
- package/client/modeller/figure-side.ts +1 -0
- package/client/modeller/figure-source.test.ts +2 -0
- package/client/modeller/figure-source.ts +25 -3
- package/client/pages/figure-modeller-page.ts +364 -49
- package/dist-client/modeller/figure-ai-target.d.ts +10 -0
- package/dist-client/modeller/figure-ai-target.js +10 -0
- package/dist-client/modeller/figure-ai-target.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +1 -0
- package/dist-client/modeller/figure-canvas.js +21 -2
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +4 -1
- package/dist-client/modeller/figure-inspector.js +47 -6
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-side.js +1 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +10 -3
- package/dist-client/modeller/figure-source.js +5 -0
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +48 -1
- package/dist-client/pages/figure-modeller-page.js +376 -52
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/ai-proposal-contract.test.ts +3 -3
- package/translations/en.json +10 -1
- package/translations/ko.json +11 -1
|
@@ -9,3 +9,15 @@ export function takeFigureAITarget() {
|
|
|
9
9
|
pending = undefined
|
|
10
10
|
return target
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
let pendingStage: { figureId?: string; source?: any; proposal?: any } | undefined
|
|
14
|
+
export function stageFigureProposal(data: { figureId?: string; source?: any; proposal?: any }) {
|
|
15
|
+
pendingStage = structuredClone(data)
|
|
16
|
+
window.dispatchEvent(new CustomEvent('figure-ai-stage', { detail: data }))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function consumePendingFigureProposal() {
|
|
20
|
+
const target = pendingStage
|
|
21
|
+
pendingStage = undefined
|
|
22
|
+
return target
|
|
23
|
+
}
|
|
@@ -8,6 +8,19 @@ import { gridStep } from './part-edits.js'
|
|
|
8
8
|
import { DEFAULT_VIEW, WORKBENCH } from './figure-view.js'
|
|
9
9
|
import type { ViewSettings } from './figure-view.js'
|
|
10
10
|
import type { BoardModel, PartModel } from './figure-source.js'
|
|
11
|
+
import type { FigurePlacement } from '@hatiolab/figure-model'
|
|
12
|
+
|
|
13
|
+
function toScenePlacement(placement?: FigurePlacement): 'floor' | 'inverted' | 'space' {
|
|
14
|
+
switch (placement) {
|
|
15
|
+
case 'ceiling':
|
|
16
|
+
return 'inverted'
|
|
17
|
+
case 'center':
|
|
18
|
+
return 'space'
|
|
19
|
+
case 'floor':
|
|
20
|
+
default:
|
|
21
|
+
return 'floor'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
/**
|
|
13
26
|
* 판의 종이 색 — 거의 흰색.
|
|
@@ -188,6 +201,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
188
201
|
/** 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다(`figure-view.ts`). */
|
|
189
202
|
@property({ type: Object }) view: ViewSettings = DEFAULT_VIEW
|
|
190
203
|
@property({ type: Array }) parts: PartModel[] = []
|
|
204
|
+
@property({ type: Number }) revision = 0
|
|
191
205
|
/** 지금 고른 부품 이름. 목록에서 고른 것을 캔버스에 알린다. */
|
|
192
206
|
@property({ type: String }) picked?: string
|
|
193
207
|
|
|
@@ -275,7 +289,8 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
275
289
|
willUpdate(changed: Map<string, unknown>) {
|
|
276
290
|
if (!this.hasUpdated) return
|
|
277
291
|
|
|
278
|
-
if (changed.has('
|
|
292
|
+
if (changed.has('revision')) this.standing = undefined
|
|
293
|
+
if (changed.has('board') || changed.has('parts') || changed.has('figureId') || changed.has('revision')) this.standUp()
|
|
279
294
|
if (changed.has('view')) this.applyView()
|
|
280
295
|
if (changed.has('picked')) this.showPicked()
|
|
281
296
|
}
|
|
@@ -317,7 +332,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
317
332
|
return
|
|
318
333
|
}
|
|
319
334
|
|
|
320
|
-
const signature = `${this.figureId ?? ''}:${board.width}x${board.height}x${board.depth}`
|
|
335
|
+
const signature = `${this.figureId ?? ''}:${board.width}x${board.height}x${board.depth}:${board.placement ?? 'floor'}`
|
|
321
336
|
if (this.scene && this.standing === signature) {
|
|
322
337
|
this.syncParts()
|
|
323
338
|
return
|
|
@@ -333,6 +348,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
333
348
|
width: board.width,
|
|
334
349
|
height: board.height,
|
|
335
350
|
depth: board.depth,
|
|
351
|
+
placement: toScenePlacement(board.placement),
|
|
336
352
|
threed: true,
|
|
337
353
|
environment: 'studio',
|
|
338
354
|
/*
|
|
@@ -13,10 +13,11 @@ import {
|
|
|
13
13
|
SEGMENTED_PRIMITIVES,
|
|
14
14
|
SEGMENT_PRESETS,
|
|
15
15
|
SIZING_RULES,
|
|
16
|
+
FIGURE_PLACEMENTS,
|
|
16
17
|
trianglesOf
|
|
17
18
|
} from '@hatiolab/figure-model'
|
|
18
19
|
import { ANCHOR_RULES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model'
|
|
19
|
-
import type { AnchorRule, Axis, FigurePart, MaterialPreset, PartRole, PrimitiveKind } from '@hatiolab/figure-model'
|
|
20
|
+
import type { AnchorRule, Axis, FigurePart, FigurePlacement, MaterialPreset, PartRole, PrimitiveKind } from '@hatiolab/figure-model'
|
|
20
21
|
import { activePalette } from '@hatiolab/things-scene'
|
|
21
22
|
|
|
22
23
|
import { partToFigure, partFromFigure } from './figure-source.js'
|
|
@@ -774,7 +775,8 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
774
775
|
`
|
|
775
776
|
]
|
|
776
777
|
|
|
777
|
-
/**
|
|
778
|
+
/** 도형의 DB 식별자. 비어 있으면 미저장 신규 모델이다. */
|
|
779
|
+
@property({ type: String }) figureId = ''
|
|
778
780
|
/** 도형의 표시 이름. 정본은 저작면이 들고, 여기서는 보여 주고 바꾼다(`figure-rename` 으로 알린다). */
|
|
779
781
|
@property({ type: String }) figureName = ''
|
|
780
782
|
/** 분류 하나 — 팔레트에서 묶는 기준. */
|
|
@@ -962,17 +964,49 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
962
964
|
/>
|
|
963
965
|
</div>
|
|
964
966
|
<div inline-field>
|
|
965
|
-
<label>${i18next.t('figure.label.figure-type')}${this.helpButton('figure.text.type-name-cannot-change')}</label>
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
967
|
+
<label>${i18next.t('figure.label.figure-type')}${this.figureId ? this.helpButton('figure.text.type-name-cannot-change') : nothing}</label>
|
|
968
|
+
${!this.figureId
|
|
969
|
+
? html`<input
|
|
970
|
+
.value=${this.board?.figureType ?? ''}
|
|
971
|
+
placeholder=${i18next.t('figure.text.figure-type-placeholder', { defaultValue: '도형 식별 타입 (예: OHT)' })}
|
|
972
|
+
@change=${(e: Event) =>
|
|
973
|
+
this.dispatchEvent(
|
|
974
|
+
new CustomEvent('figure-type-change', {
|
|
975
|
+
detail: { type: (e.target as HTMLInputElement).value.trim().toUpperCase() },
|
|
976
|
+
bubbles: true,
|
|
977
|
+
composed: true
|
|
978
|
+
})
|
|
979
|
+
)}
|
|
980
|
+
/>`
|
|
981
|
+
: html`<code fixed title=${i18next.t('figure.text.type-name-cannot-change')}>
|
|
982
|
+
<md-icon>lock</md-icon>${this.board?.figureType ?? ''}
|
|
983
|
+
</code>
|
|
984
|
+
${this.helpHint('figure.text.type-name-cannot-change')}`}
|
|
970
985
|
</div>
|
|
971
986
|
<!--
|
|
972
987
|
왜 못 고치는지 **적어 둔다.** 전에는 tooltip 에만 있었고, 그러면 눌러 보고 안 되는 것을
|
|
973
988
|
알게 된다 — 화면이 안 되는 이유를 말하지 않으면 사용자가 자기 실수라고 생각한다.
|
|
974
989
|
-->
|
|
975
990
|
|
|
991
|
+
<div inline-field>
|
|
992
|
+
<label>${i18next.t('figure.label.figure-placement')}${this.helpButton('figure.text.placement-explained')}</label>
|
|
993
|
+
<select
|
|
994
|
+
.value=${this.board?.placement ?? 'floor'}
|
|
995
|
+
@change=${(e: Event) => this.setPlacement((e.target as HTMLSelectElement).value as FigurePlacement)}
|
|
996
|
+
>
|
|
997
|
+
<option value="floor" ?selected=${(this.board?.placement ?? 'floor') === 'floor'}>
|
|
998
|
+
${i18next.t('figure.label.placement-floor')}
|
|
999
|
+
</option>
|
|
1000
|
+
<option value="ceiling" ?selected=${this.board?.placement === 'ceiling'}>
|
|
1001
|
+
${i18next.t('figure.label.placement-ceiling')}
|
|
1002
|
+
</option>
|
|
1003
|
+
<option value="center" ?selected=${this.board?.placement === 'center'}>
|
|
1004
|
+
${i18next.t('figure.label.placement-center')}
|
|
1005
|
+
</option>
|
|
1006
|
+
</select>
|
|
1007
|
+
${this.helpHint('figure.text.placement-explained')}
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
976
1010
|
<div inline-field>
|
|
977
1011
|
<label>${i18next.t('figure.label.category')}${this.helpButton('figure.text.category-explained')}</label>
|
|
978
1012
|
<input
|
|
@@ -1041,6 +1075,13 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1041
1075
|
this.dispatchEvent(new CustomEvent('figure-catalog', { detail: patch, bubbles: true, composed: true }))
|
|
1042
1076
|
}
|
|
1043
1077
|
|
|
1078
|
+
/** 3D 배치 기준면을 바꾼다 ('floor' | 'ceiling' | 'center'). */
|
|
1079
|
+
private setPlacement(placement: FigurePlacement) {
|
|
1080
|
+
if (!this.board) return
|
|
1081
|
+
const board = { ...this.board, placement }
|
|
1082
|
+
this.dispatchEvent(new CustomEvent('update-board', { detail: { board }, bubbles: true, composed: true }))
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1044
1085
|
private renderIdentity(part: FigurePart) {
|
|
1045
1086
|
return html`
|
|
1046
1087
|
<section>
|
|
@@ -192,6 +192,7 @@ export class FigureSide extends localize(i18next)(LitElement) {
|
|
|
192
192
|
|
|
193
193
|
<figure-inspector
|
|
194
194
|
?off=${this.tab !== 'properties'}
|
|
195
|
+
.figureId=${this.figureId}
|
|
195
196
|
.figureName=${this.figureName}
|
|
196
197
|
.figureCategory=${this.figureCategory}
|
|
197
198
|
.figureTags=${this.figureTags}
|
|
@@ -145,8 +145,10 @@ describe('펴기 — 형식을 씬 모델로', () => {
|
|
|
145
145
|
*/
|
|
146
146
|
/** 형식이 가진 것을 되도록 다 담은 정본. 안 쓰이는 필드도 넣는다. */
|
|
147
147
|
const FULL: FigureSource = {
|
|
148
|
+
version: 1,
|
|
148
149
|
type: 'AGV',
|
|
149
150
|
base: { x: 3000, y: 800, z: 2000 },
|
|
151
|
+
placement: 'floor',
|
|
150
152
|
detailLevel: 'L',
|
|
151
153
|
styleKit: 'factory',
|
|
152
154
|
parts: [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { FIGURE_SOURCE_VERSION, type DetailLevel, type FigurePart, type FigurePlacement, type FigureSource, type Vec3 } from '@hatiolab/figure-model'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Scene model <-> `FigureSource`.
|
|
@@ -72,14 +72,27 @@ import type { DetailLevel, FigurePart, FigureSource, Vec3 } from '@hatiolab/figu
|
|
|
72
72
|
* under `shape` costs a line here, and forgetting that line was the fourth
|
|
73
73
|
* of the four.
|
|
74
74
|
*/
|
|
75
|
-
/**
|
|
75
|
+
/** 판 컴포넌트의 상태. 씬의 용어 그대로다. */
|
|
76
76
|
export interface BoardModel {
|
|
77
|
-
/**
|
|
77
|
+
/** 형식 버전. */
|
|
78
|
+
version?: number
|
|
79
|
+
/*
|
|
80
|
+
The three sizes keep the scene component's names, and the scene assigns its axes differently
|
|
81
|
+
from the format: here `depth` is the vertical one. Naming the axis alone ("the y axis") does
|
|
82
|
+
not say which of the two conventions is meant, and this interface sits on the boundary between
|
|
83
|
+
them — so each field names the `FigureSource` field it becomes. `toFigureSource` and
|
|
84
|
+
`fromFigureSource` below are the only places that may disagree with these three lines.
|
|
85
|
+
*/
|
|
86
|
+
/** 가로 너비 (mm) → `base.x`. */
|
|
78
87
|
width: number
|
|
88
|
+
/** 앞뒤 깊이 (mm) → `base.z`. 저작자가 위에서 내려다볼 때의 세로다. */
|
|
79
89
|
height: number
|
|
90
|
+
/** 높이 (mm) → `base.y`. 서 있는 방향이다. */
|
|
80
91
|
depth: number
|
|
81
92
|
/** 컴포넌트 타입 이름. **저장되는 식별자다.** */
|
|
82
93
|
figureType?: string
|
|
94
|
+
/** 3D 배치 기준면 ('floor' | 'ceiling' | 'center'). */
|
|
95
|
+
placement?: FigurePlacement
|
|
83
96
|
detailLevel?: DetailLevel
|
|
84
97
|
styleKit?: string
|
|
85
98
|
/**
|
|
@@ -137,7 +150,12 @@ export interface PartModel {
|
|
|
137
150
|
*/
|
|
138
151
|
hidden?: boolean
|
|
139
152
|
|
|
153
|
+
/** 색 토큰 이름 (`palette.primary` 등). */
|
|
140
154
|
token?: string
|
|
155
|
+
/*
|
|
156
|
+
`FigurePart['material']` is required in the format, so these index into it directly. The
|
|
157
|
+
`NonNullable` on `shape` above is not the same case — `shape` is optional there.
|
|
158
|
+
*/
|
|
141
159
|
preset?: FigurePart['material']['preset']
|
|
142
160
|
flatShading?: boolean
|
|
143
161
|
transparent?: boolean
|
|
@@ -228,8 +246,10 @@ export function toFigureSource(board: BoardModel, parts: PartModel[]): FigureSou
|
|
|
228
246
|
const half = halfOf(board)
|
|
229
247
|
|
|
230
248
|
return withoutEmpty({
|
|
249
|
+
version: board.version ?? FIGURE_SOURCE_VERSION,
|
|
231
250
|
type: board.figureType ?? '',
|
|
232
251
|
base: { x: board.width, y: board.depth, z: board.height },
|
|
252
|
+
placement: board.placement,
|
|
233
253
|
detailLevel: board.detailLevel,
|
|
234
254
|
styleKit: board.styleKit,
|
|
235
255
|
parts: parts.map(part => partTo(part, half)),
|
|
@@ -292,10 +312,12 @@ export function fromFigureSource(source: FigureSource): { board: BoardModel; par
|
|
|
292
312
|
const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }
|
|
293
313
|
|
|
294
314
|
const board = withoutEmpty({
|
|
315
|
+
version: source.version,
|
|
295
316
|
width: source.base.x,
|
|
296
317
|
height: source.base.z,
|
|
297
318
|
depth: source.base.y,
|
|
298
319
|
figureType: source.type,
|
|
320
|
+
placement: source.placement,
|
|
299
321
|
detailLevel: source.detailLevel,
|
|
300
322
|
styleKit: source.styleKit,
|
|
301
323
|
animations: source.animations,
|