@things-factory/figure-ui 10.1.14 → 10.1.15
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 +17 -21
- package/client/pages/figure-modeller-page.ts +323 -46
- 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 +15 -21
- 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 +11 -0
- package/dist-client/pages/figure-modeller-page.js +337 -49
- package/dist-client/pages/figure-modeller-page.js.map +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`.
|
|
@@ -62,24 +62,21 @@ import type { DetailLevel, FigurePart, FigureSource, Vec3 } from '@hatiolab/figu
|
|
|
62
62
|
* is the guard below actually earning its keep. The third showed up as a conveyor motor that stayed on the
|
|
63
63
|
* floor no matter what the sample said; the fourth as a hollowed tray that
|
|
64
64
|
* came out a solid block.
|
|
65
|
-
*
|
|
66
|
-
* All four passed the round-trip test, because the fixture it uses did not
|
|
67
|
-
* carry the field. So the test now reads the **field list the format declares**
|
|
68
|
-
* rather than trusting the fixture.
|
|
69
|
-
*
|
|
70
|
-
* `shape` is unpacked into `round`, `path` and `hollow` rather than carried
|
|
71
|
-
* whole, because the editor holds those as separate state. Every field added
|
|
72
|
-
* under `shape` costs a line here, and forgetting that line was the fourth
|
|
73
|
-
* of the four.
|
|
74
65
|
*/
|
|
75
|
-
/**
|
|
66
|
+
/** 판 컴포넌트의 상태. 씬의 용어 그대로다. */
|
|
76
67
|
export interface BoardModel {
|
|
77
|
-
/**
|
|
68
|
+
/** 형식 버전. */
|
|
69
|
+
version?: number
|
|
70
|
+
/** 가로 너비 (mm). 씬의 x 축. */
|
|
78
71
|
width: number
|
|
72
|
+
/** 세로 깊이 (mm). 씬의 y 축 (평면에서는 세로, 저작자가 위에서 내려다보는 세로). */
|
|
79
73
|
height: number
|
|
74
|
+
/** 높이 (mm). 씬의 z 축. */
|
|
80
75
|
depth: number
|
|
81
76
|
/** 컴포넌트 타입 이름. **저장되는 식별자다.** */
|
|
82
77
|
figureType?: string
|
|
78
|
+
/** 3D 배치 기준면 ('floor' | 'ceiling' | 'center'). */
|
|
79
|
+
placement?: FigurePlacement
|
|
83
80
|
detailLevel?: DetailLevel
|
|
84
81
|
styleKit?: string
|
|
85
82
|
/**
|
|
@@ -137,18 +134,13 @@ export interface PartModel {
|
|
|
137
134
|
*/
|
|
138
135
|
hidden?: boolean
|
|
139
136
|
|
|
137
|
+
/** 색 토큰 이름 (`palette.primary` 등). */
|
|
140
138
|
token?: string
|
|
141
|
-
preset?: FigurePart['material']
|
|
139
|
+
preset?: FigurePart['material'] extends infer M ? (M extends { preset?: infer P } ? P : never) : never
|
|
142
140
|
flatShading?: boolean
|
|
143
141
|
transparent?: boolean
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
* 바깥의 `token` 이 **꺼진 모습**이고 이 안의 `token` 이 **켜진 모습**이다 — 꺼진
|
|
148
|
-
* 전구는 빨갛지 않고 어두운 플라스틱이다.
|
|
149
|
-
*/
|
|
150
|
-
emissive?: FigurePart['material']['emissive']
|
|
151
|
-
surface?: FigurePart['material']['surface']
|
|
142
|
+
emissive?: NonNullable<FigurePart['material']>['emissive']
|
|
143
|
+
surface?: NonNullable<FigurePart['material']>['surface']
|
|
152
144
|
|
|
153
145
|
/* 아직 아무 일도 안 하지만 잃어버리지 않는다. */
|
|
154
146
|
materialSlot?: FigurePart['materialSlot']
|
|
@@ -228,8 +220,10 @@ export function toFigureSource(board: BoardModel, parts: PartModel[]): FigureSou
|
|
|
228
220
|
const half = halfOf(board)
|
|
229
221
|
|
|
230
222
|
return withoutEmpty({
|
|
223
|
+
version: board.version ?? FIGURE_SOURCE_VERSION,
|
|
231
224
|
type: board.figureType ?? '',
|
|
232
225
|
base: { x: board.width, y: board.depth, z: board.height },
|
|
226
|
+
placement: board.placement,
|
|
233
227
|
detailLevel: board.detailLevel,
|
|
234
228
|
styleKit: board.styleKit,
|
|
235
229
|
parts: parts.map(part => partTo(part, half)),
|
|
@@ -292,10 +286,12 @@ export function fromFigureSource(source: FigureSource): { board: BoardModel; par
|
|
|
292
286
|
const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }
|
|
293
287
|
|
|
294
288
|
const board = withoutEmpty({
|
|
289
|
+
version: source.version,
|
|
295
290
|
width: source.base.x,
|
|
296
291
|
height: source.base.z,
|
|
297
292
|
depth: source.base.y,
|
|
298
293
|
figureType: source.type,
|
|
294
|
+
placement: source.placement,
|
|
299
295
|
detailLevel: source.detailLevel,
|
|
300
296
|
styleKit: source.styleKit,
|
|
301
297
|
animations: source.animations,
|