@things-factory/figure-ui 10.1.65 → 10.1.67

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.
Files changed (176) hide show
  1. package/client/graphql/index.ts +0 -45
  2. package/client/modeller/figure-history-panel.ts +6 -145
  3. package/client/modeller/figure-v3-capability.ts +2 -2
  4. package/client/modeller/figure-v3-clips.ts +2 -2
  5. package/client/modeller/figure-v3-controls.ts +2 -2
  6. package/client/modeller/figure-v3-first-save.ts +1 -1
  7. package/client/modeller/figure-v3-links.ts +6 -6
  8. package/client/modeller/figure-v3-material.ts +11 -11
  9. package/client/modeller/figure-v3-parts.ts +7 -10
  10. package/client/modeller/figure-v3-preview.ts +3 -3
  11. package/client/modeller/figure-v3-properties.ts +5 -6
  12. package/client/modeller/figure-v3-verdict.ts +9 -10
  13. package/client/modeller/figure-view.ts +2 -3
  14. package/client/modeller/modeller-route.ts +2 -3
  15. package/client/modeller/scene-budget.ts +0 -14
  16. package/client/modeller/side-panel-styles.ts +5 -5
  17. package/client/modeller/v3-align.ts +16 -6
  18. package/client/modeller/v3-canvas.ts +12 -12
  19. package/client/modeller/v3-clips.ts +1 -1
  20. package/client/modeller/v3-controls.ts +1 -1
  21. package/client/modeller/v3-editor.ts +21 -21
  22. package/client/modeller/v3-fields.ts +1 -1
  23. package/client/modeller/v3-figure-facts.ts +1 -1
  24. package/client/modeller/v3-first-save.ts +3 -4
  25. package/client/modeller/v3-gizmo.ts +5 -5
  26. package/client/modeller/v3-help.ts +1 -1
  27. package/client/modeller/v3-joint-fan.ts +4 -5
  28. package/client/modeller/v3-keys.ts +1 -1
  29. package/client/modeller/v3-links.ts +9 -12
  30. package/client/modeller/v3-maturity.ts +22 -26
  31. package/client/modeller/v3-refusals.ts +17 -0
  32. package/client/modeller/v3-renderer.ts +1 -1
  33. package/client/modeller/v3-stage-model.ts +2 -2
  34. package/client/modeller/v3-thumbnail.ts +1 -3
  35. package/client/pages/figure-v3-modeller-page.ts +8 -11
  36. package/client/types.ts +2 -31
  37. package/dist-client/graphql/index.js +0 -45
  38. package/dist-client/graphql/index.js.map +1 -1
  39. package/dist-client/modeller/figure-history-panel.d.ts +4 -37
  40. package/dist-client/modeller/figure-history-panel.js +8 -154
  41. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  42. package/dist-client/modeller/figure-v3-capability.d.ts +1 -1
  43. package/dist-client/modeller/figure-v3-capability.js +1 -1
  44. package/dist-client/modeller/figure-v3-capability.js.map +1 -1
  45. package/dist-client/modeller/figure-v3-clips.d.ts +2 -2
  46. package/dist-client/modeller/figure-v3-clips.js +2 -2
  47. package/dist-client/modeller/figure-v3-clips.js.map +1 -1
  48. package/dist-client/modeller/figure-v3-controls.d.ts +1 -1
  49. package/dist-client/modeller/figure-v3-controls.js +2 -2
  50. package/dist-client/modeller/figure-v3-controls.js.map +1 -1
  51. package/dist-client/modeller/figure-v3-first-save.d.ts +1 -1
  52. package/dist-client/modeller/figure-v3-first-save.js +1 -1
  53. package/dist-client/modeller/figure-v3-first-save.js.map +1 -1
  54. package/dist-client/modeller/figure-v3-links.d.ts +5 -5
  55. package/dist-client/modeller/figure-v3-links.js +6 -6
  56. package/dist-client/modeller/figure-v3-links.js.map +1 -1
  57. package/dist-client/modeller/figure-v3-material.d.ts +6 -6
  58. package/dist-client/modeller/figure-v3-material.js +11 -11
  59. package/dist-client/modeller/figure-v3-material.js.map +1 -1
  60. package/dist-client/modeller/figure-v3-parts.d.ts +6 -8
  61. package/dist-client/modeller/figure-v3-parts.js +1 -1
  62. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  63. package/dist-client/modeller/figure-v3-preview.js.map +1 -1
  64. package/dist-client/modeller/figure-v3-properties.d.ts +4 -5
  65. package/dist-client/modeller/figure-v3-properties.js +5 -6
  66. package/dist-client/modeller/figure-v3-properties.js.map +1 -1
  67. package/dist-client/modeller/figure-v3-verdict.d.ts +2 -2
  68. package/dist-client/modeller/figure-v3-verdict.js +9 -10
  69. package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
  70. package/dist-client/modeller/figure-view.d.ts +2 -3
  71. package/dist-client/modeller/figure-view.js.map +1 -1
  72. package/dist-client/modeller/modeller-route.d.ts +2 -3
  73. package/dist-client/modeller/modeller-route.js +2 -3
  74. package/dist-client/modeller/modeller-route.js.map +1 -1
  75. package/dist-client/modeller/scene-budget.d.ts +0 -11
  76. package/dist-client/modeller/scene-budget.js +0 -12
  77. package/dist-client/modeller/scene-budget.js.map +1 -1
  78. package/dist-client/modeller/side-panel-styles.d.ts +2 -2
  79. package/dist-client/modeller/side-panel-styles.js +5 -5
  80. package/dist-client/modeller/side-panel-styles.js.map +1 -1
  81. package/dist-client/modeller/v3-align.d.ts +9 -5
  82. package/dist-client/modeller/v3-align.js +13 -3
  83. package/dist-client/modeller/v3-align.js.map +1 -1
  84. package/dist-client/modeller/v3-canvas.d.ts +4 -4
  85. package/dist-client/modeller/v3-canvas.js +9 -9
  86. package/dist-client/modeller/v3-canvas.js.map +1 -1
  87. package/dist-client/modeller/v3-clips.js.map +1 -1
  88. package/dist-client/modeller/v3-controls.js.map +1 -1
  89. package/dist-client/modeller/v3-editor.d.ts +13 -13
  90. package/dist-client/modeller/v3-editor.js +19 -18
  91. package/dist-client/modeller/v3-editor.js.map +1 -1
  92. package/dist-client/modeller/v3-fields.js.map +1 -1
  93. package/dist-client/modeller/v3-figure-facts.d.ts +1 -1
  94. package/dist-client/modeller/v3-figure-facts.js +1 -1
  95. package/dist-client/modeller/v3-figure-facts.js.map +1 -1
  96. package/dist-client/modeller/v3-first-save.js +3 -4
  97. package/dist-client/modeller/v3-first-save.js.map +1 -1
  98. package/dist-client/modeller/v3-gizmo.d.ts +5 -5
  99. package/dist-client/modeller/v3-gizmo.js +1 -1
  100. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  101. package/dist-client/modeller/v3-help.d.ts +1 -1
  102. package/dist-client/modeller/v3-help.js +1 -1
  103. package/dist-client/modeller/v3-help.js.map +1 -1
  104. package/dist-client/modeller/v3-joint-fan.js +4 -5
  105. package/dist-client/modeller/v3-joint-fan.js.map +1 -1
  106. package/dist-client/modeller/v3-keys.js.map +1 -1
  107. package/dist-client/modeller/v3-links.d.ts +5 -7
  108. package/dist-client/modeller/v3-links.js +6 -8
  109. package/dist-client/modeller/v3-links.js.map +1 -1
  110. package/dist-client/modeller/v3-maturity.d.ts +7 -7
  111. package/dist-client/modeller/v3-maturity.js +20 -24
  112. package/dist-client/modeller/v3-maturity.js.map +1 -1
  113. package/dist-client/modeller/v3-refusals.d.ts +6 -0
  114. package/dist-client/modeller/v3-refusals.js +18 -0
  115. package/dist-client/modeller/v3-refusals.js.map +1 -1
  116. package/dist-client/modeller/v3-renderer.js +1 -1
  117. package/dist-client/modeller/v3-renderer.js.map +1 -1
  118. package/dist-client/modeller/v3-stage-model.js.map +1 -1
  119. package/dist-client/modeller/v3-thumbnail.js +1 -3
  120. package/dist-client/modeller/v3-thumbnail.js.map +1 -1
  121. package/dist-client/pages/figure-v3-modeller-page.d.ts +4 -5
  122. package/dist-client/pages/figure-v3-modeller-page.js +8 -11
  123. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  124. package/dist-client/route.d.ts +1 -1
  125. package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
  126. package/dist-client/tsconfig.tsbuildinfo +1 -1
  127. package/dist-client/types.d.ts +2 -29
  128. package/dist-client/types.js +3 -0
  129. package/dist-client/types.js.map +1 -1
  130. package/docs/v3-product-cutover.md +14 -24
  131. package/package.json +5 -5
  132. package/test/browser/modeller-page.js +6 -7
  133. package/test/browser/modeller.check.mjs +9 -16
  134. package/test/fixtures/joint-column.json +1138 -0
  135. package/test/fixtures/joint-roller.json +872 -0
  136. package/test/fixtures/robot-arm-6dof.json +2776 -0
  137. package/test/i18n-prefix-guard.test.ts +0 -31
  138. package/test/modeller-route.test.ts +2 -3
  139. package/test/v3-align.test.ts +9 -4
  140. package/test/v3-clips.test.ts +1 -1
  141. package/test/v3-controls.test.ts +1 -1
  142. package/test/v3-fields.test.ts +4 -4
  143. package/test/v3-first-save.test.ts +1 -1
  144. package/test/v3-gizmo.test.ts +5 -5
  145. package/test/v3-joint-fan.test.ts +12 -42
  146. package/test/v3-keys.test.ts +1 -1
  147. package/test/v3-layout.test.ts +4 -5
  148. package/test/v3-links.test.ts +3 -3
  149. package/test/v3-material.test.ts +3 -3
  150. package/test/v3-maturity.test.ts +8 -9
  151. package/test/v3-parts-wiring.test.ts +3 -3
  152. package/test/v3-preview.test.ts +1 -1
  153. package/test/v3-product-write.test.ts +1 -1
  154. package/test/v3-properties.test.ts +3 -3
  155. package/test/v3-refusals.test.ts +12 -0
  156. package/test/v3-release-panel.test.ts +5 -5
  157. package/test/v3-scene-canvas.test.ts +2 -2
  158. package/test/v3-theme.test.ts +2 -2
  159. package/test/v3-verdict.test.ts +2 -2
  160. package/test/v3-view.test.ts +2 -2
  161. package/translations/en.json +2 -143
  162. package/translations/ja.json +2 -143
  163. package/translations/ko.json +2 -143
  164. package/translations/ms.json +2 -143
  165. package/translations/zh.json +2 -143
  166. package/client/modeller/figure-source.ts +0 -437
  167. package/client/modeller/part-edits.ts +0 -312
  168. package/dist-client/modeller/figure-source.d.ts +0 -238
  169. package/dist-client/modeller/figure-source.js +0 -193
  170. package/dist-client/modeller/figure-source.js.map +0 -1
  171. package/dist-client/modeller/part-edits.d.ts +0 -89
  172. package/dist-client/modeller/part-edits.js +0 -270
  173. package/dist-client/modeller/part-edits.js.map +0 -1
  174. package/test/expect.ts +0 -57
  175. package/test/figure-source.test.ts +0 -511
  176. package/test/part-edits.test.ts +0 -350
