@things-factory/figure-ui 10.1.16 → 10.1.17
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-animations.ts +12 -12
- package/client/modeller/figure-canvas.ts +32 -48
- package/client/modeller/figure-capabilities.ts +10 -10
- package/client/modeller/figure-inspector.ts +39 -28
- package/client/modeller/figure-parts.ts +1 -1
- package/client/modeller/figure-preview.ts +122 -25
- package/client/modeller/figure-report.ts +24 -6
- package/client/modeller/figure-settings.ts +45 -2
- package/client/modeller/figure-side.ts +6 -6
- package/client/modeller/figure-source.ts +52 -32
- package/client/modeller/figure-thumbnail.ts +5 -5
- package/client/modeller/figure-view.ts +53 -0
- package/client/modeller/part-edits.ts +36 -36
- package/client/modeller/proposal.ts +25 -4
- package/client/pages/figure-modeller-page.ts +64 -58
- package/dist-client/modeller/figure-animations.d.ts +4 -4
- package/dist-client/modeller/figure-animations.js +10 -10
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +7 -7
- package/dist-client/modeller/figure-canvas.js +27 -40
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +2 -2
- package/dist-client/modeller/figure-capabilities.js +9 -9
- package/dist-client/modeller/figure-capabilities.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +16 -5
- package/dist-client/modeller/figure-inspector.js +38 -27
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +1 -1
- package/dist-client/modeller/figure-parts.js +1 -1
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +39 -0
- package/dist-client/modeller/figure-preview.js +112 -23
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +9 -0
- package/dist-client/modeller/figure-report.js +23 -5
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +42 -2
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +2 -2
- package/dist-client/modeller/figure-side.js +5 -5
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +17 -15
- package/dist-client/modeller/figure-source.js +40 -21
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
- package/dist-client/modeller/figure-thumbnail.js +3 -3
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +45 -0
- package/dist-client/modeller/figure-view.js +27 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/part-edits.d.ts +16 -16
- package/dist-client/modeller/part-edits.js +35 -35
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +15 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +9 -10
- package/dist-client/pages/figure-modeller-page.js +60 -54
- 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 +4 -4
- package/test/ai-proposal-contract.test.ts +393 -12
- package/test/expect.ts +55 -0
- package/test/figure-source.test.ts +562 -0
- package/test/i18n-prefix-guard.test.ts +1 -1
- package/{client/modeller → test}/part-edits.test.ts +50 -45
- package/translations/en.json +31 -28
- package/translations/ja.json +16 -21
- package/translations/ko.json +41 -38
- package/translations/ms.json +16 -21
- package/translations/zh.json +16 -21
- package/client/modeller/figure-ask.ts +0 -265
- package/client/modeller/figure-source.test.ts +0 -352
- package/dist-client/modeller/figure-ask.d.ts +0 -40
- package/dist-client/modeller/figure-ask.js +0 -282
- package/dist-client/modeller/figure-ask.js.map +0 -1
|
@@ -8,7 +8,7 @@ import { ScrollbarStyles } from '@operato/styles'
|
|
|
8
8
|
import { AXES, CHANNEL_PATHS, CLIP_DRIVES, INTERPOLATIONS, LIMITS } from '@hatiolab/figure-model'
|
|
9
9
|
import type { ChannelPath, Vec3 } from '@hatiolab/figure-model'
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { FigureDraft, PartModel } from './figure-source.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* 이 도형이 **어떻게 움직이는가.**
|
|
@@ -35,7 +35,7 @@ import type { BoardModel, PartModel } from './figure-source.js'
|
|
|
35
35
|
* 받는다. 이 선택이 화면에서 가장 설명이 필요한 자리다.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
type Clip = NonNullable<
|
|
38
|
+
type Clip = NonNullable<FigureDraft['animations']>[number]
|
|
39
39
|
type Channel = Clip['channels'][number]
|
|
40
40
|
type Keyframe = Channel['keys'][number]
|
|
41
41
|
|
|
@@ -286,7 +286,7 @@ export class FigureAnimations extends localize(i18next)(LitElement) {
|
|
|
286
286
|
`
|
|
287
287
|
]
|
|
288
288
|
|
|
289
|
-
@property({ type: Object })
|
|
289
|
+
@property({ type: Object }) draft?: FigureDraft
|
|
290
290
|
@property({ type: Array }) parts: PartModel[] = []
|
|
291
291
|
|
|
292
292
|
/** 펼쳐 둔 clip. 이름이 아니라 자리로 잡는다 — 이름은 편집 중에 바뀐다. */
|
|
@@ -294,7 +294,7 @@ export class FigureAnimations extends localize(i18next)(LitElement) {
|
|
|
294
294
|
@state() private recipeTarget = ''
|
|
295
295
|
|
|
296
296
|
private get clips(): Clip[] {
|
|
297
|
-
return this.
|
|
297
|
+
return this.draft?.animations ?? []
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/** 부품 이름 — 채널이 이것을 가리킨다. */
|
|
@@ -315,7 +315,7 @@ export class FigureAnimations extends localize(i18next)(LitElement) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
render(): TemplateResult {
|
|
318
|
-
if (!this.
|
|
318
|
+
if (!this.draft) return html``
|
|
319
319
|
|
|
320
320
|
return html`
|
|
321
321
|
<section>
|
|
@@ -626,19 +626,19 @@ export class FigureAnimations extends localize(i18next)(LitElement) {
|
|
|
626
626
|
// ─── 고치기 ───────────────────────────────────────────────────────────────
|
|
627
627
|
|
|
628
628
|
/**
|
|
629
|
-
*
|
|
629
|
+
* 초안에 실어 올려보낸다.
|
|
630
630
|
*
|
|
631
|
-
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-
|
|
631
|
+
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
|
|
632
632
|
* 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
|
|
633
633
|
*/
|
|
634
634
|
private tell(animations: Clip[]): void {
|
|
635
|
-
if (!this.
|
|
635
|
+
if (!this.draft) return
|
|
636
636
|
|
|
637
|
-
const
|
|
638
|
-
if (animations.length === 0) delete
|
|
639
|
-
else
|
|
637
|
+
const draft: FigureDraft = { ...this.draft }
|
|
638
|
+
if (animations.length === 0) delete draft.animations
|
|
639
|
+
else draft.animations = animations
|
|
640
640
|
|
|
641
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
641
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))
|
|
642
642
|
}
|
|
643
643
|
|
|
644
644
|
private patchClip(at: number, patch: Partial<Clip>): void {
|
|
@@ -5,22 +5,9 @@ import { i18next, localize } from '@operato/i18n'
|
|
|
5
5
|
import { create, Model } from '@hatiolab/things-scene'
|
|
6
6
|
|
|
7
7
|
import { gridStep } from './part-edits.js'
|
|
8
|
-
import { DEFAULT_VIEW, WORKBENCH } from './figure-view.js'
|
|
8
|
+
import { DEFAULT_VIEW, sceneLook, WORKBENCH } from './figure-view.js'
|
|
9
9
|
import type { ViewSettings } from './figure-view.js'
|
|
10
|
-
import type {
|
|
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
|
-
}
|
|
10
|
+
import type { FigureDraft, PartModel } from './figure-source.js'
|
|
24
11
|
|
|
25
12
|
/**
|
|
26
13
|
* 판의 종이 색 — 거의 흰색.
|
|
@@ -70,10 +57,10 @@ interface SceneHandle {
|
|
|
70
57
|
* 있다. 부품은 판에 붙고, 선택 알림은 루트가 낸다. 둘을 섞으면 알림이 안 온다 —
|
|
71
58
|
* 실제로 그랬다.
|
|
72
59
|
*/
|
|
73
|
-
root?:
|
|
60
|
+
root?: SceneLayer
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
interface
|
|
63
|
+
interface SceneLayer {
|
|
77
64
|
invalidate?: () => void
|
|
78
65
|
/** 보기 설정을 넣는 자리. 씬이 스스로 반응한다. */
|
|
79
66
|
set?: (patch: Record<string, unknown>) => void
|
|
@@ -101,7 +88,7 @@ interface SceneComponent {
|
|
|
101
88
|
}
|
|
102
89
|
|
|
103
90
|
/**
|
|
104
|
-
* 편집 캔버스 — 부품들을
|
|
91
|
+
* 편집 캔버스 — 부품들을 기준 상자 위에 세우고, 여기서 고치게 한다.
|
|
105
92
|
*
|
|
106
93
|
* ## 미리보기가 아니다
|
|
107
94
|
*
|
|
@@ -119,10 +106,10 @@ interface SceneComponent {
|
|
|
119
106
|
* 이제 부품 하나가 컴포넌트 하나다. 선택 · 기즈모 · 윤곽선 · 다중 선택 · 되돌리기가
|
|
120
107
|
* 씬의 기본 동작으로 그냥 된다.
|
|
121
108
|
*
|
|
122
|
-
* ##
|
|
109
|
+
* ## 초안이 곧 기준 상자다
|
|
123
110
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
111
|
+
* 초안의 `width` · `height` · `depth` 가 `FigureSource.base` 다. 부품 좌표를 변환할 때
|
|
112
|
+
* 상자 중심만 빼면 된다 — `figure-source.ts` 가 그 일을 한다.
|
|
126
113
|
*
|
|
127
114
|
* ## 다시 세우지 않는다
|
|
128
115
|
*
|
|
@@ -194,10 +181,10 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
194
181
|
*
|
|
195
182
|
* 한동안 `FigureSource` 로 받아 여기서 변환했다. 그런데 모델러 페이지가 갖고 있는 것이
|
|
196
183
|
* 이미 씬 모델이므로, 그러면 값을 하나 고칠 때마다 왕복 변환이 한 번씩 일어난다.
|
|
197
|
-
* 무손실이긴 하지만 하는 일이 없다 —
|
|
184
|
+
* 무손실이긴 하지만 하는 일이 없다 — 초안이 곧 판이고 부품이 곧 컴포넌트다.
|
|
198
185
|
*/
|
|
199
186
|
@property({ type: String }) figureId?: string
|
|
200
|
-
@property({ type: Object })
|
|
187
|
+
@property({ type: Object }) draft?: FigureDraft
|
|
201
188
|
/** 보기 설정. 자산이 아니라 작업대다 — 저장되지 않는다(`figure-view.ts`). */
|
|
202
189
|
@property({ type: Object }) view: ViewSettings = DEFAULT_VIEW
|
|
203
190
|
@property({ type: Array }) parts: PartModel[] = []
|
|
@@ -246,7 +233,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
246
233
|
아무것도 보여 주지 않고, 저장할 때마다 판 번호만 올라간다 — 없는 것보다 나쁘다.
|
|
247
234
|
*/
|
|
248
235
|
const layer = this.scene?.root as
|
|
249
|
-
| (
|
|
236
|
+
| (SceneLayer & {
|
|
250
237
|
snapshot3d?: (
|
|
251
238
|
width?: number,
|
|
252
239
|
height?: number,
|
|
@@ -290,7 +277,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
290
277
|
if (!this.hasUpdated) return
|
|
291
278
|
|
|
292
279
|
if (changed.has('revision')) this.standing = undefined
|
|
293
|
-
if (changed.has('
|
|
280
|
+
if (changed.has('draft') || changed.has('parts') || changed.has('figureId') || changed.has('revision')) this.standUp()
|
|
294
281
|
if (changed.has('view')) this.applyView()
|
|
295
282
|
if (changed.has('picked')) this.showPicked()
|
|
296
283
|
}
|
|
@@ -318,8 +305,8 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
318
305
|
* 숫자 하나 고칠 때마다 화면이 처음으로 돌아간다.
|
|
319
306
|
*/
|
|
320
307
|
private standUp() {
|
|
321
|
-
const
|
|
322
|
-
if (!
|
|
308
|
+
const draft = this.draft
|
|
309
|
+
if (!draft) {
|
|
323
310
|
this.teardown()
|
|
324
311
|
this.cannot = i18next.t('figure.text.canvas-appears-once-a-part-exists')
|
|
325
312
|
return
|
|
@@ -332,7 +319,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
332
319
|
return
|
|
333
320
|
}
|
|
334
321
|
|
|
335
|
-
const signature = `${this.figureId ?? ''}:${
|
|
322
|
+
const signature = `${this.figureId ?? ''}:${draft.width}x${draft.height}x${draft.depth}`
|
|
336
323
|
if (this.scene && this.standing === signature) {
|
|
337
324
|
this.syncParts()
|
|
338
325
|
return
|
|
@@ -345,10 +332,9 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
345
332
|
target: this.stage,
|
|
346
333
|
model: {
|
|
347
334
|
type: 'model-layer',
|
|
348
|
-
width:
|
|
349
|
-
height:
|
|
350
|
-
depth:
|
|
351
|
-
placement: toScenePlacement(board.placement),
|
|
335
|
+
width: draft.width,
|
|
336
|
+
height: draft.height,
|
|
337
|
+
depth: draft.depth,
|
|
352
338
|
threed: true,
|
|
353
339
|
environment: 'studio',
|
|
354
340
|
/*
|
|
@@ -363,7 +349,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
363
349
|
보드는 이 값을 안 주므로 안 보인다.
|
|
364
350
|
*/
|
|
365
351
|
...this.sceneView(),
|
|
366
|
-
gridStep: gridStep(
|
|
352
|
+
gridStep: gridStep(draft),
|
|
367
353
|
sky: 'color',
|
|
368
354
|
/*
|
|
369
355
|
판의 면 — 2D 는 밝은 종이, 3D 는 없음.
|
|
@@ -439,9 +425,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
439
425
|
* 이름이다. 표를 두면 씬이 새 설정을 받아도 이쪽이 모른다.
|
|
440
426
|
*/
|
|
441
427
|
private sceneView(): Record<string, unknown> {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return { grid, gridLabels, floor, environment, skyColor, ...light }
|
|
428
|
+
return sceneLook(this.view ?? DEFAULT_VIEW)
|
|
445
429
|
}
|
|
446
430
|
|
|
447
431
|
/**
|
|
@@ -452,11 +436,11 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
452
436
|
* 밝기 하나 끄는 동안 화면이 계속 처음으로 돌아간다.
|
|
453
437
|
*/
|
|
454
438
|
private applyView(): void {
|
|
455
|
-
const
|
|
456
|
-
if (!
|
|
439
|
+
const draft = this.scene?.root
|
|
440
|
+
if (!draft) return
|
|
457
441
|
|
|
458
|
-
|
|
459
|
-
|
|
442
|
+
draft.set?.(this.sceneView())
|
|
443
|
+
draft.invalidate?.()
|
|
460
444
|
}
|
|
461
445
|
|
|
462
446
|
/**
|
|
@@ -509,9 +493,9 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
509
493
|
* 목록이 바뀌면 엉뚱한 부품을 가리킨다.
|
|
510
494
|
*/
|
|
511
495
|
private watchScene() {
|
|
512
|
-
const
|
|
513
|
-
const root =
|
|
514
|
-
if (!
|
|
496
|
+
const draft = this.scene?.root
|
|
497
|
+
const root = draft?.root
|
|
498
|
+
if (!draft || !root?.on || this.viewOnly) return
|
|
515
499
|
|
|
516
500
|
root.on('selected', (after: unknown) => {
|
|
517
501
|
const chosen = (after as SceneComponent[] | undefined)?.[0]
|
|
@@ -528,7 +512,7 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
528
512
|
뒤에 붙여 부모에게 넘긴다. 첫 인자를 컴포넌트로 읽으면 아무 일도 안 일어난다 —
|
|
529
513
|
실제로 그랬다.
|
|
530
514
|
*/
|
|
531
|
-
|
|
515
|
+
draft.on?.('change', (...args: unknown[]) => {
|
|
532
516
|
const hint = args[args.length - 1] as { origin?: SceneComponent } | undefined
|
|
533
517
|
const changed = hint?.origin
|
|
534
518
|
const name = changed?.state?.name
|
|
@@ -544,13 +528,13 @@ export class FigureCanvas extends localize(i18next)(LitElement) {
|
|
|
544
528
|
|
|
545
529
|
/** 목록에서 고른 것을 캔버스의 선택에 맞춘다. */
|
|
546
530
|
private showPicked() {
|
|
547
|
-
const
|
|
548
|
-
const root =
|
|
549
|
-
if (!
|
|
531
|
+
const draft = this.scene?.root
|
|
532
|
+
const root = draft?.root
|
|
533
|
+
if (!draft || !root) return
|
|
550
534
|
|
|
551
535
|
const component = this.picked ? this.partsOnBoard().get(this.picked) : undefined
|
|
552
536
|
root.selected = component ? [component] : []
|
|
553
|
-
|
|
537
|
+
draft.invalidate?.()
|
|
554
538
|
}
|
|
555
539
|
|
|
556
540
|
private tell(what: string, detail: object) {
|
|
@@ -5,7 +5,7 @@ import { live } from 'lit/directives/live.js'
|
|
|
5
5
|
import { i18next, localize } from '@operato/i18n'
|
|
6
6
|
import { ScrollbarStyles } from '@operato/styles'
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { FigureDraft, PartModel } from './figure-source.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 이 도형이 **무엇을 하는가.**
|
|
@@ -240,12 +240,12 @@ export class FigureCapabilities extends localize(i18next)(LitElement) {
|
|
|
240
240
|
`
|
|
241
241
|
]
|
|
242
242
|
|
|
243
|
-
@property({ type: Object })
|
|
243
|
+
@property({ type: Object }) draft?: FigureDraft
|
|
244
244
|
@property({ type: Array }) parts: PartModel[] = []
|
|
245
245
|
|
|
246
246
|
render() {
|
|
247
|
-
const rung = rungOf(this.
|
|
248
|
-
const names = this.
|
|
247
|
+
const rung = rungOf(this.draft?.capabilities)
|
|
248
|
+
const names = this.draft?.capabilities ?? []
|
|
249
249
|
|
|
250
250
|
return html`
|
|
251
251
|
<section>
|
|
@@ -349,10 +349,10 @@ export class FigureCapabilities extends localize(i18next)(LitElement) {
|
|
|
349
349
|
* 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.
|
|
350
350
|
*/
|
|
351
351
|
private setRung(which: Rung) {
|
|
352
|
-
const
|
|
353
|
-
if (!
|
|
352
|
+
const draft = this.draft
|
|
353
|
+
if (!draft) return
|
|
354
354
|
|
|
355
|
-
this.tellBoard({ ...
|
|
355
|
+
this.tellBoard({ ...draft, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined })
|
|
356
356
|
|
|
357
357
|
if (which === 'none') {
|
|
358
358
|
this.parts.forEach((part, index) => {
|
|
@@ -365,8 +365,8 @@ export class FigureCapabilities extends localize(i18next)(LitElement) {
|
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
private tellBoard(
|
|
369
|
-
this.
|
|
370
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
368
|
+
private tellBoard(draft: FigureDraft) {
|
|
369
|
+
this.draft = draft
|
|
370
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))
|
|
371
371
|
}
|
|
372
372
|
}
|
|
@@ -21,7 +21,7 @@ import type { AnchorRule, Axis, FigurePart, FigurePlacement, MaterialPreset, Par
|
|
|
21
21
|
import { activePalette } from '@hatiolab/things-scene'
|
|
22
22
|
|
|
23
23
|
import { partToFigure, partFromFigure } from './figure-source.js'
|
|
24
|
-
import type {
|
|
24
|
+
import type { FigureDraft, PartModel } from './figure-source.js'
|
|
25
25
|
import { canMirror } from './part-edits.js'
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -29,7 +29,7 @@ import { canMirror } from './part-edits.js'
|
|
|
29
29
|
*
|
|
30
30
|
* ## 여기가 사용자 용어로 보여 주는 자리다
|
|
31
31
|
*
|
|
32
|
-
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 —
|
|
32
|
+
* 모델러 페이지가 갖고 있는 것은 **씬 모델**이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.
|
|
33
33
|
* 그런데 사용자에게 보여야 하는 것은 씬 속성 이름(`left`·`top`·`zPos`)이 아니라
|
|
34
34
|
* **X·Y·Z** 다. 다른 3D 도구가 모두 그렇게 부르고, 위치도 부품의 **중심**으로 읽는다.
|
|
35
35
|
*
|
|
@@ -787,8 +787,8 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
787
787
|
/** 펴 둔 도움말. 부품을 바꿔도 유지된다 — 매번 다시 펴게 하지 않는다. */
|
|
788
788
|
@state() private opened = new Set<string>()
|
|
789
789
|
|
|
790
|
-
@property({ type: Object })
|
|
791
|
-
/**
|
|
790
|
+
@property({ type: Object }) draft?: FigureDraft
|
|
791
|
+
/** 초안 위의 부품들. 씬 모델 그대로다. */
|
|
792
792
|
@property({ type: Array }) parts: PartModel[] = []
|
|
793
793
|
@property({ type: Number }) selected = -1
|
|
794
794
|
|
|
@@ -799,7 +799,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
799
799
|
|
|
800
800
|
render() {
|
|
801
801
|
const part = this.chosen
|
|
802
|
-
if (!part || !this.
|
|
802
|
+
if (!part || !this.draft) {
|
|
803
803
|
return html`
|
|
804
804
|
${this.renderFigure(false)}
|
|
805
805
|
<section>
|
|
@@ -810,7 +810,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
// 여기부터는 사용자 용어다 — X·Y·Z, 위치는 부품 중심, 회전은 도(degree).
|
|
813
|
-
const shown = partToFigure(part, this.
|
|
813
|
+
const shown = partToFigure(part, this.draft)
|
|
814
814
|
|
|
815
815
|
/*
|
|
816
816
|
역할을 먼저 묻는다.
|
|
@@ -837,7 +837,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
837
837
|
* 컨베이어가 길어지면 실을 자리가 늘어나는 것이 그것이고, 여기 적은 값은 안 쓰인다.
|
|
838
838
|
*/
|
|
839
839
|
private renderRole(part: FigurePart) {
|
|
840
|
-
if (!this.
|
|
840
|
+
if (!this.draft?.capabilities?.length) return nothing
|
|
841
841
|
|
|
842
842
|
const roles = part.capability?.roles ?? []
|
|
843
843
|
const repeats = part.sizing === 'repeat' && !!part.repeat
|
|
@@ -967,7 +967,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
967
967
|
<label>${i18next.t('figure.label.figure-type')}${this.figureId ? this.helpButton('figure.text.type-name-cannot-change') : nothing}</label>
|
|
968
968
|
${!this.figureId
|
|
969
969
|
? html`<input
|
|
970
|
-
.value=${this.
|
|
970
|
+
.value=${this.draft?.figureType ?? ''}
|
|
971
971
|
placeholder=${i18next.t('figure.text.figure-type-placeholder', { defaultValue: '도형 식별 타입 (예: OHT)' })}
|
|
972
972
|
@change=${(e: Event) =>
|
|
973
973
|
this.dispatchEvent(
|
|
@@ -979,7 +979,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
979
979
|
)}
|
|
980
980
|
/>`
|
|
981
981
|
: html`<code fixed title=${i18next.t('figure.text.type-name-cannot-change')}>
|
|
982
|
-
<md-icon>lock</md-icon>${this.
|
|
982
|
+
<md-icon>lock</md-icon>${this.draft?.figureType ?? ''}
|
|
983
983
|
</code>
|
|
984
984
|
${this.helpHint('figure.text.type-name-cannot-change')}`}
|
|
985
985
|
</div>
|
|
@@ -991,16 +991,16 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
991
991
|
<div inline-field>
|
|
992
992
|
<label>${i18next.t('figure.label.figure-placement')}${this.helpButton('figure.text.placement-explained')}</label>
|
|
993
993
|
<select
|
|
994
|
-
.value=${this.
|
|
994
|
+
.value=${this.draft?.placement ?? 'floor'}
|
|
995
995
|
@change=${(e: Event) => this.setPlacement((e.target as HTMLSelectElement).value as FigurePlacement)}
|
|
996
996
|
>
|
|
997
|
-
<option value="floor" ?selected=${(this.
|
|
997
|
+
<option value="floor" ?selected=${(this.draft?.placement ?? 'floor') === 'floor'}>
|
|
998
998
|
${i18next.t('figure.label.placement-floor')}
|
|
999
999
|
</option>
|
|
1000
|
-
<option value="ceiling" ?selected=${this.
|
|
1000
|
+
<option value="ceiling" ?selected=${this.draft?.placement === 'ceiling'}>
|
|
1001
1001
|
${i18next.t('figure.label.placement-ceiling')}
|
|
1002
1002
|
</option>
|
|
1003
|
-
<option value="center" ?selected=${this.
|
|
1003
|
+
<option value="center" ?selected=${this.draft?.placement === 'center'}>
|
|
1004
1004
|
${i18next.t('figure.label.placement-center')}
|
|
1005
1005
|
</option>
|
|
1006
1006
|
</select>
|
|
@@ -1034,7 +1034,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1034
1034
|
<details asset>
|
|
1035
1035
|
<summary>
|
|
1036
1036
|
<b>${i18next.t('figure.label.figure')}</b>
|
|
1037
|
-
<small>${this.figureName || this.
|
|
1037
|
+
<small>${this.figureName || this.draft?.figureType || ''}</small>
|
|
1038
1038
|
</summary>
|
|
1039
1039
|
${fields}
|
|
1040
1040
|
</details>
|
|
@@ -1075,11 +1075,22 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1075
1075
|
this.dispatchEvent(new CustomEvent('figure-catalog', { detail: patch, bubbles: true, composed: true }))
|
|
1076
1076
|
}
|
|
1077
1077
|
|
|
1078
|
-
/**
|
|
1078
|
+
/**
|
|
1079
|
+
* 3D 배치 기준면을 바꾼다 ('floor' | 'ceiling' | 'center').
|
|
1080
|
+
*
|
|
1081
|
+
* **부품은 건드리지 않는다.** 이 값은 「이 종류는 어느 면에 붙는가」를 말할 뿐이고,
|
|
1082
|
+
* 저작한 모양은 그것과 무관하다. 형식이 부품을 상자 중심에서 재므로, 배치가 달라도
|
|
1083
|
+
* `parts` 는 한 글자도 안 바뀐다.
|
|
1084
|
+
*
|
|
1085
|
+
* 한동안 여기서 `zPos` 를 다시 계산했다. 편집 캔버스가 이 값을 씬의 좌표 모드로
|
|
1086
|
+
* 흘려보내던 때의 일인데, 그러면 배치를 고를 때마다 저작자가 적어 둔 높이가 전부
|
|
1087
|
+
* 다른 숫자로 보였다 — 저장되는 것은 그대로인데. 좌표 모드는 이제 보기 설정이고
|
|
1088
|
+
* (`figure-view.ts`), 편집 캔버스는 늘 상자 기준이다.
|
|
1089
|
+
*/
|
|
1079
1090
|
private setPlacement(placement: FigurePlacement) {
|
|
1080
|
-
if (!this.
|
|
1081
|
-
const
|
|
1082
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
1091
|
+
if (!this.draft) return
|
|
1092
|
+
const draft = { ...this.draft, placement }
|
|
1093
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))
|
|
1083
1094
|
}
|
|
1084
1095
|
|
|
1085
1096
|
private renderIdentity(part: FigurePart) {
|
|
@@ -1180,7 +1191,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1180
1191
|
</button>
|
|
1181
1192
|
${(['x', 'z'] as const).map(axis => {
|
|
1182
1193
|
// 이미 가운데면 대칭이 제자리다 — 겹친 부품이 조용히 생기지 않게 막는다
|
|
1183
|
-
const able = this.
|
|
1194
|
+
const able = this.draft ? canMirror(this.draft, this.parts, this.selected, axis) : false
|
|
1184
1195
|
return html`
|
|
1185
1196
|
<button
|
|
1186
1197
|
?disabled=${!able}
|
|
@@ -1603,10 +1614,10 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1603
1614
|
* 이 축의 anchor 가 정하는데 화면에는 그 말이 없었다. 그래서 어느 쪽인지 함께 적는다.
|
|
1604
1615
|
*/
|
|
1605
1616
|
private renderRepeatCount(part: FigurePart) {
|
|
1606
|
-
const
|
|
1607
|
-
if (!
|
|
1617
|
+
const draft = this.draft
|
|
1618
|
+
if (!draft) return nothing
|
|
1608
1619
|
|
|
1609
|
-
const base = { x:
|
|
1620
|
+
const base = { x: draft.width, y: draft.depth, z: draft.height }
|
|
1610
1621
|
const plan = repeatPlanOfPart(part, { x: 1, y: 1, z: 1 }, base)
|
|
1611
1622
|
if (!plan) return nothing
|
|
1612
1623
|
|
|
@@ -1693,8 +1704,8 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1693
1704
|
}
|
|
1694
1705
|
|
|
1695
1706
|
private renderAnchors(part: FigurePart) {
|
|
1696
|
-
const base = this.
|
|
1697
|
-
? { x: this.
|
|
1707
|
+
const base = this.draft
|
|
1708
|
+
? { x: this.draft.width, y: this.draft.height, z: this.draft.depth }
|
|
1698
1709
|
: { x: 0, y: 0, z: 0 }
|
|
1699
1710
|
const nameOf = (rule: string, axis: Axis) =>
|
|
1700
1711
|
`${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`
|
|
@@ -1841,7 +1852,7 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1841
1852
|
*/
|
|
1842
1853
|
private putEmissive(intensity: number) {
|
|
1843
1854
|
// `chosen` 은 씬 모델이다. 형식으로 옮긴 값은 `partToFigure` 이 만든 것이라 여기서 다시 꺼낸다.
|
|
1844
|
-
const glow = this.
|
|
1855
|
+
const glow = this.draft && this.chosen ? partToFigure(this.chosen, this.draft).material?.emissive : undefined
|
|
1845
1856
|
|
|
1846
1857
|
this.putMaterial({ emissive: intensity > 0 ? { ...glow, intensity } : undefined })
|
|
1847
1858
|
}
|
|
@@ -1862,10 +1873,10 @@ export class FigureInspector extends localize(i18next)(LitElement) {
|
|
|
1862
1873
|
*/
|
|
1863
1874
|
private change(edit: (part: FigurePart) => FigurePart) {
|
|
1864
1875
|
const part = this.chosen
|
|
1865
|
-
const
|
|
1866
|
-
if (!part || !
|
|
1876
|
+
const draft = this.draft
|
|
1877
|
+
if (!part || !draft) return
|
|
1867
1878
|
|
|
1868
|
-
this.emit(partFromFigure(edit(partToFigure(part,
|
|
1879
|
+
this.emit(partFromFigure(edit(partToFigure(part, draft)), draft))
|
|
1869
1880
|
}
|
|
1870
1881
|
|
|
1871
1882
|
/** 정밀 도구를 시킨다. 정본은 저작면이 갖는다 — 여기서는 알리기만 한다. */
|
|
@@ -303,7 +303,7 @@ export class FigureParts extends localize(i18next)(LitElement) {
|
|
|
303
303
|
]
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
|
-
*
|
|
306
|
+
* 초안 위의 부품들. **씬 모델 그대로 받는다.**
|
|
307
307
|
*
|
|
308
308
|
* 이 패널은 이름과 도형만 읽으므로 어느 쪽 형태로 받아도 화면은 같다. 그래도 씬
|
|
309
309
|
* 모델로 받는 것은 모델러 페이지가 데이터를 **한 벌만** 갖게 하기 위해서다. 여기만
|