@things-factory/figure-ui 10.1.43 → 10.1.45
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-v3-links.ts +303 -0
- package/client/modeller/modeller-route.ts +6 -21
- package/client/modeller/v3-editor.ts +44 -3
- package/client/modeller/v3-gizmo.ts +13 -2
- package/client/modeller/v3-links.ts +138 -0
- package/client/pages/figure-v3-modeller-page.ts +2 -5
- package/client/route.ts +2 -6
- package/dist-client/modeller/figure-v3-links.d.ts +42 -0
- package/dist-client/modeller/figure-v3-links.js +306 -0
- package/dist-client/modeller/figure-v3-links.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +4 -11
- package/dist-client/modeller/modeller-route.js +5 -22
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/v3-editor.d.ts +5 -0
- package/dist-client/modeller/v3-editor.js +46 -2
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-gizmo.js +11 -1
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +54 -0
- package/dist-client/modeller/v3-links.js +110 -0
- package/dist-client/modeller/v3-links.js.map +1 -0
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -5
- package/dist-client/pages/figure-v3-modeller-page.js +2 -5
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +2 -5
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +0 -88
- package/test/figure-source.test.ts +0 -102
- package/test/i18n-prefix-guard.test.ts +0 -24
- package/test/modeller-route.test.ts +14 -25
- package/test/part-edits.test.ts +0 -7
- package/test/scene-budget.test.ts +0 -7
- package/test/v3-gizmo.test.ts +10 -0
- package/test/v3-links.test.ts +123 -0
- package/things-factory.config.js +2 -5
- package/translations/en.json +6 -0
- package/translations/ja.json +11 -1
- package/translations/ko.json +6 -0
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
- package/client/modeller/angle-dial.ts +0 -70
- package/client/modeller/base-box.ts +0 -175
- package/client/modeller/figure-animations.ts +0 -1249
- package/client/modeller/figure-canvas.ts +0 -567
- package/client/modeller/figure-capabilities.ts +0 -379
- package/client/modeller/figure-inspector.ts +0 -2208
- package/client/modeller/figure-parts.ts +0 -479
- package/client/modeller/figure-preview.ts +0 -598
- package/client/modeller/figure-report.ts +0 -966
- package/client/modeller/figure-settings.ts +0 -328
- package/client/modeller/figure-side.ts +0 -272
- package/client/modeller/figure-thumbnail.ts +0 -123
- package/client/modeller/figure-view.ts +0 -152
- package/client/modeller/joint-edits.ts +0 -262
- package/client/modeller/joint-gizmo.ts +0 -180
- package/client/modeller/maturity.ts +0 -288
- package/client/modeller/new-figure-entry.ts +0 -4
- package/client/modeller/pose-defaults.ts +0 -67
- package/client/pages/figure-modeller-page.ts +0 -2082
- package/dist-client/modeller/angle-dial.d.ts +0 -32
- package/dist-client/modeller/angle-dial.js +0 -41
- package/dist-client/modeller/angle-dial.js.map +0 -1
- package/dist-client/modeller/base-box.d.ts +0 -71
- package/dist-client/modeller/base-box.js +0 -110
- package/dist-client/modeller/base-box.js.map +0 -1
- package/dist-client/modeller/figure-animations.d.ts +0 -159
- package/dist-client/modeller/figure-animations.js +0 -1132
- package/dist-client/modeller/figure-animations.js.map +0 -1
- package/dist-client/modeller/figure-canvas.d.ts +0 -150
- package/dist-client/modeller/figure-canvas.js +0 -506
- package/dist-client/modeller/figure-canvas.js.map +0 -1
- package/dist-client/modeller/figure-capabilities.d.ts +0 -42
- package/dist-client/modeller/figure-capabilities.js +0 -349
- package/dist-client/modeller/figure-capabilities.js.map +0 -1
- package/dist-client/modeller/figure-inspector.d.ts +0 -329
- package/dist-client/modeller/figure-inspector.js +0 -2095
- package/dist-client/modeller/figure-inspector.js.map +0 -1
- package/dist-client/modeller/figure-parts.d.ts +0 -64
- package/dist-client/modeller/figure-parts.js +0 -478
- package/dist-client/modeller/figure-parts.js.map +0 -1
- package/dist-client/modeller/figure-preview.d.ts +0 -138
- package/dist-client/modeller/figure-preview.js +0 -549
- package/dist-client/modeller/figure-preview.js.map +0 -1
- package/dist-client/modeller/figure-report.d.ts +0 -165
- package/dist-client/modeller/figure-report.js +0 -952
- package/dist-client/modeller/figure-report.js.map +0 -1
- package/dist-client/modeller/figure-settings.d.ts +0 -39
- package/dist-client/modeller/figure-settings.js +0 -317
- package/dist-client/modeller/figure-settings.js.map +0 -1
- package/dist-client/modeller/figure-side.d.ts +0 -78
- package/dist-client/modeller/figure-side.js +0 -317
- package/dist-client/modeller/figure-side.js.map +0 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
- package/dist-client/modeller/figure-thumbnail.js +0 -95
- package/dist-client/modeller/figure-thumbnail.js.map +0 -1
- package/dist-client/modeller/figure-view.d.ts +0 -118
- package/dist-client/modeller/figure-view.js +0 -78
- package/dist-client/modeller/figure-view.js.map +0 -1
- package/dist-client/modeller/joint-edits.d.ts +0 -49
- package/dist-client/modeller/joint-edits.js +0 -223
- package/dist-client/modeller/joint-edits.js.map +0 -1
- package/dist-client/modeller/joint-gizmo.d.ts +0 -34
- package/dist-client/modeller/joint-gizmo.js +0 -158
- package/dist-client/modeller/joint-gizmo.js.map +0 -1
- package/dist-client/modeller/maturity.d.ts +0 -55
- package/dist-client/modeller/maturity.js +0 -212
- package/dist-client/modeller/maturity.js.map +0 -1
- package/dist-client/modeller/new-figure-entry.d.ts +0 -2
- package/dist-client/modeller/new-figure-entry.js +0 -5
- package/dist-client/modeller/new-figure-entry.js.map +0 -1
- package/dist-client/modeller/pose-defaults.d.ts +0 -9
- package/dist-client/modeller/pose-defaults.js +0 -35
- package/dist-client/modeller/pose-defaults.js.map +0 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -419
- package/dist-client/pages/figure-modeller-page.js +0 -2060
- package/dist-client/pages/figure-modeller-page.js.map +0 -1
- package/test/angle-dial.test.ts +0 -78
- package/test/base-box.test.ts +0 -235
- package/test/joint-edits.test.ts +0 -165
- package/test/joint-gizmo.test.ts +0 -174
- package/test/joint-needle.test.ts +0 -162
- package/test/modeller-undo-redo.test.ts +0 -33
- package/test/new-figure-entry.test.ts +0 -15
- package/test/pose-defaults.test.ts +0 -71
- package/test/preview-board-depth.test.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.45",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"clean": "npm run clean:server && npm run clean:client"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@hatiolab/figure-model": "^0.1.
|
|
29
|
+
"@hatiolab/figure-model": "^0.1.47",
|
|
30
30
|
"@hatiolab/things-scene": "^10.1.26",
|
|
31
31
|
"@material/web": "^2.0.0",
|
|
32
32
|
"@operato/data-grist": "^10.0.0",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@operato/shell": "^10.0.0",
|
|
38
38
|
"@operato/styles": "^10.0.0",
|
|
39
39
|
"@operato/utils": "^10.0.0",
|
|
40
|
-
"@things-factory/figure-service": "^10.1.
|
|
40
|
+
"@things-factory/figure-service": "^10.1.45",
|
|
41
41
|
"three": "^0.185.1"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "d72e1fd1285797fcd7839d97a18e31f5bf076ae3"
|
|
44
44
|
}
|
|
@@ -137,17 +137,6 @@ test('피드백은 후보 세션의 선택과 측정값만 전송한다', () =>
|
|
|
137
137
|
* as it stands right now — unsaved edits included — so the candidate is a revision of what is open
|
|
138
138
|
* rather than something invented from the prompt alone. And it saves nothing by itself.
|
|
139
139
|
*/
|
|
140
|
-
test('모델러는 AI 문을 스스로 두지 않고, 물으면 지금 정본을 답한다', () => {
|
|
141
|
-
const path = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
142
|
-
const source = readFileSync(path, 'utf8')
|
|
143
|
-
|
|
144
|
-
assert.doesNotMatch(source, /openOverlay\('figure-ai-dock'/, '앱 머리줄에 이미 있는 문을 여기 또 두지 않는다')
|
|
145
|
-
assert.match(source, /addEventListener\('figure-ai-context-request'/)
|
|
146
|
-
assert.match(source, /detail: \{ figureId: this\.figure\?\.id, source: this\.folded \}/)
|
|
147
|
-
assert.doesNotMatch(source, /<figure-ask/, 'the inline ask form is gone; the dock is the entrance')
|
|
148
|
-
assert.doesNotMatch(source, /proposeFigure\(/, 'the page must not call the propose mutation itself')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
140
|
test('그림이 붙은 AI 요청은 JSON이 아니라 GraphQL multipart Upload 링크를 탄다', () => {
|
|
152
141
|
const path = fileURLToPath(new URL('../client/graphql/index.ts', import.meta.url))
|
|
153
142
|
const source = readFileSync(path, 'utf8')
|
|
@@ -156,83 +145,6 @@ test('그림이 붙은 AI 요청은 JSON이 아니라 GraphQL multipart Upload
|
|
|
156
145
|
assert.match(propose, /context: \{ hasUpload: !!request\.image \}/)
|
|
157
146
|
})
|
|
158
147
|
|
|
159
|
-
test('모델러의 수락·거절 피드백은 다음 인라인 후보 요청에도 같은 Figure 세션에서만 전달한다', () => {
|
|
160
|
-
const path = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
161
|
-
const source = readFileSync(path, 'utf8')
|
|
162
|
-
|
|
163
|
-
assert.match(source, /new CustomEvent\('figure-ai-feedback'/)
|
|
164
|
-
assert.doesNotMatch(source, /<figure-ask/)
|
|
165
|
-
assert.match(source, /this\.proposalFeedback = \[\.\.\.this\.proposalFeedback, feedback\]\.slice\(-5\)/)
|
|
166
|
-
assert.match(source, /changed\.has\('figure'\).*this\.proposalFeedback = \[\]/s)
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
test('발행 게이트 수정안은 원본을 바꾸지 않고 후보로만 연다', () => {
|
|
170
|
-
const historyPath = fileURLToPath(new URL('../client/modeller/figure-history-panel.ts', import.meta.url))
|
|
171
|
-
const modellerPath = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
172
|
-
const sessionPath = fileURLToPath(new URL('../client/modeller/proposal-session.ts', import.meta.url))
|
|
173
|
-
const history = readFileSync(historyPath, 'utf8')
|
|
174
|
-
const modeller = readFileSync(modellerPath, 'utf8')
|
|
175
|
-
const session = readFileSync(sessionPath, 'utf8')
|
|
176
|
-
|
|
177
|
-
assert.match(history, /i18next\.t\('figure\.button\.preview-fix'/)
|
|
178
|
-
assert.equal(JSON.parse(readFileSync(new URL('../translations/ko.json', import.meta.url), 'utf8'))['figure.button.preview-fix'].startsWith('수정안 미리보기'), true)
|
|
179
|
-
assert.match(history, /i18next\.t\('figure\.button\.apply-fix'/)
|
|
180
|
-
assert.match(history, /new CustomEvent\('apply-sizing-fix'/)
|
|
181
|
-
assert.match(history, /new CustomEvent\('preview-sizing-fix'/)
|
|
182
|
-
assert.match(modeller, /@preview-sizing-fix=\$\{\(e: CustomEvent\) => this\.previewSizingFix\(e\.detail\)\}/)
|
|
183
|
-
assert.match(modeller, /private previewSizingFix\(fix: FigureGateFix\)/)
|
|
184
|
-
assert.match(modeller, /private applySizingFix\(fix: FigureGateFix\)/)
|
|
185
|
-
assert.match(modeller, /if \(!fix\.safe \|\| !this\.folded\) return/)
|
|
186
|
-
assert.match(modeller, /this\.proposalSession = proposalSessions\.openSizingFix\(this\.folded, fix\)/)
|
|
187
|
-
assert.match(modeller, /private take\(\)[\s\S]*applyProposal\(this\.folded, this\.proposalSession\.source, this\.proposalSession\.picked\)[\s\S]*this\.dirty = true/)
|
|
188
|
-
assert.match(session, /export function openSizingFix[\s\S]*anchor: \{ \.\.\.\(part\.anchor \?\? \{\}\), \[fix\.axis\]: fix\.to \}/)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
test('새 Figure 경로는 MDI에 남아 있는 직전 Figure를 재사용하지 않고 빈 초안을 만든다', () => {
|
|
192
|
-
const path = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
193
|
-
const source = readFileSync(path, 'utf8')
|
|
194
|
-
const pageUpdated = source.slice(source.indexOf('async pageUpdated'), source.indexOf(' private async load'))
|
|
195
|
-
const startNew = source.slice(source.indexOf(' private startNew()'), source.indexOf(' private rename'))
|
|
196
|
-
|
|
197
|
-
// resourceId가 없다는 것은 명시적으로 「새 Figure」를 뜻한다.
|
|
198
|
-
assert.match(pageUpdated, /if \(!this\.figure\)[\s\S]*this\.startNew\(\)/)
|
|
199
|
-
assert.match(pageUpdated, /if \(this\.figure\.id\)[\s\S]*this\.startNew\(\)/)
|
|
200
|
-
// 새 초안은 저장 식별자·선택·기존 부품·진단을 모두 초기화한다.
|
|
201
|
-
assert.match(startNew, /this\.figure = \{ id: '', type: draftType, name: '' \}/)
|
|
202
|
-
assert.match(startNew, /const \{ draft, parts \} = startingModel\(draftType\)/)
|
|
203
|
-
assert.match(startNew, /this\.selected = -1/)
|
|
204
|
-
assert.match(startNew, /this\.dirty = true/)
|
|
205
|
-
assert.match(startNew, /this\.violations = \[\]/)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
test('빈 새 Figure는 캔버스의 직전 씬을 폐기하고 안내 상태로 전환한다', () => {
|
|
209
|
-
const path = fileURLToPath(new URL('../client/modeller/figure-canvas.ts', import.meta.url))
|
|
210
|
-
const source = readFileSync(path, 'utf8')
|
|
211
|
-
const standUp = source.slice(source.indexOf(' private standUp()'), source.indexOf(' /**\n * 지금 판 위에'))
|
|
212
|
-
|
|
213
|
-
assert.match(standUp, /if \(!draft\) \{[\s\S]*this\.teardown\(\)/)
|
|
214
|
-
assert.match(standUp, /if \(this\.parts\.length === 0\) \{[\s\S]*this\.teardown\(\)[\s\S]*canvas-appears-once-a-part-exists/)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
test('캔버스는 figureId를 판별에 포함하고 없어진 부품을 씬에서 완전 제거한다', () => {
|
|
218
|
-
const canvasPath = fileURLToPath(new URL('../client/modeller/figure-canvas.ts', import.meta.url))
|
|
219
|
-
const canvasSource = readFileSync(canvasPath, 'utf8')
|
|
220
|
-
const modellerPath = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
221
|
-
const modellerSource = readFileSync(modellerPath, 'utf8')
|
|
222
|
-
|
|
223
|
-
// figureId 변경 시 새 씬을 세우도록 signature에 figureId가 포함된다
|
|
224
|
-
assert.match(canvasSource, /@property\(\{ type: String \}\) figureId\?: string/)
|
|
225
|
-
assert.match(canvasSource, /signature = `\$\{this\.figureId \?\? ''\}:\$\{draft\.width\}x\$\{draft\.height\}x\$\{draft\.depth\}/)
|
|
226
|
-
|
|
227
|
-
// 없어진 부품은 root.removeComponent와 component.dispose로 제거된다
|
|
228
|
-
assert.match(canvasSource, /root\.removeComponent\?\.\(component\)/)
|
|
229
|
-
assert.match(canvasSource, /component\.dispose\?\.\(\)/)
|
|
230
|
-
|
|
231
|
-
// 모델러 페이지는 figure-canvas에 figureId를 전달한다
|
|
232
|
-
assert.match(modellerSource, /\.figureId=\$\{this\.figure\?\.id \|\| this\.figure\?\.type\}/)
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
|
|
236
148
|
/*
|
|
237
149
|
* ── The whole round trip, not just one hop ───────────────────────────────────
|
|
238
150
|
*
|
|
@@ -502,112 +502,10 @@ describe('배치는 씬의 좌표 모드가 아니다', () => {
|
|
|
502
502
|
)
|
|
503
503
|
})
|
|
504
504
|
|
|
505
|
-
it('편집 캔버스가 씬에 배치를 안 싣는다', () => {
|
|
506
|
-
const source = read('../client/modeller/figure-canvas.ts')
|
|
507
|
-
|
|
508
|
-
assert.doesNotMatch(
|
|
509
|
-
source,
|
|
510
|
-
/placement:/,
|
|
511
|
-
'편집 캔버스가 배치를 씬에 실으면 저작자가 적은 높이의 뜻이 드롭다운마다 바뀐다'
|
|
512
|
-
)
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
it('setPlacement 가 부품을 안 건드린다', () => {
|
|
516
|
-
const source = read('../client/modeller/figure-inspector.ts')
|
|
517
|
-
const body = source.slice(source.indexOf('private setPlacement'), source.indexOf('private setPlacement') + 500)
|
|
518
|
-
|
|
519
|
-
assert.match(body, /detail: \{ draft \}/, '배치는 필드 하나만 쓴다')
|
|
520
|
-
assert.doesNotMatch(body, /zPos/, '배치를 바꾸면서 부품 높이를 다시 계산하면 안 된다')
|
|
521
|
-
})
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
describe('미리보기가 높이를 씬에게 맡긴다', () => {
|
|
525
|
-
const read = (rel: string) => readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), 'utf8')
|
|
526
|
-
const preview = read('../client/modeller/figure-preview.ts')
|
|
527
|
-
|
|
528
|
-
/*
|
|
529
|
-
배치 기준을 높이로 옮기는 규칙은 씬이 갖는다 — `FigureRealObject.effectiveZPos` 가 청사진의
|
|
530
|
-
`placement` 와 레이어의 `heights` 로 정한다(ADR-0045). 미리보기는 「도면에 놓았을 때」를
|
|
531
|
-
말하는 자리이므로 도면과 같은 길로 세워야 한다.
|
|
532
|
-
|
|
533
|
-
이 화면이 규칙을 한 벌 더 들고 있었다. 그러면 씬의 규칙이 바뀌어도 이 화면만 옛 답을 내고,
|
|
534
|
-
저작자는 도면에 놓고 나서야 다르다는 것을 안다.
|
|
535
|
-
*/
|
|
536
|
-
it('인스턴스에 zPos 를 적지 않는다 — 적으면 적은 값이 이긴다', () => {
|
|
537
|
-
const at = preview.indexOf('const components = PREVIEW_WAYS.map')
|
|
538
|
-
const built = preview.slice(at, preview.indexOf('const draft = {', at))
|
|
539
|
-
|
|
540
|
-
assert.doesNotMatch(built, /zPos/, '여기서 높이를 정하면 씬이 무엇을 할지가 이 화면에서만 안 보인다')
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
it('배치를 높이로 옮기는 계산을 들고 있지 않다', () => {
|
|
544
|
-
assert.doesNotMatch(preview, /bottomOf|zPosOf/, '규칙이 두 벌이면 갈린다')
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
it('천장 높이를 씬에 실어 준다 — 값이 사는 자리가 거기다', () => {
|
|
548
|
-
assert.match(preview, /heights: \{ floor: 0, ceiling: view\.ceilingHeight \}/)
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
it('미리보기가 좌표 모드를 보기 설정에서 받는다 — 도형에서가 아니라', () => {
|
|
552
|
-
assert.match(preview, /placement: view\.placement/, '보기 설정의 좌표 모드를 씬에 실어야 한다')
|
|
553
|
-
assert.doesNotMatch(preview, /placement: (source|toScenePlacement)/, '도형의 배치를 씬 모드로 쓰면 안 된다')
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
it('미리보기가 보기 설정의 조명·환경을 따른다', () => {
|
|
557
|
-
assert.match(preview, /\.\.\.sceneLook\(view\)/, '하드코딩하면 편집 캔버스와 두 화면이 갈린다')
|
|
558
|
-
assert.doesNotMatch(preview, /environment: 'studio'/, '환경이 하드코딩으로 남아 있다')
|
|
559
|
-
})
|
|
560
505
|
})
|
|
561
506
|
|
|
562
507
|
describe('보기 설정이 편집 캔버스로 새지 않는다', () => {
|
|
563
508
|
const read = (rel: string) => readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), 'utf8')
|
|
564
509
|
|
|
565
|
-
it('sceneLook 이 좌표 모드와 천장 높이를 뺀다', () => {
|
|
566
|
-
const view = read('../client/modeller/figure-view.ts')
|
|
567
|
-
assert.match(view, /const \{ placement, ceilingHeight, \.\.\.look \} = view/)
|
|
568
|
-
})
|
|
569
|
-
|
|
570
|
-
it('편집 캔버스가 rest spread 로 보기 설정을 넘기지 않는다', () => {
|
|
571
|
-
/*
|
|
572
|
-
한동안 `const { grid, ..., ...light } = view` 였다. 그러면 설정을 하나 더할 때마다
|
|
573
|
-
그것이 두 화면에 **말없이** 실린다 — 좌표 모드가 바로 그 길로 들어갈 뻔했다.
|
|
574
|
-
*/
|
|
575
|
-
const canvas = read('../client/modeller/figure-canvas.ts')
|
|
576
|
-
const fn = canvas.slice(canvas.indexOf('private sceneView'), canvas.indexOf('private sceneView') + 200)
|
|
577
|
-
|
|
578
|
-
assert.match(fn, /sceneLook\(/)
|
|
579
|
-
assert.doesNotMatch(fn, /\.\.\.light/, '나머지를 통째로 넘기면 새 설정이 조용히 실린다')
|
|
580
|
-
})
|
|
581
510
|
})
|
|
582
511
|
|
|
583
|
-
/*
|
|
584
|
-
* ── 막는 것과 알리는 것을 화면이 갈라야 한다 ──────────────────────────────────
|
|
585
|
-
*
|
|
586
|
-
* 판정 패널은 위반을 전부 같은 `info` 로 그렸다. 기준 상자 위반 둘이 발행을 막게 된 뒤에도
|
|
587
|
-
* 그대로라, 저작자는 다섯 줄의 알림을 보고 **발행을 눌러 보고 나서야** 막힌 것을 안다.
|
|
588
|
-
*
|
|
589
|
-
* 등급은 형식이 안다(`blocksRelease`). 화면이 제 목록을 들면 코드가 하나 늘 때 조용히
|
|
590
|
-
* 「알림」 쪽으로 떨어진다 — 안전하지 않은 쪽으로 기우는 기본값이다.
|
|
591
|
-
*/
|
|
592
|
-
describe('판정 패널이 발행을 막는 것을 갈라 보인다', () => {
|
|
593
|
-
const read = (rel: string) => readFileSync(join(dirname(fileURLToPath(import.meta.url)), rel), 'utf8')
|
|
594
|
-
const report = read('../client/modeller/figure-report.ts')
|
|
595
|
-
|
|
596
|
-
it('등급을 형식에서 받는다 — 제 목록을 들지 않는다', () => {
|
|
597
|
-
assert.match(report, /blocksRelease/, '형식이 어느 위반이 막는지 안다')
|
|
598
|
-
assert.doesNotMatch(
|
|
599
|
-
report,
|
|
600
|
-
/'part-outside-base'|'parts-off-placement-face'/,
|
|
601
|
-
'화면이 코드 이름을 적으면 형식과 두 벌이 된다'
|
|
602
|
-
)
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
it('막는 것은 다른 아이콘으로 그린다', () => {
|
|
606
|
-
assert.match(report, /v\.blocking \? 'block' : 'info'/, '같은 아이콘이면 갈린 뜻이 화면에 안 나온다')
|
|
607
|
-
})
|
|
608
|
-
|
|
609
|
-
it('얼마나 넘쳤는지 보여 준다', () => {
|
|
610
|
-
/* 「상자를 벗어난다」만으로는 2mm 인지 250mm 인지 모르고, 그 둘은 고치는 품이 다르다. */
|
|
611
|
-
assert.match(report, /v\.actual/, '넘친 양을 버리면 저작자가 무엇부터 고칠지 못 고른다')
|
|
612
|
-
})
|
|
613
|
-
})
|
|
@@ -122,23 +122,6 @@ test('★ 한국어를 defaultValue 로 들고 있지 않다 — 번역 파일
|
|
|
122
122
|
assert.deepEqual(hard, [], `한국어 defaultValue:\n ${hard.join('\n ')}`)
|
|
123
123
|
})
|
|
124
124
|
|
|
125
|
-
/*
|
|
126
|
-
이어 붙이는 자리는 값을 코드에서 읽어야 알 수 있어 여기서 전수 검사하지 않는다. 대신 **통이
|
|
127
|
-
갈린 짝**을 못 박는다 — 사다리 이름은 `label`, 그 설명은 `text` 다. 한 번 어긋났고, 그때
|
|
128
|
-
사다리 설명 세 줄이 화면에 키로 떴다. 넓은 앞머리(`figure.text.`)가 미사용 검사를 덮어
|
|
129
|
-
통과시켰다.
|
|
130
|
-
*/
|
|
131
|
-
test('★ 통이 갈린 짝 — 사다리 이름과 설명', () => {
|
|
132
|
-
for (const rung of ['none', 'hold', 'flow']) {
|
|
133
|
-
assert.ok(`figure.label.rung-${rung}` in KO, `figure.label.rung-${rung} 이 없다`)
|
|
134
|
-
assert.ok(`figure.text.rung-${rung}-note` in KO, `figure.text.rung-${rung}-note 이 없다`)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const source = readFileSync(join(CLIENT, 'modeller/figure-capabilities.ts'), 'utf-8')
|
|
138
|
-
assert.ok(source.includes('figure.label.rung-${which}'), '사다리 이름을 label 통에서 찾지 않는다')
|
|
139
|
-
assert.ok(source.includes('figure.text.rung-${which}-note'), '사다리 설명을 text 통에서 찾지 않는다')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
125
|
/*
|
|
143
126
|
애니메이션 탭도 열거 값을 키에 이어 붙인다. 사다리와 **같은 모양의 함정**이라 같은 못을 박는다.
|
|
144
127
|
|
|
@@ -168,13 +151,6 @@ test('★ 열거 값마다 짝이 다 있다 — 경로 · 보간', () => {
|
|
|
168
151
|
assert.deepEqual(missing, [], `이어 붙여 부르는 키가 번역에 없다:\n ${missing.join('\n ')}`)
|
|
169
152
|
})
|
|
170
153
|
|
|
171
|
-
test('★ 애니메이션 탭이 그 통에서 찾는다 — 이름은 label, 설명은 text', () => {
|
|
172
|
-
const source = readFileSync(join(CLIENT, 'modeller/figure-animations.ts'), 'utf-8')
|
|
173
|
-
|
|
174
|
-
assert.ok(source.includes("'figure.label.path-' + path"), '경로 이름을 label 통에서 찾지 않는다')
|
|
175
|
-
assert.ok(source.includes("'figure.text.path-' + channel.path + '-unit'"), '단위 설명을 text 통에서 찾지 않는다')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
154
|
/*
|
|
179
155
|
대체 언어(`fallbackLng`)는 **en** 이다 — 클라이언트(`@operato/i18n` 의 config)와 서버
|
|
180
156
|
(`i18n-base` 의 미들웨어) 양쪽 다.
|
|
@@ -1,42 +1,31 @@
|
|
|
1
1
|
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 목록은 모든 도형을 V2 모델러로 보냈다. V3 로 바꾼 복사본에서는 어느 카드를 눌러도 V2 화면이 「Figure를 열지
|
|
5
|
-
* 못했습니다 — Cannot read properties of undefined (reading 'x')」로 멈췄고, V3 모델러는 주소를 쳐야만 닿았다.
|
|
2
|
+
* One modeller since ADR-0087 step 5 (2026-09-24, user decision: V2 is not kept). The catalogue, a new figure and
|
|
3
|
+
* an old address all open it.
|
|
6
4
|
*/
|
|
7
5
|
import { test } from 'node:test'
|
|
8
6
|
import assert from 'node:assert/strict'
|
|
9
7
|
import { readFileSync } from 'node:fs'
|
|
10
8
|
import { fileURLToPath } from 'node:url'
|
|
11
9
|
|
|
12
|
-
import {
|
|
10
|
+
import { modellerPathOf } from '../client/modeller/modeller-route.ts'
|
|
13
11
|
|
|
14
12
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
15
13
|
|
|
16
|
-
test('
|
|
17
|
-
assert.equal(modellerPathOf({ id: 'a'
|
|
18
|
-
assert.equal(modellerPathOf(
|
|
19
|
-
assert.equal(modellerPathOf(), 'figure-v3-modeller')
|
|
20
|
-
assert.equal(modellerPathOf({ id: 'a', format: null }), 'figure-modeller/a', '서버가 형식을 모르면 V2 화면이 읽지 못한 까닭을 말한다')
|
|
14
|
+
test('every figure and a new one open the one modeller', () => {
|
|
15
|
+
assert.equal(modellerPathOf({ id: 'a' }), 'figure-modeller/a')
|
|
16
|
+
assert.equal(modellerPathOf(), 'figure-modeller')
|
|
21
17
|
})
|
|
22
18
|
|
|
23
|
-
test('V3
|
|
24
|
-
|
|
25
|
-
assert.
|
|
26
|
-
|
|
27
|
-
assert.
|
|
19
|
+
test('the route and the shell table send both addresses to the V3 page, and the V2 page is gone', () => {
|
|
20
|
+
const route = read('../client/route.ts')
|
|
21
|
+
assert.doesNotMatch(route, /figure-modeller-page/)
|
|
22
|
+
const config = read('../things-factory.config.js')
|
|
23
|
+
assert.match(config, /tagname: 'figure-v3-modeller-page', page: 'figure-modeller'/)
|
|
24
|
+
assert.match(config, /tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller'/)
|
|
25
|
+
assert.doesNotMatch(config, /'figure-modeller-page'/)
|
|
28
26
|
})
|
|
29
27
|
|
|
30
|
-
test('
|
|
28
|
+
test('the catalogue opens figures through the one path', () => {
|
|
31
29
|
const list = read('../client/pages/figure-list-page.ts')
|
|
32
30
|
assert.match(list, /navigate\(modellerPathOf\(figure\)\)/)
|
|
33
|
-
assert.match(list, /this\.openModeller\(record\)/, '카드는 id 가 아니라 형식을 가진 행을 넘긴다')
|
|
34
|
-
assert.doesNotMatch(list, /navigate\(id \? `figure-modeller/)
|
|
35
|
-
assert.match(read('../client/graphql/index.ts'), /groups\s+format\s+thumbnailUpdatedAt/, '목록 질의가 형식을 받는다')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('V2 주소로 온 V3 도형은 V3 모델러로 넘긴다 — 뒤로 가기가 다시 V2 로 돌지 않게 바꿔 넣는다', () => {
|
|
39
|
-
const v2 = read('../client/pages/figure-modeller-page.ts')
|
|
40
|
-
const load = v2.slice(v2.indexOf('private async load('), v2.indexOf('fromFigureSource(JSON.parse'))
|
|
41
|
-
assert.match(load, /if \(isV3Source\(figure\.source\)\) \{\s*navigate\(modellerPathOf\(\{ id, format: 'v3' \}\), true\)\s*return/)
|
|
42
31
|
})
|
package/test/part-edits.test.ts
CHANGED
|
@@ -89,13 +89,6 @@ describe('씬에서 온 부품을 밀리미터로 맞춘다', () => {
|
|
|
89
89
|
expect(roundPart(once)).toEqual(once)
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
it('캔버스가 씬의 변경을 받을 때 그것을 지난다 — 안 부르면 위 넷이 전부 초록인 채로 소수가 남는다', () => {
|
|
93
|
-
const canvas = readFileSync(fileURLToPath(new URL('../client/modeller/figure-canvas.ts', import.meta.url)), 'utf8')
|
|
94
|
-
|
|
95
|
-
assert.ok(/roundPart/.test(canvas), 'import')
|
|
96
|
-
assert.ok(/this\.tell\('part-changed', \{ name, part: roundPart\(/.test(canvas), '올려 보내기 전에 맞춘다')
|
|
97
|
-
})
|
|
98
|
-
|
|
99
92
|
it('크기·자리 밖의 값은 건드리지 않는다', () => {
|
|
100
93
|
const kept = roundPart(part({ name: 'arm', token: 'palette.dark', segments: 12, zPos: 3.4 }))
|
|
101
94
|
expect(kept.name).toBe('arm')
|
|
@@ -66,13 +66,6 @@ describe('한 화면이 감당하는 양', () => {
|
|
|
66
66
|
describe('놓는 개수 기준 문구가 화면에 없다', () => {
|
|
67
67
|
const HERE = fileURLToPath(new URL('.', import.meta.url))
|
|
68
68
|
|
|
69
|
-
it('보고서가 「N개 기준」 칸을 그리지 않는다', () => {
|
|
70
|
-
const report = readFileSync(join(HERE, '../client/modeller/figure-report.ts'), 'utf8')
|
|
71
|
-
|
|
72
|
-
assert.ok(!/at-100|at-10k/.test(report), '개수 기준 칸')
|
|
73
|
-
assert.ok(!/costOf\(blueprint, \d/.test(report), '개수를 넣어 셈하지 않는다')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
69
|
it('번역에 그 낱말이 남아 있지 않다', () => {
|
|
77
70
|
const ko = JSON.parse(readFileSync(join(HERE, '../translations/ko.json'), 'utf8')) as Record<string, string>
|
|
78
71
|
const left = Object.entries(ko).filter(([key]) => /at-100|at-10k/.test(key))
|
package/test/v3-gizmo.test.ts
CHANGED
|
@@ -95,3 +95,13 @@ test('기즈모의 말은 다섯 말 모두에 있다', () => {
|
|
|
95
95
|
for (const mode of ['translate', 'rotate', 'scale']) assert.ok(table[`figure.label.gizmo-${mode}`], `${lang}: ${mode}`)
|
|
96
96
|
}
|
|
97
97
|
})
|
|
98
|
+
|
|
99
|
+
test('떼어 낸 부품은 다시 옮길 수 있다 — 움직임이 있는 부품은 아니다', () => {
|
|
100
|
+
let asset = probe()
|
|
101
|
+
asset = addV3Box(asset, { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 300, z: 0 } })
|
|
102
|
+
const fastened = applyV3Authoring(asset, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'body', face: 'top' } })
|
|
103
|
+
const free = applyV3Authoring(fastened, { kind: 'detach', part: 'lid' })
|
|
104
|
+
assert.equal(gizmoReachOf(free as never, 'lid').translate, true)
|
|
105
|
+
const moving = applyV3Authoring(free, { kind: 'add-motion', part: 'lid', motion: { kind: 'slide', axis: 'x' }, state: { id: 'lid-slide', unit: 'mm', min: 0, max: 100, start: 0 } })
|
|
106
|
+
assert.equal(gizmoReachOf(moving as never, 'lid').translate, false)
|
|
107
|
+
})
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **V3 부품이 무엇에 묶여 있나 — 크기 따르기 · 붙이기 · 움직임** (V3 동등표 ④ ⑤ ⑥, 2026-09-24).
|
|
3
|
+
*
|
|
4
|
+
* 세 칸이 보여 주는 것은 figure-model 명령이 남긴 그래프를 거꾸로 읽은 것이다. 명령을 실제로 적용한 뒤 읽어서,
|
|
5
|
+
* 화면이 명령과 같은 말을 하는지 잰다.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
|
|
10
|
+
import { addV3Box, addV3Primitive, applyV3Authoring, createV3Asset } from '@hatiolab/figure-model'
|
|
11
|
+
import type { V3Asset } from '@hatiolab/figure-model'
|
|
12
|
+
|
|
13
|
+
import { canFasten, canFollow, fastenedTo, followOf, hasInstanceSize, motionOf, positionsWorkedOut } from '../client/modeller/v3-links.ts'
|
|
14
|
+
|
|
15
|
+
const pair = (): V3Asset => {
|
|
16
|
+
let asset = createV3Asset('Pair')
|
|
17
|
+
asset = addV3Box(asset, { id: 'plate', size: { x: 600, y: 100, z: 400 }, position: { x: 0, y: 50, z: 0 } })
|
|
18
|
+
asset = addV3Box(asset, { id: 'block', size: { x: 200, y: 60, z: 120 }, position: { x: 0, y: 130, z: 0 } })
|
|
19
|
+
const model = asset.document.model
|
|
20
|
+
for (const axis of ['x', 'y', 'z']) {
|
|
21
|
+
model.inputs.push({ id: `size.${axis}`, unit: 'mm', min: 100, max: 20000, role: 'design' } as never)
|
|
22
|
+
asset.designInputs[`size.${axis}`] = axis === 'y' ? 100 : axis === 'x' ? 600 : 400
|
|
23
|
+
}
|
|
24
|
+
return asset
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('크기 — 제 치수 · 인스턴스 크기 · 다른 부품의 치수를 가려 읽는다', () => {
|
|
28
|
+
const asset = pair()
|
|
29
|
+
assert.deepEqual(followOf(asset, 'plate', 'x'), { of: 'own' })
|
|
30
|
+
const byInstance = applyV3Authoring(asset, { kind: 'link-dimension', part: 'plate', axis: 'x', source: { of: 'instance', axis: 'x' } })
|
|
31
|
+
assert.deepEqual(followOf(byInstance, 'plate', 'x'), { of: 'instance', axis: 'x' })
|
|
32
|
+
const byPart = applyV3Authoring(byInstance, { kind: 'link-dimension', part: 'block', axis: 'x', source: { of: 'part', part: 'plate', axis: 'x' }, times: 0.5 })
|
|
33
|
+
assert.deepEqual(followOf(byPart, 'block', 'x'), { of: 'part', part: 'plate', axis: 'x' }, '배율이 끼어도 원천을 찾는다')
|
|
34
|
+
const back = applyV3Authoring(byPart, { kind: 'unlink-dimension', part: 'block', axis: 'x', mm: 300 })
|
|
35
|
+
assert.deepEqual(followOf(back, 'block', 'x'), { of: 'own' }, '떼면 다시 제 치수')
|
|
36
|
+
assert.equal(hasInstanceSize(asset, 'y'), true)
|
|
37
|
+
assert.equal(hasInstanceSize(createV3Asset('Bare'), 'y'), false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('붙이기 — 누구에게 붙었나, 붙일 수 있는 모양인가', () => {
|
|
41
|
+
const asset = pair()
|
|
42
|
+
assert.equal(fastenedTo(asset, 'block'), null)
|
|
43
|
+
const fastened = applyV3Authoring(asset, { kind: 'attach', part: 'block', face: 'bottom', to: { part: 'plate', face: 'top' } })
|
|
44
|
+
assert.equal(fastenedTo(fastened, 'block'), 'plate')
|
|
45
|
+
assert.equal(fastenedTo(applyV3Authoring(fastened, { kind: 'detach', part: 'block' }), 'block'), null)
|
|
46
|
+
const withDrum = addV3Primitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
47
|
+
assert.equal(canFasten(withDrum, 'block'), true)
|
|
48
|
+
assert.equal(canFasten(withDrum, 'drum'), true, '원기둥도 면으로 붙인다(figure-model 0.1.47)')
|
|
49
|
+
assert.equal(canFollow(withDrum, 'drum'), false, '원기둥의 치수 셋은 X·Y·Z 가 아니라 따르기는 상자만')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('움직임 — 미끄러짐 · 돌기, 축, 무엇이 모느냐를 읽는다', () => {
|
|
53
|
+
const asset = pair()
|
|
54
|
+
assert.equal(motionOf(asset, 'block'), undefined)
|
|
55
|
+
const sliding = applyV3Authoring(asset, {
|
|
56
|
+
kind: 'add-motion',
|
|
57
|
+
part: 'block',
|
|
58
|
+
motion: { kind: 'slide', axis: 'x' },
|
|
59
|
+
state: { id: 'travel', unit: 'mm', min: 0, max: 300, start: 0 }
|
|
60
|
+
})
|
|
61
|
+
const motion = motionOf(sliding, 'block')!
|
|
62
|
+
assert.equal(motion.kind, 'slide')
|
|
63
|
+
assert.equal(motion.axis, 'x')
|
|
64
|
+
assert.equal(motion.control?.id, 'travel')
|
|
65
|
+
assert.equal(motion.control?.max, 300)
|
|
66
|
+
const turning = applyV3Authoring(asset, {
|
|
67
|
+
kind: 'add-motion',
|
|
68
|
+
part: 'block',
|
|
69
|
+
motion: { kind: 'turn', axis: 'y' },
|
|
70
|
+
state: { id: 'swing', unit: 'deg', min: -90, max: 90, start: 0 }
|
|
71
|
+
})
|
|
72
|
+
assert.deepEqual([motionOf(turning, 'block')!.kind, motionOf(turning, 'block')!.axis], ['turn', 'y'])
|
|
73
|
+
assert.equal(motionOf(applyV3Authoring(sliding, { kind: 'remove-motion', part: 'block' }), 'block'), undefined)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
import { readFileSync } from 'node:fs'
|
|
77
|
+
import { fileURLToPath } from 'node:url'
|
|
78
|
+
import { choiceOf } from '../client/modeller/v3-links.ts'
|
|
79
|
+
|
|
80
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
81
|
+
|
|
82
|
+
test('고르개의 값은 지금 따르는 것을 가리킨다', () => {
|
|
83
|
+
assert.equal(choiceOf({ of: 'own' }), 'own')
|
|
84
|
+
assert.equal(choiceOf({ of: 'instance', axis: 'y' }), 'instance:y')
|
|
85
|
+
assert.equal(choiceOf({ of: 'part', part: 'plate', axis: 'x' }), 'part:plate:x')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('편집기는 세 칸의 명령을 세션의 이력 하나로 돌리고, 만날 수 있는 거절을 사람의 말로 한다', () => {
|
|
89
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
90
|
+
assert.match(editor, /@v3-command=\$\{\(e: CustomEvent\) => this\.linkCommand\(e\.detail\.action\)\}/)
|
|
91
|
+
assert.match(editor, /this\.session!\.command\(action\)/)
|
|
92
|
+
assert.match(editor, /code === 'DIMENSION_CYCLE'[\s\S]*?figure\.text\.follow-would-loop/)
|
|
93
|
+
assert.match(editor, /code === 'ATTACH_CYCLE'[\s\S]*?figure\.text\.fasten-would-loop/)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('떼면 지금 크기로 고정한다 — 따르던 값을 잃지 않는다', () => {
|
|
97
|
+
const links = read('../client/modeller/figure-v3-links.ts')
|
|
98
|
+
assert.match(links, /kind: 'unlink-dimension', part: this\.partId, axis, mm \}/)
|
|
99
|
+
assert.match(links, /this\.dims\?\.\[AXES\.indexOf\(axis\)\]/)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('세 칸의 말은 다섯 말 모두에 있다', () => {
|
|
103
|
+
const links = read('../client/modeller/figure-v3-links.ts')
|
|
104
|
+
const keys = [...links.matchAll(/'(figure\.[a-z.-]*[a-z])'/g)].map(m => m[1])
|
|
105
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
106
|
+
const table = JSON.parse(read(`../translations/${lang}.json`))
|
|
107
|
+
for (const key of [...keys, 'figure.text.follow-would-loop', 'figure.text.fasten-would-loop', 'figure.label.motion-slide', 'figure.label.motion-turn'])
|
|
108
|
+
assert.ok(table[key], `${lang}: ${key}`)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('도형 크기를 따라 움직이는 자리를 가려 읽고, 여기에 고정하면 사라진다 — 드라이룸 램프의 길', () => {
|
|
113
|
+
let asset = pair()
|
|
114
|
+
asset = addV3Box(asset, { id: 'lamp', size: { x: 40, y: 20, z: 40 }, position: { x: 0, y: 10, z: 0 } })
|
|
115
|
+
const model = asset.document.model
|
|
116
|
+
model.nodes.push({ id: 'lamp.lift', op: 'add@1', args: ['size.y', 'lamp.position.y'], outputs: { value: 'lamp.lift.value' } } as never)
|
|
117
|
+
model.nodes.find(n => n.id === 'lamp.pose')!.args[1] = 'lamp.lift.value'
|
|
118
|
+
assert.deepEqual(positionsWorkedOut(asset, 'lamp'), ['y'])
|
|
119
|
+
assert.deepEqual(positionsWorkedOut(asset, 'plate'), [], '제 숫자로 놓인 부품에는 없다')
|
|
120
|
+
const fixed = applyV3Authoring(asset, { kind: 'fix-position', part: 'lamp', axis: 'y' })
|
|
121
|
+
assert.deepEqual(positionsWorkedOut(fixed, 'lamp'), [])
|
|
122
|
+
assert.equal(fixed.designInputs['lamp.position.y'], 110, '지금 자리 그대로 — 100 + 10')
|
|
123
|
+
})
|
package/things-factory.config.js
CHANGED
|
@@ -5,11 +5,8 @@ export default {
|
|
|
5
5
|
route,
|
|
6
6
|
routes: [
|
|
7
7
|
{ tagname: 'figure-list-page', page: 'figure-list' },
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
The V3 modeller under its own name until the catalogue's assets are V3 (ADR-0087 step 5). Without this row
|
|
11
|
-
the URL changes and no element is made — the shell builds the DOM from this table, not from route().
|
|
12
|
-
*/
|
|
8
|
+
/* One modeller since ADR-0087 step 5; the old V3 address is an alias. The shell builds the DOM from this table. */
|
|
9
|
+
{ tagname: 'figure-v3-modeller-page', page: 'figure-modeller' },
|
|
13
10
|
{ tagname: 'figure-v3-modeller-page', page: 'figure-v3-modeller' }
|
|
14
11
|
],
|
|
15
12
|
bootstrap
|
package/translations/en.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"figure.button.discard": "discard",
|
|
15
15
|
"figure.button.duplicate-part": "duplicate",
|
|
16
16
|
"figure.button.fit-base-to-parts": "fit to parts",
|
|
17
|
+
"figure.button.fix-position": "fix here",
|
|
17
18
|
"figure.button.mirror-x": "mirror X",
|
|
18
19
|
"figure.button.mirror-z": "mirror Z",
|
|
19
20
|
"figure.button.move-part-down": "move down",
|
|
@@ -37,13 +38,18 @@
|
|
|
37
38
|
"figure.button.snap": "snap",
|
|
38
39
|
"figure.button.take-n-changes": "apply {n}",
|
|
39
40
|
"figure.label.declared-space": "declared space",
|
|
41
|
+
"figure.label.follows-expression": "set by an expression",
|
|
40
42
|
"figure.label.gizmo-rotate": "turn",
|
|
41
43
|
"figure.label.gizmo-scale": "size",
|
|
42
44
|
"figure.label.gizmo-translate": "move",
|
|
45
|
+
"figure.label.position-worked-out": "moves with the figure size",
|
|
43
46
|
"figure.label.primitive-rounded-box": "rounded box",
|
|
44
47
|
"figure.text.catalog-needs-a-save": "can be set once the figure is saved.",
|
|
45
48
|
"figure.text.declared-space-declared": "declared from the parts.",
|
|
46
49
|
"figure.text.declared-space-none": "not declared yet.",
|
|
50
|
+
"figure.text.fasten-would-loop": "that part is already fastened to this one, so it cannot be fastened the other way.",
|
|
51
|
+
"figure.text.follow-would-loop": "that dimension already follows this part, so the two cannot follow each other.",
|
|
52
|
+
"figure.text.links-need-a-box": "size following works on box-shaped parts, fastening on boxes and cylinders.",
|
|
47
53
|
"figure.text.mounting-face-needs-space": "declare the space first to choose one.",
|
|
48
54
|
"figure.text.part-is-referenced": "'{part}' cannot be deleted: another part is fastened to it or follows its size. Undo that link first.",
|
|
49
55
|
"figure.text.part-name-invalid": "'{name}' cannot be a part name. Start with a letter and use letters, digits, - and _.",
|
package/translations/ja.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"figure.button.discard": "discard",
|
|
15
15
|
"figure.button.duplicate-part": "duplicate",
|
|
16
16
|
"figure.button.fit-base-to-parts": "部品に合わせる",
|
|
17
|
+
"figure.button.fix-position": "ここに固定",
|
|
17
18
|
"figure.button.mirror-x": "mirror X",
|
|
18
19
|
"figure.button.mirror-z": "mirror Z",
|
|
19
20
|
"figure.button.move-part-down": "move down",
|
|
@@ -35,13 +36,18 @@
|
|
|
35
36
|
"figure.button.snap": "snap",
|
|
36
37
|
"figure.button.take-n-changes": "apply {n}",
|
|
37
38
|
"figure.label.declared-space": "空間宣言",
|
|
39
|
+
"figure.label.follows-expression": "式で決まる",
|
|
38
40
|
"figure.label.gizmo-rotate": "回転",
|
|
39
41
|
"figure.label.gizmo-scale": "サイズ",
|
|
40
42
|
"figure.label.gizmo-translate": "移動",
|
|
43
|
+
"figure.label.position-worked-out": "図形のサイズに合わせて動く",
|
|
41
44
|
"figure.label.primitive-rounded-box": "角丸ボックス",
|
|
42
45
|
"figure.text.catalog-needs-a-save": "保存した後に設定できます。",
|
|
43
46
|
"figure.text.declared-space-declared": "部品から宣言しました。",
|
|
44
47
|
"figure.text.declared-space-none": "まだ宣言していません。",
|
|
48
|
+
"figure.text.fasten-would-loop": "その部品はすでにこの部品に取り付けられているため、逆向きに取り付けられません。",
|
|
49
|
+
"figure.text.follow-would-loop": "その寸法はすでにこの部品に追従しているため、互いに追従させられません。",
|
|
50
|
+
"figure.text.links-need-a-box": "サイズ追従は箱形、取り付けは箱形と円柱の部品で使えます。",
|
|
45
51
|
"figure.text.mounting-face-needs-space": "先に空間を宣言すると選べます。",
|
|
46
52
|
"figure.text.part-is-referenced": "'{part}' には他の部品が取り付けられているか、そのサイズに従っているため削除できません。先にその連結を外してください。",
|
|
47
53
|
"figure.text.part-name-invalid": "'{name}' は部品名に使えません。英字で始め、英字・数字・-・_ のみ使えます。",
|
|
@@ -555,5 +561,9 @@
|
|
|
555
561
|
"figure.label.state-inputs": "state inputs",
|
|
556
562
|
"figure.label.design-inputs": "design inputs",
|
|
557
563
|
"figure.text.release-blocked": "Release is blocked — {count}",
|
|
558
|
-
"figure.text.release-ready": "Ready to release"
|
|
564
|
+
"figure.text.release-ready": "Ready to release",
|
|
565
|
+
"figure.button.keep-changed-appearance": "調整したモデル幅で公開",
|
|
566
|
+
"figure.button.restore-released-base": "以前のモデル幅に戻す",
|
|
567
|
+
"figure.text.appearance-choice-explained": "戻すと X と Z を直前の公開値に戻し、Y とほかの編集はそのまま残します。調整した幅で公開すると、今のモデルの外形を残します。",
|
|
568
|
+
"figure.text.choose-appearance-change": "調整したモデル幅の扱いを選んでください"
|
|
559
569
|
}
|
package/translations/ko.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"figure.button.discard": "취소",
|
|
15
15
|
"figure.button.duplicate-part": "복제",
|
|
16
16
|
"figure.button.fit-base-to-parts": "부품에 맞추기",
|
|
17
|
+
"figure.button.fix-position": "여기에 고정",
|
|
17
18
|
"figure.button.mirror-x": "X 대칭",
|
|
18
19
|
"figure.button.mirror-z": "Z 대칭",
|
|
19
20
|
"figure.button.move-part-down": "아래로",
|
|
@@ -37,13 +38,18 @@
|
|
|
37
38
|
"figure.button.snap": "눈금에",
|
|
38
39
|
"figure.button.take-n-changes": "{n}개 적용",
|
|
39
40
|
"figure.label.declared-space": "공간 선언",
|
|
41
|
+
"figure.label.follows-expression": "식으로 정해짐",
|
|
40
42
|
"figure.label.gizmo-rotate": "돌리기",
|
|
41
43
|
"figure.label.gizmo-scale": "크기",
|
|
42
44
|
"figure.label.gizmo-translate": "옮기기",
|
|
45
|
+
"figure.label.position-worked-out": "도형 크기를 따라 움직임",
|
|
43
46
|
"figure.label.primitive-rounded-box": "둥근 상자",
|
|
44
47
|
"figure.text.catalog-needs-a-save": "저장한 뒤에 정할 수 있습니다.",
|
|
45
48
|
"figure.text.declared-space-declared": "부품으로 선언했습니다.",
|
|
46
49
|
"figure.text.declared-space-none": "아직 선언하지 않았습니다.",
|
|
50
|
+
"figure.text.fasten-would-loop": "그 부품은 이미 이 부품에 붙어 있어서 거꾸로 붙일 수 없습니다.",
|
|
51
|
+
"figure.text.follow-would-loop": "그 치수는 이미 이 부품을 따르고 있어서 서로 따르게 할 수 없습니다.",
|
|
52
|
+
"figure.text.links-need-a-box": "크기 따르기는 상자 모양, 붙이기는 상자·원기둥 부품에서 씁니다.",
|
|
47
53
|
"figure.text.mounting-face-needs-space": "공간을 먼저 선언해야 고를 수 있습니다.",
|
|
48
54
|
"figure.text.part-is-referenced": "'{part}' 에 다른 부품이 붙어 있거나 그 크기를 따르고 있어 지울 수 없습니다. 그 연결을 먼저 푸세요.",
|
|
49
55
|
"figure.text.part-name-invalid": "'{name}' 은 부품 이름으로 쓸 수 없습니다. 영문자로 시작하고 영문자·숫자·-·_ 만 씁니다.",
|