@@ -1,437 +0,0 @@
1
- import {
2
- FIGURE_SOURCE_VERSION,
3
- type DetailLevel,
4
- type FigurePart,
5
- type FigurePlacement,
6
- type FigureSource,
7
- type Vec3
8
- } from '@hatiolab/figure-model'
9
-
10
- /**
11
- * Scene model <-> `FigureSource`.
12
- *
13
- * ## Why this is the only place that converts
14
- *
15
- * While authoring, the source of truth is the **scene model** -- part
16
- * components sitting on a draft. `FigureSource` is produced from it when the
17
- * asset is saved or scored.
18
- *
19
- * The two use different vocabularies. The scene inherited 2D canvas terms
20
- * (`width`/`height` in plan, `depth` for height, `left`/`top`/`zPos` for
21
- * position); the format uses the axes 3D tools use (`x` right, `y` up, `z`
22
- * front, position at the part's **centre**).
23
- *
24
- * **Convert in one place.** Two places drift, and this format has already had
25
- * one incident caused by axes disagreeing.
26
- *
27
- * ## It was called `fold`, and that was wrong
28
- *
29
- * `fold` is an established term -- it reduces a structure to a summary, and
30
- * this repo already uses it correctly (`foldKpi`, `foldProbeResults`). Nothing
31
- * is reduced here: the same information comes out in a different shape, and it
32
- * goes back unchanged. A reader who learned `fold` from `foldKpi` reads this
33
- * one as reducing something, and the user did exactly that.
34
- *
35
- * ## The mapping
36
- *
37
- * The draft is the base box; its centre is the format's origin.
38
- *
39
- * format scene
40
- * ──────────────── ──────────────────────────────
41
- * base.x draft width
42
- * base.y draft depth <- height
43
- * base.z draft height
44
- *
45
- * size.x width
46
- * size.y depth
47
- * size.z height
48
- *
49
- * position.x left + width/2 - draft width/2
50
- * position.y zPos + depth/2 <- both measured from the box bottom
51
- * position.z top + height/2 - draft height/2
52
- *
53
- * (Source edition 2 measures y from the bottom face of the base box, as the scene's zPos does, so
54
- * that row needs no half-box shift. x and z are still measured from the box centre. ADR-0065.)
55
- *
56
- * rotation.x rotationX (scene radians, format degrees)
57
- * rotation.y -rotation
58
- * rotation.z rotationY
59
- *
60
- * The rotation names are crossed because of the scene: `rotationY` becomes
61
- * three's z (`real-object.ts`, `get rotation()`). That stays inside this table.
62
- *
63
- * ## Fields nothing reads yet still travel
64
- *
65
- * `materialSlot`, `keepRound`, `sizing`, `anchor`, `repeat` and `label` exist in the format
66
- * and the editor ignores them. They still have to survive a round trip. When a
67
- * source proposed by AI is opened, edited and written back, a field that
68
- * disappears **without an error** is one the author cannot know about.
69
- *
70
- * **Five have gone that way here**: `ports`, `animations`, `anchor`,
71
- * `shape.hollow` and `keepRound`. The last one the test caught first, which
72
- * is the guard below actually earning its keep. The third showed up as a conveyor motor that stayed on the
73
- * floor no matter what the sample said; the fourth as a hollowed tray that
74
- * came out a solid block.
75
- *
76
- * All four passed the round-trip test, because the fixture it uses did not
77
- * carry the field. So the test now reads the **field list the format declares**
78
- * rather than trusting the fixture.
79
- *
80
- * `shape` is unpacked into `round`, `path` and `hollow` rather than carried
81
- * whole, because the editor holds those as separate state. Every field added
82
- * under `shape` costs a line here, and forgetting that line was the fourth
83
- * of the four.
84
- */
85
- /** 판 컴포넌트의 상태. 씬의 용어 그대로다. */
86
- export interface FigureDraft {
87
- /** 형식 버전. */
88
- version?: number
89
- /*
90
- The three sizes keep the scene component's names, and the scene assigns its axes differently
91
- from the format: here `depth` is the vertical one. Naming the axis alone ("the y axis") does
92
- not say which of the two conventions is meant, and this interface sits on the boundary between
93
- them — so each field names the `FigureSource` field it becomes. `toFigureSource` and
94
- `fromFigureSource` below are the only places that may disagree with these three lines.
95
- */
96
- /** 가로 너비 (mm) → `base.x`. */
97
- width: number
98
- /** 앞뒤 깊이 (mm) → `base.z`. 저작자가 위에서 내려다볼 때의 세로다. */
99
- height: number
100
- /** 높이 (mm) → `base.y`. 서 있는 방향이다. */
101
- depth: number
102
- /** 컴포넌트 타입 이름. **저장되는 식별자다.** */
103
- figureType?: string
104
- /** 3D 배치 기준면 ('floor' | 'ceiling' | 'center'). */
105
- placement?: FigurePlacement
106
- detailLevel?: DetailLevel
107
- styleKit?: string
108
- /**
109
- * 움직임. **부품 바깥에 있다** — clip 하나가 부품 여럿을 함께 움직이기 때문이다.
110
- *
111
- * 편집면이 아직 이것을 만지지 않는다. 그래도 여기 실어 두는 것은, 안 실으면 열었다
112
- * 저장하는 것만으로 **조용히 지워지기** 때문이다.
113
- */
114
- animations?: FigureSource['animations']
115
- /**
116
- * 값이 만드는 자세 — **애니메이션과 다른 칸이다.**
117
- *
118
- * 저것은 시각이 흘러 움직이는 것(도는 롤러)이고 이것은 인스턴스가 준 값이 그대로 자세가
119
- * 되는 것(1200mm 내려온 호이스트)이다. 한동안 애니메이션 안에 `drive: 'hold'` 로 숨어
120
- * 있었고, 그래서 「길이값을 변수로 노출하라」는 요청에 아무도 이것을 못 찾았다(ADR-0051).
121
- */
122
- parameters?: FigureSource['parameters']
123
- /**
124
- * 이 도형이 지는 능력. 씬의 capability mixin 이름이다.
125
- *
126
- * **부품이 아니라 도형 전체의 것이다** — 「이 컨베이어는 흐름의 마디다」는 부품 하나가
127
- * 아니라 도형이 지는 성질이다. 부품 쪽 `capability` 는 그 능력을 **어느 부품이 받쳐
128
- * 주나**를 말하는 다른 칸이다.
129
- */
130
- capabilities?: FigureSource['capabilities']
131
- /**
132
- * How parts move relative to their parents (ADR-0066). Kept in figure coordinates as stored, the same
133
- * terms the inspector shows. Carried even when nothing edits them, so opening and saving never drops
134
- * them.
135
- */
136
- joints?: FigureSource['joints']
137
- }
138
-
139
- /** 부품 컴포넌트의 상태. 씬의 용어 그대로다. */
140
- export interface PartModel {
141
- type: 'figure-part'
142
- /** 부품 이름. **저장되는 식별자다.** 씬은 이 값을 검색에도 쓴다. */
143
- name: string
144
- primitive: FigurePart['primitive']
145
-
146
- left: number
147
- top: number
148
- width: number
149
- height: number
150
- /** 부피의 **밑면** 높이. 씬의 'floor' 배치에서 그렇게 정해져 있다. */
151
- zPos: number
152
- depth: number
153
-
154
- /** 라디안. 씬이 그렇게 쓴다. */
155
- rotationX?: number
156
- rotation?: number
157
- rotationY?: number
158
-
159
- segments?: number
160
- round?: number
161
- path?: { x: number; y: number }[]
162
- /** 속을 판 단면. 편집면이 아직 안 만지지만 잃어버리면 통이 통짜 상자가 된다. */
163
- hollow?: NonNullable<FigurePart['shape']>['hollow']
164
-
165
- /**
166
- * 편집 중에만 쓰는 상태 — **저장 형식으로 넘어가지 않는다.**
167
- *
168
- * 씬이 이미 갖고 있는 속성이다. 편집 모드에서는 `faint`(반투명 + 색을 버리고 회색으로
169
- * 수렴)로 보이고, 뷰 모드에서는 아예 안 그려진다. 판단은 씬의 `modeling-faint` 한 곳에
170
- * 있고 2D 캔버스·3D 재질·DOM 세 채널이 같이 쓴다.
171
- *
172
- * 숨김은 "지금 이 뒤를 만지려고" 하는 것이지 그 부품의 성질이 아니다. 그래서
173
- * `toFigureSource` 가 싣지 않고, 다시 열면 전부 보이는 상태로 돌아온다.
174
- */
175
- hidden?: boolean
176
-
177
- /**
178
- * 씬이 이것을 **집지 않는다** — 클릭·마퀴·기즈모 어디에도 안 걸린다.
179
- *
180
- * 저작면에만 서는 것(관절 부채꼴)이 쓴다. 저장 형식에는 없는 칸이라 `toFigureSource` 가
181
- * 싣지 않는다.
182
- */
183
- locked?: boolean
184
-
185
- /** 색 토큰 이름 (`palette.primary` 등). */
186
- token?: string
187
- /*
188
- `FigurePart['material']` is required in the format, so these index into it directly. The
189
- `NonNullable` on `shape` above is not the same case — `shape` is optional there.
190
- */
191
- preset?: FigurePart['material']['preset']
192
- flatShading?: boolean
193
- transparent?: boolean
194
- /**
195
- * 자체발광. 없으면 안 빛난다.
196
- *
197
- * 바깥의 `token` 이 **꺼진 모습**이고 이 안의 `token` 이 **켜진 모습**이다 — 꺼진
198
- * 전구는 빨갛지 않고 어두운 플라스틱이다.
199
- */
200
- emissive?: FigurePart['material']['emissive']
201
- surface?: FigurePart['material']['surface']
202
-
203
- /* 아직 아무 일도 안 하지만 잃어버리지 않는다. */
204
- materialSlot?: FigurePart['materialSlot']
205
- /** 단면을 원으로 지킬까. 곡면만 뜻이 있고 기본은 지키는 것이다. */
206
- keepRound?: FigurePart['keepRound']
207
- sizing?: FigurePart['sizing']
208
- /** 축마다 어느 면을 붙잡나. `sizing` 이 정한 것을 덮어쓴다. */
209
- anchor?: FigurePart['anchor']
210
- repeat?: FigurePart['repeat']
211
- label?: FigurePart['label']
212
- /**
213
- * 이 부품이 능력의 어느 자리인가 — 물건이 앉는 자리(slot)이거나 드나드는 문(port)이다.
214
- *
215
- * 이 칸이 있는 부품은 **그려지지 않는다.** 저작면에서는 어디인지 보여야 하니 그리지만,
216
- * 청사진에서는 `groups` 가 아니라 `anchors` 로 간다.
217
- */
218
- capability?: FigurePart['capability']
219
- /** The part this one is attached to (ADR-0066). The one place a parent is written. */
220
- parent?: FigurePart['parent']
221
- }
222
-
223
- const RAD_TO_DEG = 180 / Math.PI
224
- const DEG_TO_RAD = Math.PI / 180
225
-
226
- /** 값이 있는 것만 남긴다. 없는 필드를 `undefined` 로 두면 접었다 편 것이 원본과 달라진다. */
227
- function withoutEmpty<T extends object>(value: T): T {
228
- const out = {} as Record<string, unknown>
229
- for (const [key, item] of Object.entries(value)) {
230
- if (item !== undefined) out[key] = item
231
- }
232
- return out as T
233
- }
234
-
235
- /** 소수점 넷째 자리까지. 라디안↔도를 오가며 생기는 꼬리를 자른다. */
236
- export function round4(value: number): number {
237
- return Math.round(value * 1e4) / 1e4
238
- }
239
-
240
- /**
241
- * 판의 절반. 형식의 원점이 판 중심이라 이 값이 늘 함께 다닌다.
242
- *
243
- * 축 이름은 **형식의 것**이다 — x 가로 · y 높이 · z 세로.
244
- */
245
- export function halfOf(draft: FigureDraft): Vec3 {
246
- return { x: draft.width / 2, y: draft.depth / 2, z: draft.height / 2 }
247
- }
248
-
249
- /*
250
- * ── The placement is not in these numbers ────────────────────────────────────
251
- *
252
- * `placement` says which reference surface a figure of this kind attaches to — a conveyor stands
253
- * on the floor, an OHT hangs from the ceiling. That is a fact about the figure *type*, and the
254
- * scene acts on it when an instance is placed: `floor` puts the box on the ground, `ceiling` at
255
- * the ceiling height less the figure's own height. Nothing about the authored shape changes.
256
- *
257
- * So the conversion below does not read it, and the editing canvas does not either. The box is
258
- * always `0 … base.y` there and `zPos` is always the part's underside. Two figures with identical
259
- * parts and different placements have identical `parts` arrays, which is what the format says.
260
- *
261
- * This used to be wired the other way: the canvas fed the figure's placement to the model layer's
262
- * `state.placement`, which is a scene-wide coordinate mode ('floor' | 'space' | 'inverted') that
263
- * flips what `zPos` names for everything in that layer. It stood in for the archetype because the
264
- * modeller holds exactly one figure — and it cost a whole day of defects, because every number the
265
- * author typed then meant a different face depending on a dropdown. That coordinate mode is now
266
- * where it belongs: a workbench setting in `figure-view.ts`, applied to the preview alone.
267
- */
268
-
269
- /**
270
- * 저작자가 보는 축이 씬의 어느 용어인가.
271
- *
272
- * 씬은 2D 캔버스에서 온 용어를 쓰므로 축 이름이 그대로 없다. 이 표가 **유일한** 대응이다 —
273
- * 편집 규칙이 저마다 자기 표를 들면 한 곳을 고칠 때 다른 곳이 남는다.
274
- */
275
- export const AXIS_KEYS = {
276
- x: { at: 'left', size: 'width' },
277
- y: { at: 'zPos', size: 'depth' },
278
- z: { at: 'top', size: 'height' }
279
- } as const
280
-
281
- export type PlaneAxis = keyof typeof AXIS_KEYS
282
-
283
- /** 판이 그 축으로 얼마나 넓은가. */
284
- export function draftExtent(draft: FigureDraft, axis: PlaneAxis): number {
285
- return axis === 'x' ? draft.width : axis === 'y' ? draft.depth : draft.height
286
- }
287
-
288
- /** 부품 하나를 저장 형식으로 변환한다. 저작 화면이 저작자의 어휘로 보일 때 쓴다. */
289
- export function partToFigure(part: PartModel, draft: FigureDraft): FigurePart {
290
- return partTo(part, halfOf(draft))
291
- }
292
-
293
- /** 부품 하나를 씬 용어로 되돌린다. */
294
- export function partFromFigure(part: FigurePart, draft: FigureDraft): PartModel {
295
- return partFrom(part, halfOf(draft))
296
- }
297
-
298
- /** 씬 모델을 저장 형식으로 변환한다. */
299
- export function toFigureSource(draft: FigureDraft, parts: PartModel[]): FigureSource {
300
- const half = halfOf(draft)
301
-
302
- return withoutEmpty({
303
- version: draft.version ?? FIGURE_SOURCE_VERSION,
304
- type: draft.figureType ?? '',
305
- base: { x: draft.width, y: draft.depth, z: draft.height },
306
- placement: draft.placement,
307
- detailLevel: draft.detailLevel,
308
- styleKit: draft.styleKit,
309
- parts: parts.map(part => partTo(part, half)),
310
- animations: draft.animations,
311
- parameters: draft.parameters,
312
- capabilities: draft.capabilities,
313
- joints: draft.joints?.length ? draft.joints : undefined
314
- }) as FigureSource
315
- }
316
-
317
- function partTo(part: PartModel, half: Vec3): FigurePart {
318
- const rotation = withoutEmpty({
319
- x: part.rotationX ? round4(part.rotationX * RAD_TO_DEG) : undefined,
320
- y: part.rotation ? round4(-part.rotation * RAD_TO_DEG) : undefined,
321
- z: part.rotationY ? round4(part.rotationY * RAD_TO_DEG) : undefined
322
- })
323
-
324
- const shape = withoutEmpty({ round: part.round, path: part.path, hollow: part.hollow })
325
- /*
326
- 재질이 하나도 없으면 **칸 자체를 안 적는다.**
327
-
328
- 빈 객체를 적으면 형식이 「재질은 있는데 무엇으로 칠할지 모른다」로 읽어
329
- `material-unresolvable` 을 낸다. 그리지 않는 자리(`capability`)에는 재질이 없는 것이
330
- 정상이고, 그 부품이 조용히 오류를 달고 다니게 된다.
331
- */
332
- const material = withoutEmpty({
333
- token: part.token,
334
- preset: part.preset,
335
- flatShading: part.flatShading,
336
- transparent: part.transparent,
337
- emissive: part.emissive,
338
- surface: part.surface
339
- })
340
-
341
- return withoutEmpty({
342
- name: part.name,
343
- primitive: part.primitive,
344
- transform: withoutEmpty({
345
- position: {
346
- x: round4(part.left + part.width / 2 - half.x),
347
- y: round4(part.zPos + part.depth / 2),
348
- z: round4(part.top + part.height / 2 - half.z)
349
- },
350
- size: { x: part.width, y: part.depth, z: part.height },
351
- rotation: Object.keys(rotation).length > 0 ? rotation : undefined
352
- }),
353
- segments: part.segments,
354
- shape: Object.keys(shape).length > 0 ? shape : undefined,
355
- material: Object.keys(material).length > 0 ? material : undefined,
356
- materialSlot: part.materialSlot,
357
- keepRound: part.keepRound,
358
- sizing: part.sizing,
359
- anchor: part.anchor,
360
- repeat: part.repeat,
361
- label: part.label,
362
- capability: part.capability,
363
- parent: part.parent
364
- }) as FigurePart
365
- }
366
-
367
- /** 형식을 씬 모델로 편다. */
368
- export function fromFigureSource(source: FigureSource): { draft: FigureDraft; parts: PartModel[] } {
369
- const half: Vec3 = { x: source.base.x / 2, y: source.base.y / 2, z: source.base.z / 2 }
370
-
371
- const draft = withoutEmpty({
372
- version: source.version,
373
- width: source.base.x,
374
- height: source.base.z,
375
- depth: source.base.y,
376
- figureType: source.type,
377
- placement: source.placement,
378
- detailLevel: source.detailLevel,
379
- styleKit: source.styleKit,
380
- /* Opened as stored. A legacy `drive: 'hold'` clip is refused by the format now; stored ones were migrated. */
381
- animations: source.animations?.length ? source.animations : undefined,
382
- parameters: source.parameters?.length ? source.parameters : undefined,
383
- capabilities: source.capabilities,
384
- joints: source.joints?.length ? source.joints : undefined
385
- }) as FigureDraft
386
-
387
- return { draft, parts: source.parts.map(part => partFrom(part, half)) }
388
- }
389
-
390
- function partFrom(part: FigurePart, half: Vec3): PartModel {
391
- const { position, size, rotation } = part.transform
392
- const material = part.material ?? {}
393
-
394
- return withoutEmpty({
395
- type: 'figure-part',
396
- name: part.name,
397
- primitive: part.primitive,
398
-
399
- left: round4(half.x + position.x - size.x / 2),
400
- top: round4(half.z + position.z - size.z / 2),
401
- width: size.x,
402
- height: size.z,
403
- zPos: round4(position.y - size.y / 2),
404
- depth: size.y,
405
-
406
- /*
407
- 라디안은 자르지 않는다.
408
-
409
- 소수 넷째에서 잘랐더니 도로 되돌릴 때 57 배로 커져 45 도가 45.0001 이 됐다.
410
- 자르는 것은 **사람이 읽는 쪽**(도)에서만 한다.
411
- */
412
- rotationX: rotation?.x ? rotation.x * DEG_TO_RAD : undefined,
413
- rotation: rotation?.y ? -rotation.y * DEG_TO_RAD : undefined,
414
- rotationY: rotation?.z ? rotation.z * DEG_TO_RAD : undefined,
415
-
416
- segments: part.segments,
417
- round: part.shape?.round,
418
- path: part.shape?.path,
419
- hollow: part.shape?.hollow,
420
-
421
- token: material.token,
422
- preset: material.preset,
423
- flatShading: material.flatShading,
424
- transparent: material.transparent,
425
- emissive: material.emissive,
426
- surface: material.surface,
427
-
428
- materialSlot: part.materialSlot,
429
- keepRound: part.keepRound,
430
- sizing: part.sizing,
431
- anchor: part.anchor,
432
- repeat: part.repeat,
433
- label: part.label,
434
- capability: part.capability,
435
- parent: part.parent
436
- }) as PartModel
437
- }