@things-factory/figure-ui 10.1.204 → 10.1.205
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/canvas.ts +91 -9
- package/client/modeller/editor-styles.ts +7 -22
- package/client/modeller/editor.ts +176 -145
- package/client/modeller/fasten-form.ts +85 -6
- package/client/modeller/figure-fasten-dialog.ts +42 -9
- package/client/modeller/figure-fasten-drawing.ts +233 -12
- package/client/modeller/figure-section-editor.ts +6 -6
- package/client/modeller/figure-slot-layout-panel.ts +1 -1
- package/client/modeller/gizmo.ts +12 -1
- package/client/modeller/links.ts +48 -6
- package/client/modeller/section-drawing.ts +7 -7
- package/client/modeller/size-drag.ts +112 -0
- package/client/modeller/slot-layout-host.ts +3 -3
- package/client/modeller/stage-model.ts +6 -9
- package/client/pages/figure-modeller-page.ts +1 -4
- package/dist-client/modeller/canvas.d.ts +24 -3
- package/dist-client/modeller/canvas.js +96 -11
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +7 -22
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +28 -32
- package/dist-client/modeller/editor.js +176 -153
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +36 -3
- package/dist-client/modeller/fasten-form.js +80 -5
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +17 -3
- package/dist-client/modeller/figure-fasten-dialog.js +47 -10
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.d.ts +33 -1
- package/dist-client/modeller/figure-fasten-drawing.js +235 -11
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +3 -3
- package/dist-client/modeller/figure-section-editor.js +4 -4
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +12 -1
- package/dist-client/modeller/gizmo.js +8 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/links.d.ts +14 -2
- package/dist-client/modeller/links.js +42 -4
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +3 -4
- package/dist-client/modeller/section-drawing.js +10 -9
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/size-drag.d.ts +38 -0
- package/dist-client/modeller/size-drag.js +85 -0
- package/dist-client/modeller/size-drag.js.map +1 -0
- package/dist-client/modeller/slot-layout-host.d.ts +1 -3
- package/dist-client/modeller/slot-layout-host.js +3 -3
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +5 -7
- package/dist-client/modeller/stage-model.js +6 -6
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -2
- package/dist-client/pages/figure-modeller-page.js +1 -6
- 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 +7 -7
- package/test/align.test.ts +7 -11
- package/test/browser/acceptance-page.js +10 -0
- package/test/browser/editing.check.mjs +17 -13
- package/test/browser/links-page.js +2 -2
- package/test/browser/links.check.mjs +94 -56
- package/test/browser/modeller-page.js +30 -18
- package/test/browser/modeller-revision.check.mjs +39 -0
- package/test/browser/modeller.check.mjs +32 -10
- package/test/browser/screen-page.js +0 -2
- package/test/browser/screen.check.mjs +11 -13
- package/test/browser/section.check.mjs +2 -2
- package/test/fasten-form.test.ts +62 -1
- package/test/gizmo.test.ts +13 -15
- package/test/links.test.ts +8 -3
- package/test/resize-with-placement.test.ts +14 -13
- package/test/revision.test.ts +3 -2
- package/test/section-editor.test.ts +9 -10
- package/test/size-drag.test.ts +114 -0
- package/test/slot-layout-host.test.ts +13 -8
- package/translations/en.json +25 -22
- package/translations/es.json +25 -22
- package/translations/ja.json +25 -22
- package/translations/ko.json +25 -22
- package/translations/ms.json +25 -22
- package/translations/zh.json +25 -22
package/test/fasten-form.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ import assert from 'node:assert/strict'
|
|
|
9
9
|
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
|
|
10
10
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
|
-
import { aimFasten, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf, freshFasten } from '../client/modeller/fasten-form.ts'
|
|
12
|
+
import { aimFasten, aimFastenAt, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf, freshFasten } from '../client/modeller/fasten-form.ts'
|
|
13
13
|
import { revisionOf } from './revision-of.ts'
|
|
14
14
|
|
|
15
15
|
const box = (asset: FigureAsset, id: string, [x, y, z]: number[], at = [0, 0, 0]) => addFigureBox(asset, { id, size: { x, y, z }, position: { x: at[0]!, y: at[1]!, z: at[2]! } })
|
|
@@ -102,3 +102,64 @@ test('the target list: every other part, the one hanging from this part listed a
|
|
|
102
102
|
])
|
|
103
103
|
assert.deepEqual(fastenTargetsOf(r, 'lid').map(t => t.part), ['frame', 'roller'])
|
|
104
104
|
})
|
|
105
|
+
|
|
106
|
+
/* A drum lying on the floor along Y and a ball, with a tag standing beside each (ADR-0140 3.9). */
|
|
107
|
+
async function curved() {
|
|
108
|
+
const { addFigurePrimitive } = await import('@hatiolab/figure-model')
|
|
109
|
+
let a = addFigurePrimitive(createFigureAsset('Curved'), { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 1600 }, position: { x: 0, y: 0, z: 0 } } as never)
|
|
110
|
+
a = addFigurePrimitive(a, { id: 'ball', kind: 'sphere', dimensions: { radiusX: 100, radiusY: 100, radiusZ: 100 }, position: { x: 0, y: 0, z: 3000 } } as never)
|
|
111
|
+
a = box(a, 'tag', [40, 40, 40], [600, 600, 600])
|
|
112
|
+
return box(a, 'cap', [40, 40, 40], [0, 600, 3000])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
test('aimed at a cylinder it stands beside, it goes on the side at the point it faces; aimed at a sphere, on its round', async () => {
|
|
116
|
+
const r = revisionOf(await curved())
|
|
117
|
+
const drum = aimFasten(r, 'tag', freshFasten(), { part: 'drum' })
|
|
118
|
+
assert.deepEqual(drum.surface, { around: 315, along: { mm: 600 } }, 'beside it, 600 up its length, toward +X +Z')
|
|
119
|
+
const plan = fastenPlanOf(r, 'tag', drum)
|
|
120
|
+
assert.deepEqual((plan.action as { to: unknown }).to, { part: 'drum', surface: { around: 315, along: { mm: 600 } } })
|
|
121
|
+
assert.equal((plan.action as { align?: unknown }).align, undefined, 'on a surface, where is its own words')
|
|
122
|
+
assert.deepEqual(plan.surface, { form: 'around', axis: 'y', place: { around: 315, along: { mm: 600 } } })
|
|
123
|
+
assert.equal(fastenSentenceOf(drum, plan, '바닥', say), 'figure.text.fasten-sentence-meet(to=drum의 figure.label.surface-around())')
|
|
124
|
+
const ball = aimFasten(r, 'cap', freshFasten(), { part: 'ball' })
|
|
125
|
+
assert.deepEqual(ball.surface, { toward: { x: 0, y: 1, z: 0 } }, 'straight above it')
|
|
126
|
+
/* Applied, and opened again: on the surface as it was. */
|
|
127
|
+
const done = applyFigureAuthoring(r.asset, plan.action!)
|
|
128
|
+
assert.deepEqual(fastenFormOf(revisionOf(done), 'tag').surface, { around: 315, along: { mm: 600 } })
|
|
129
|
+
const { rowBlockOf } = await import('../client/modeller/links.ts')
|
|
130
|
+
assert.equal(rowBlockOf(done, 'tag'), 'on-surface', 'a point of a curved surface has no face to lay a row across')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('a face or the surface, as the drawing asks; the surface chosen starts where the part stands', async () => {
|
|
134
|
+
const r = revisionOf(await curved())
|
|
135
|
+
const side = aimFasten(r, 'tag', freshFasten(), { part: 'drum' })
|
|
136
|
+
const top = fastenChanged(r, 'tag', side, { targetFace: 'top' })
|
|
137
|
+
assert.equal(top.surface, undefined, 'its end disc is a face')
|
|
138
|
+
assert.deepEqual((fastenPlanOf(r, 'tag', top).action as { to: unknown }).to, { part: 'drum', face: 'top' })
|
|
139
|
+
const back = fastenChanged(r, 'tag', top, { onSurface: true })
|
|
140
|
+
assert.deepEqual(back.surface, { around: 315, along: { mm: 600 } })
|
|
141
|
+
const turned = fastenChanged(r, 'tag', back, { surface: { around: 90, along: 'max' } })
|
|
142
|
+
assert.deepEqual((fastenPlanOf(r, 'tag', turned).action as { to: unknown }).to, { part: 'drum', surface: { around: 90, along: 'max' } })
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test("pressed on the canvas: the face or the point pressed, read in that part's own frame; itself and a curve with no fastening said (ADR-0140 stage 4)", async () => {
|
|
146
|
+
const { addFigurePrimitive } = await import('@hatiolab/figure-model')
|
|
147
|
+
/* A drum lying along X (turned a quarter about Z), a rounded box, and the tag to fasten. */
|
|
148
|
+
let a = addFigurePrimitive(createFigureAsset('Pressed'), { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 400 }, position: { x: 0, y: 500, z: 0 }, rotation: { x: 0, y: 0, z: 90 } } as never)
|
|
149
|
+
a = addFigurePrimitive(a, { id: 'soft', kind: 'rounded-box', dimensions: { width: 300, height: 200, depth: 300, round: 40 }, position: { x: 0, y: 100, z: 1500 } } as never)
|
|
150
|
+
const r = revisionOf(box(a, 'tag', [40, 40, 40], [900, 900, 900]))
|
|
151
|
+
const pose = (r.geometry as Array<{ id: string; pose: { r: number[][]; t: number[] } }>).find(g => g.id === 'drum')!.pose
|
|
152
|
+
const world = (v: number[], move = true) => [0, 1, 2].map(i => [0, 1, 2].reduce((s, c) => s + pose.r[i]![c]! * v[c]!, move ? pose.t[i]! : 0))
|
|
153
|
+
const rad = (d: number) => (d * Math.PI) / 180
|
|
154
|
+
const at = (deg: number) => [Math.cos(rad(deg)), 0, -Math.sin(rad(deg))]
|
|
155
|
+
/* A 12-sided drawing: the facet between 0° and 30°, pressed where the ray at 20° meets it, 50 up its own Y. */
|
|
156
|
+
const s = (100 * Math.cos(rad(15))) / Math.cos(rad(5))
|
|
157
|
+
const press = { id: 'drum', point: world(at(20).map((v, i) => (i === 1 ? 50 : v * s))), normal: world(at(15), false) }
|
|
158
|
+
const side = aimFastenAt(r, 'tag', freshFasten(), press)
|
|
159
|
+
assert.deepEqual([side.note, side.form?.target, side.form?.surface], [undefined, 'drum', { around: 20, along: { mm: 50 } }])
|
|
160
|
+
const end = aimFastenAt(r, 'tag', freshFasten(), { id: 'drum', point: world([10, 200, 20]), normal: world([0, 1, 0], false) })
|
|
161
|
+
assert.deepEqual([end.form?.surface, end.form?.targetFace, end.form?.align, end.form?.alignMm], [undefined, 'top', { x: 'mm', z: 'mm' }, { x: 10, z: 20 }], "its end, wherever the drum's own Y lies, its middle where pressed")
|
|
162
|
+
assert.deepEqual(aimFastenAt(r, 'tag', freshFasten(), { id: 'tag', point: [900, 920, 900], normal: [0, 1, 0] }), { note: 'aim-self', part: 'tag' })
|
|
163
|
+
const corner = aimFastenAt(r, 'tag', freshFasten(), { id: 'soft', point: [145, 100, 1645], normal: [Math.SQRT1_2, 0, Math.SQRT1_2] })
|
|
164
|
+
assert.deepEqual([corner.note, corner.form?.target], ['aim-not-here', 'soft'], 'a rounded corner: aimed at the part, said')
|
|
165
|
+
})
|
package/test/gizmo.test.ts
CHANGED
|
@@ -127,33 +127,31 @@ test('원기둥 · 구도 크기 기즈모가 먹는다 — 단면은 원으로'
|
|
|
127
127
|
})
|
|
128
128
|
|
|
129
129
|
/*
|
|
130
|
-
*
|
|
131
|
-
* (
|
|
132
|
-
*
|
|
130
|
+
* 크기는 커널의 한 함수로 바뀐다(ADR-0140 4.1 · 4.2, `figureSizeEditOf`): 숫자 칸은 부품의 가운데를, 손잡이는 잡은
|
|
131
|
+
* 쪽의 반대편을 그 자리에 둔다 — 배치(바닥 · 천장)와 상관없이, 누운 원기둥이든 서 있는 기둥이든. 같은 치수 · 같은
|
|
132
|
+
* 기준이면 칸과 손잡이가 같은 결과다.
|
|
133
133
|
*/
|
|
134
|
-
test('크기
|
|
135
|
-
const {
|
|
134
|
+
test('크기 칸은 가운데를, 손잡이는 반대쪽을 — 배치와 상관없이 커널의 한 함수로', async () => {
|
|
135
|
+
const { figureSizeEditOf, applyFigureAuthoring } = await import('@hatiolab/figure-model')
|
|
136
136
|
let asset = createFigureAsset('Post')
|
|
137
137
|
asset = addFigureBox(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
138
|
-
asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 50, length: 100 }, position: { x: 0, y: 300, z: 0 } })
|
|
139
138
|
asset = addFigurePrimitive(asset, { id: 'roller', kind: 'cylinder', dimensions: { radius: 30, length: 500 }, position: { x: 0, y: 30, z: 0 }, rotation: { x: 90, y: 0, z: 0 } })
|
|
140
139
|
const centreY = (a: typeof asset, part: string) => {
|
|
141
140
|
const g = compileFigureAsset(a).evaluate().geometry.find(o => o.id === part)! as unknown as { pose: { t: number[] } }
|
|
142
141
|
return g.pose.t[1]!
|
|
143
142
|
}
|
|
144
143
|
const set = (input: string, value: number) => ({ kind: 'set-design', input, unit: 'mm', value }) as never
|
|
145
|
-
const grow = (a: typeof asset, part: string, input: string, value: number) => apply(a,
|
|
144
|
+
const grow = (a: typeof asset, part: string, input: string, value: number, keep: never) => apply(a, figureSizeEditOf(a, part, [set(input, value)], keep).changes)
|
|
146
145
|
const postH = gizmoReachOf(asset, 'post').sizes![1]
|
|
147
|
-
assert.equal(centreY(grow(asset, 'post', postH, 2200
|
|
148
|
-
|
|
149
|
-
assert.equal(centreY(grow(asset, '
|
|
146
|
+
assert.equal(centreY(grow(asset, 'post', postH, 2200, { keep: 'centre' } as never), 'post'), 50, '숫자 칸: 가운데가 그 자리')
|
|
147
|
+
assert.equal(centreY(grow(asset, 'post', postH, 2200, { keep: 'opposite', axis: 'y', side: 1 } as never), 'post') - 2200 / 2, 0, '위 손잡이: 아랫면이 그 자리')
|
|
148
|
+
assert.equal(centreY(grow(asset, 'post', postH, 2200, { keep: 'opposite', axis: 'y', side: -1 } as never), 'post') + 2200 / 2, 100, '아래 손잡이: 윗면이 그 자리')
|
|
150
149
|
const rollerLength = gizmoReachOf(asset, 'roller').round!.along[0]!
|
|
151
|
-
assert.deepEqual(
|
|
152
|
-
|
|
153
|
-
/* 천장 도형이면 윗면을 지킨다 — 전에는 여기서도 아랫면을 지켜 천장을 뚫고 올라갔다. */
|
|
150
|
+
assert.deepEqual(figureSizeEditOf(asset, 'roller', [set(rollerLength, 800)], { keep: 'centre' }).changes, [set(rollerLength, 800)], '누운 롤러의 길이: 가운데가 원점이라 자리는 그대로')
|
|
151
|
+
/* 천장 도형도 같다 — 배치가 지키는 면을 따로 두지 않는다(ADR-0140 4.1 이 ADR-0097 §4.3 을 대신함). */
|
|
154
152
|
let hung = addFigureBox(createFigureAsset('Lamp'), { id: 'lamp', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: -50, z: 0 } })
|
|
155
|
-
hung = applyFigureAuthoring(hung, { kind: '
|
|
156
|
-
assert.equal(centreY(grow(hung, 'lamp', gizmoReachOf(hung, 'lamp').sizes![1], 300
|
|
153
|
+
hung = applyFigureAuthoring(hung, { kind: 'set-placement', placement: 'ceiling' })
|
|
154
|
+
assert.equal(centreY(grow(hung, 'lamp', gizmoReachOf(hung, 'lamp').sizes![1], 300, { keep: 'centre' } as never), 'lamp'), -50, '천장 도형: 숫자 칸도 가운데')
|
|
157
155
|
})
|
|
158
156
|
|
|
159
157
|
/* 손잡이는 키를 몰라도 찾는다 — 도구 막대에 옮기기 · 돌리기 · 크기, 제 숫자로 놓인 부품에만 (09-26). */
|
package/test/links.test.ts
CHANGED
|
@@ -367,8 +367,8 @@ test('따라 움직이는 관절 — 같은 방식으로 움직이고 제 조작
|
|
|
367
367
|
assert.deepEqual(motionOf(asset, 'right')?.follows, { sum: [{ joint: 'left', multiplier: -1 }], offset: 0 }, '화면은 무엇을 따르는지 읽는다')
|
|
368
368
|
})
|
|
369
369
|
|
|
370
|
-
test('면은 도형이 정한다 — 문틀(portal)은 바깥 여섯에 개구부 셋, 공은
|
|
371
|
-
const { facesOf, faceAxisOf, canFasten, alignAxesOf } = await import('../client/modeller/links.ts')
|
|
370
|
+
test('면은 도형이 정한다 — 문틀(portal)은 바깥 여섯에 개구부 셋, 공은 없고 곡면, 원뿔대는 위아래뿐, 원기둥 옆은 면이 아님 (2026-09-25, ADR-0140 3.9)', async () => {
|
|
371
|
+
const { facesOf, faceAxisOf, canFasten, alignAxesOf, surfaceOf } = await import('../client/modeller/links.ts')
|
|
372
372
|
let asset = createFigureAsset('Doorway')
|
|
373
373
|
asset = addFigurePrimitive(asset, {
|
|
374
374
|
id: 'frame',
|
|
@@ -379,10 +379,15 @@ test('면은 도형이 정한다 — 문틀(portal)은 바깥 여섯에 개구
|
|
|
379
379
|
asset = addFigurePrimitive(asset, { id: 'ball', kind: 'sphere', dimensions: { radiusX: 50, radiusY: 50, radiusZ: 50 }, position: { x: 0, y: 0, z: 0 } })
|
|
380
380
|
asset = addFigurePrimitive(asset, { id: 'cone', kind: 'frustum', dimensions: { radiusTop: 20, radiusBottom: 60, height: 100 }, position: { x: 0, y: 0, z: 0 } })
|
|
381
381
|
asset = addFigureBox(asset, { id: 'leaf', size: { x: 890, y: 2095, z: 40 }, position: { x: 0, y: 0, z: 0 } })
|
|
382
|
+
asset = addFigurePrimitive(asset, { id: 'drum', kind: 'cylinder', dimensions: { radius: 100, length: 400 }, position: { x: 0, y: 0, z: 0 } })
|
|
382
383
|
assert.deepEqual(facesOf(asset, 'frame'), ['left', 'right', 'bottom', 'top', 'back', 'front', 'opening-left', 'opening-right', 'opening-top'])
|
|
383
384
|
assert.equal(faceAxisOf(asset, 'frame', 'opening-top'), 'y')
|
|
384
385
|
assert.deepEqual(facesOf(asset, 'ball'), [])
|
|
385
|
-
assert.equal(canFasten(asset, 'ball'),
|
|
386
|
+
assert.equal(canFasten(asset, 'ball'), true, '공은 면이 없어도 곡면 위 점에 붙인다')
|
|
387
|
+
assert.deepEqual(surfaceOf(asset, 'ball'), { form: 'toward', axis: 'y' })
|
|
388
|
+
assert.deepEqual(facesOf(asset, 'drum'), ['bottom', 'top'], '원기둥 옆은 곡면이라 면이 아니다')
|
|
389
|
+
assert.deepEqual(surfaceOf(asset, 'drum'), { form: 'around', axis: 'y' })
|
|
390
|
+
assert.equal(surfaceOf(asset, 'cone'), undefined, '원뿔대 옆은 아직 받지 않는다')
|
|
386
391
|
assert.equal(canFasten(asset, 'frame'), true)
|
|
387
392
|
assert.deepEqual(facesOf(asset, 'cone'), ['bottom', 'top'])
|
|
388
393
|
assert.deepEqual(alignAxesOf(asset, { part: 'frame', face: 'opening-left' }), [{ axis: 'y', edges: true }, { axis: 'z', edges: true }])
|
|
@@ -117,22 +117,20 @@ test('a field only the room reads is taken out of the part\'s position row and s
|
|
|
117
117
|
})
|
|
118
118
|
|
|
119
119
|
/*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
120
|
+
* ADR-0140 4.4: what the drag shows is what is written. The part component asks the scene to leave its place to the
|
|
121
|
+
* modeller (things-scene `scalePlacement: 'owner'`), which draws each frame from figure-model's one size function --
|
|
122
|
+
* whatever the placement; no face is named to the scene.
|
|
122
123
|
*/
|
|
123
|
-
test('the part component
|
|
124
|
+
test('the part component leaves its place in a size drag to the modeller, for each placement', async () => {
|
|
124
125
|
const { componentOf } = await import('../client/modeller/stage-model.js')
|
|
125
|
-
const { figureScaleAnchorOf } = await import('@hatiolab/figure-model')
|
|
126
|
-
const expected = { floor: { x: 0, y: 1, z: 0 }, ceiling: { x: 0, y: -1, z: 0 }, wall: { x: 0, y: 0, z: 1 }, center: { x: 0, y: 0, z: 0 } } as const
|
|
127
126
|
for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
|
|
128
127
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: START[mode] })
|
|
129
128
|
a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: mode })
|
|
130
|
-
const anchor = figureScaleAnchorOf(a, 'box')!
|
|
131
|
-
assert.deepEqual(anchor, expected[mode], mode)
|
|
132
129
|
const body = compileFigureAsset(a).evaluate().geometry.find(g => g.id === 'box') as never
|
|
133
|
-
const state = componentOf(body, {}, { width: 1000, height: 1000 },
|
|
134
|
-
assert.
|
|
135
|
-
assert.equal(
|
|
130
|
+
const state = componentOf(body, {}, { width: 1000, height: 1000 }, true)!
|
|
131
|
+
assert.equal(state.scalePlacement, 'owner', mode)
|
|
132
|
+
assert.equal(state.scaleAnchor, undefined, `${mode}: no face named to the scene`)
|
|
133
|
+
assert.equal(componentOf(body, {}, { width: 1000, height: 1000 })!.scalePlacement, undefined, 'a shown-only body is not')
|
|
136
134
|
}
|
|
137
135
|
})
|
|
138
136
|
|
|
@@ -155,12 +153,15 @@ test('size response and attachment are separate screen paths', () => {
|
|
|
155
153
|
assert.equal(ko['figure.text.held-on-plane'].replace('{place}', '바닥').replace('{gap}', '0'), '바닥에 붙어 있음 (간격 0 mm)')
|
|
156
154
|
})
|
|
157
155
|
|
|
158
|
-
test('a part fastened
|
|
159
|
-
const {
|
|
156
|
+
test('a part fastened at any angle is sized by the gizmo too: its fastening keeps it, and nothing else is written', async () => {
|
|
157
|
+
const { figureSizeEditOf } = await import('@hatiolab/figure-model')
|
|
158
|
+
const { gizmoReachOf } = await import('../client/modeller/gizmo.js')
|
|
160
159
|
let a = addFigureBox(createFigureAsset('UX'), { id: 'hub', size: { x: 200, y: 100, z: 200 }, position: { x: 0, y: 50, z: 0 } })
|
|
161
160
|
a = addFigureBox(a, { id: 'blade', size: { x: 300, y: 20, z: 60 }, position: { x: 0, y: 110, z: 0 } })
|
|
162
161
|
a = applyFigureAuthoring(a, { kind: 'set-joint', joint: { id: 'blade', type: 'fixed', body0: { part: 'hub', face: 'top' }, body1: { part: 'blade' }, origin: { childRotate: { z: 30 } } } })
|
|
163
|
-
assert.
|
|
162
|
+
assert.ok(gizmoReachOf(a, 'blade').scale, 'offered')
|
|
163
|
+
const size = [{ kind: 'set-design', input: 'blade.size.x', unit: 'mm', value: 400 }] as never
|
|
164
|
+
assert.deepEqual(figureSizeEditOf(a, 'blade', size, { keep: 'opposite', axis: 'x', side: 1 }), { changes: size, kept: 'relation' })
|
|
164
165
|
})
|
|
165
166
|
|
|
166
167
|
/*
|
package/test/revision.test.ts
CHANGED
|
@@ -12,7 +12,7 @@ import assert from 'node:assert/strict'
|
|
|
12
12
|
import { readFileSync } from 'node:fs'
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
14
|
|
|
15
|
-
import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges, figureRoomOnlyInputsOf, figureRowOf,
|
|
15
|
+
import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges, figureRoomOnlyInputsOf, figureRowOf, measureFigureWork, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
16
16
|
import type { FigureAsset } from '@hatiolab/figure-model'
|
|
17
17
|
|
|
18
18
|
import { createFigureProductSession } from '../client/modeller/product-session.ts'
|
|
@@ -24,6 +24,7 @@ import { faceViewOf } from '../client/modeller/fasten-geometry.ts'
|
|
|
24
24
|
import { motionEndsOf } from '../client/modeller/motion-ends.ts'
|
|
25
25
|
import { figureFigureFacts } from '../client/modeller/figure-facts.ts'
|
|
26
26
|
import { gizmoReachOf } from '../client/modeller/gizmo.ts'
|
|
27
|
+
import { keptBySizeOf } from '../client/modeller/size-drag.ts'
|
|
27
28
|
import { moving } from './joints.ts'
|
|
28
29
|
import { revisionOf } from './revision-of.ts'
|
|
29
30
|
|
|
@@ -82,7 +83,7 @@ test('a version is readable while it is saved, and a save hands out the same tex
|
|
|
82
83
|
function selectionQuestions(shown: FigureRevision, part: string) {
|
|
83
84
|
const asset = shown.asset
|
|
84
85
|
const reach = gizmoReachOf(asset as never, part)
|
|
85
|
-
|
|
86
|
+
keptBySizeOf(asset, part)
|
|
86
87
|
boxOf(shown, part)
|
|
87
88
|
gridStepOf(shown)
|
|
88
89
|
jointFanOf(shown, part)
|
|
@@ -173,7 +173,7 @@ test('사각형·원도 같은 창에서 — 폭·지름·길이를 한 명령
|
|
|
173
173
|
const next = applyFigureAuthoring(box, { kind: 'set-section', part: 'b', across: { x: 500 }, length: 260 })
|
|
174
174
|
const b = (compileFigureAsset(next).evaluate().geometry.find(g => (g as { id: string }).id === 'b') as unknown as { shape: FigureEvaluatedShape; pose: { t: number[] } })
|
|
175
175
|
assert.deepEqual(b.shape.extrusion!.section, { kind: 'rectangle', size: { x: 500, z: 300 } })
|
|
176
|
-
assert.
|
|
176
|
+
assert.deepEqual([b.pose.t[1]! + b.shape.extrusion!.start, b.pose.t[1]! + b.shape.extrusion!.end], [-30, 230], 'a free part keeps its middle (ADR-0140 4.1)')
|
|
177
177
|
/* A corner that no longer fits, and a length of 0, are said before anything is sent. */
|
|
178
178
|
const rounded = applyFigureAuthoring(box, { kind: 'set-section-corner', part: 'b', corner: 100 })
|
|
179
179
|
const r = (compileFigureAsset(rounded).evaluate().geometry.find(g => (g as { id: string }).id === 'b') as unknown as { shape: FigureEvaluatedShape }).shape
|
|
@@ -217,15 +217,14 @@ test('창 안에서 폭과 속 공간을 함께 — 넓힌 폭에서만 들어
|
|
|
217
217
|
assert.deepEqual(sectionActionOf('t', 'rectangle', w, { ...w, cavity: null }), { kind: 'set-section', part: 't', cavity: null })
|
|
218
218
|
})
|
|
219
219
|
|
|
220
|
-
test('치수 칸 밑의 말: 다른 값을 따르면 그렇다고, 길이는
|
|
221
|
-
assert.deepEqual(measureNoteOf('linked',
|
|
222
|
-
assert.deepEqual(measureNoteOf('editable',
|
|
223
|
-
assert.deepEqual(measureNoteOf('editable',
|
|
224
|
-
assert.deepEqual(measureNoteOf('editable',
|
|
225
|
-
assert.
|
|
226
|
-
assert.equal(measureNoteOf(
|
|
227
|
-
assert.
|
|
228
|
-
assert.equal(say('figure.text.section-editor-keeps', { face: ko['figure.label.face-bottom']! }), '아랫면 유지')
|
|
220
|
+
test('치수 칸 밑의 말: 다른 값을 따르면 그렇다고, 길이는 가운데나 붙인 위치를 지킨다고 — 말할 것이 없으면 아무것도', () => {
|
|
221
|
+
assert.deepEqual(measureNoteOf('linked', 'centre'), { key: 'figure.text.section-editor-linked' }, 'following another value is said first')
|
|
222
|
+
assert.deepEqual(measureNoteOf('editable', 'centre'), { key: 'figure.text.size-keeps-centre' }, 'a free part keeps its middle (ADR-0140 4.1)')
|
|
223
|
+
assert.deepEqual(measureNoteOf('editable', 'relation'), { key: 'figure.text.size-keeps-fastening' }, 'a fastened part keeps where it is fastened')
|
|
224
|
+
assert.deepEqual(measureNoteOf('editable', 'none'), { key: 'figure.text.size-keeps-none' }, 'a position worked out: the size alone changes')
|
|
225
|
+
assert.equal(measureNoteOf('editable', undefined), null, 'nothing known: nothing')
|
|
226
|
+
assert.equal(measureNoteOf(undefined, undefined), null, 'a width across says only that it follows')
|
|
227
|
+
assert.deepEqual([say('figure.text.size-keeps-centre', {}), say('figure.text.size-keeps-fastening', {})], ['가운데 유지', '붙인 위치 유지'])
|
|
229
228
|
})
|
|
230
229
|
|
|
231
230
|
test('ADR-0100 A1: 상자·판·벽은 같은 돌출에 처음 치수만 다르고, 더해도 숨은 부착이나 연결이 생기지 않는다', async () => {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A size drag drawn from figure-model frame by frame (ADR-0140 4.4, `size-drag.ts`): each frame is what its release
|
|
3
|
+
* writes -- the same changes, and every body where the committed figure puts it, to the bit. A free part keeps the
|
|
4
|
+
* side opposite the handle; a fastened one moves what hangs from it, by its fastening.
|
|
5
|
+
*/
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
import assert from 'node:assert/strict'
|
|
8
|
+
|
|
9
|
+
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset, figureSizeEditOf } from '@hatiolab/figure-model'
|
|
10
|
+
import type { FigureAsset, FigureInputChange, FigureSizeKeep } from '@hatiolab/figure-model'
|
|
11
|
+
|
|
12
|
+
import { partsAtFrame, startSizeDrag } from '../client/modeller/size-drag.ts'
|
|
13
|
+
import { drawableFigureParts, type FigureRenderPart } from '../client/modeller/renderer.ts'
|
|
14
|
+
|
|
15
|
+
const set = (input: string, value: number): FigureInputChange => ({ kind: 'set-design', input, unit: 'mm', value })
|
|
16
|
+
const committed = (asset: FigureAsset, changes: FigureInputChange[]) =>
|
|
17
|
+
drawableFigureParts(compileFigureAsset({ ...asset, designInputs: { ...asset.designInputs, ...Object.fromEntries(changes.map(c => [c.input, c.value])) } }).evaluate().geometry)
|
|
18
|
+
const drawn = (asset: FigureAsset) => drawableFigureParts(compileFigureAsset(asset).evaluate().geometry)
|
|
19
|
+
const posesOf = (parts: FigureRenderPart[]) => Object.fromEntries(parts.map(p => [p.id, { r: p.pose.r, t: p.pose.t, dimensions: p.shape.dimensions }]))
|
|
20
|
+
|
|
21
|
+
/* A turned box on the floor, and a lid fastened on its top. */
|
|
22
|
+
function figure() {
|
|
23
|
+
let a = addFigureBox(createFigureAsset('Drag'), { id: 'box', size: { x: 200, y: 100, z: 150 }, position: { x: 40, y: 300, z: -20 }, rotation: { x: 10, y: 30, z: 0 } })
|
|
24
|
+
a = addFigureBox(a, { id: 'base', size: { x: 400, y: 100, z: 300 }, position: { x: 0, y: 50, z: 0 } })
|
|
25
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'base', to: { plane: 'mounting-plane' } })
|
|
26
|
+
a = addFigureBox(a, { id: 'lid', size: { x: 100, y: 20, z: 100 }, position: { x: 0, y: 0, z: 0 } })
|
|
27
|
+
return applyFigureAuthoring(a, { kind: 'attach', part: 'lid', to: { part: 'base', face: 'top' } })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('each frame of a free part is what its release writes: the same changes, every body where the commit puts it', () => {
|
|
31
|
+
const asset = figure()
|
|
32
|
+
const keep: FigureSizeKeep = { keep: 'opposite', axis: 'x', side: 1 }
|
|
33
|
+
const drag = startSizeDrag(asset, 'box', {}, keep)
|
|
34
|
+
for (const width of [230, 300, 180]) {
|
|
35
|
+
const sizes = [set('box.size.x', width)]
|
|
36
|
+
const frame = drag.frame(sizes)
|
|
37
|
+
assert.deepEqual(frame.changes, figureSizeEditOf(asset, 'box', sizes, keep).changes, `${width}: the changes`)
|
|
38
|
+
assert.deepEqual(posesOf(partsAtFrame(drawn(asset), frame.moved)), posesOf(committed(asset, frame.changes)), `${width}: the bodies`)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('a fastened part dragged: what hangs from it moves by its fastening, frame by frame, as the commit puts it', () => {
|
|
43
|
+
const asset = figure()
|
|
44
|
+
const drag = startSizeDrag(asset, 'base', {}, { keep: 'opposite', axis: 'y', side: 1 })
|
|
45
|
+
for (const height of [140, 220]) {
|
|
46
|
+
const frame = drag.frame([set('base.size.y', height)])
|
|
47
|
+
assert.equal(frame.changes.length, 1, 'its size alone: its fastening places it')
|
|
48
|
+
assert.ok(frame.moved.has('lid'), 'the lid on its top moves with it')
|
|
49
|
+
assert.deepEqual(posesOf(partsAtFrame(drawn(asset), frame.moved)), posesOf(committed(asset, frame.changes)), `${height}`)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('a body that moved and then stood still is drawn where it stopped, not where the drag began', () => {
|
|
54
|
+
const asset = figure()
|
|
55
|
+
const drag = startSizeDrag(asset, 'base', {}, { keep: 'centre' })
|
|
56
|
+
drag.frame([set('base.size.y', 200)])
|
|
57
|
+
/* The same height again: nothing changes in this frame, and the lid stays up where the last frame put it. */
|
|
58
|
+
const frame = drag.frame([set('base.size.y', 200)])
|
|
59
|
+
assert.deepEqual(posesOf(partsAtFrame(drawn(asset), frame.moved)), posesOf(committed(asset, frame.changes)))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('the handle held is what is kept from: one axis keeps the side opposite it; the uniform handle, or none, the middle', async () => {
|
|
63
|
+
const { sizeKeepOf } = await import('../client/modeller/gizmo.ts')
|
|
64
|
+
assert.deepEqual(sizeKeepOf({ x: 0, y: 1, z: 0 }), { keep: 'opposite', axis: 'y', side: 1 })
|
|
65
|
+
assert.deepEqual(sizeKeepOf({ x: -1, y: 0, z: 0 }), { keep: 'opposite', axis: 'x', side: -1 })
|
|
66
|
+
assert.deepEqual(sizeKeepOf({ x: 1, y: 1, z: 1 }), { keep: 'centre' }, 'the uniform handle')
|
|
67
|
+
assert.deepEqual(sizeKeepOf(undefined), { keep: 'centre' }, 'no handle known')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('what a size typed keeps, as the panel says it: the middle of a free part, the fastening of a fastened one', async () => {
|
|
71
|
+
const { keptBySizeOf } = await import('../client/modeller/size-drag.ts')
|
|
72
|
+
const asset = figure()
|
|
73
|
+
assert.deepEqual(['box', 'base', 'lid'].map(part => keptBySizeOf(asset, part)), ['centre', 'relation', 'relation'])
|
|
74
|
+
/* Its height placed by a rule: no point is kept, only the size changes -- said as such (chief architect 10-20). */
|
|
75
|
+
const ruled = structuredClone(asset) as FigureAsset
|
|
76
|
+
;(ruled.document.model as unknown as { nodes: Array<{ id: string; args: string[] }> }).nodes.find(n => n.id === 'box.pose')!.args[1] = 'box.position.x'
|
|
77
|
+
assert.equal(keptBySizeOf(ruled, 'box'), 'none')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('how far a free part goes past its mounting plane: its real extreme against the side its placement uses', async () => {
|
|
81
|
+
const { throughPlaneOf } = await import('../client/modeller/size-drag.ts')
|
|
82
|
+
const box = (placement: 'floor' | 'ceiling' | 'wall' | 'center', position: { x: number; y: number; z: number }, rotation = { x: 0, y: 0, z: 0 }) =>
|
|
83
|
+
applyFigureAuthoring(addFigureBox(createFigureAsset('P'), { id: 'b', size: { x: 100, y: 100, z: 100 }, position, rotation }), { kind: 'set-placement', placement })
|
|
84
|
+
assert.equal(throughPlaneOf(box('floor', { x: 0, y: 20, z: 0 }), drawn(box('floor', { x: 0, y: 20, z: 0 })), 'b'), 30, 'its bottom 30 under the floor')
|
|
85
|
+
assert.equal(throughPlaneOf(box('floor', { x: 0, y: 50, z: 0 }), drawn(box('floor', { x: 0, y: 50, z: 0 })), 'b'), 0, 'standing on it')
|
|
86
|
+
assert.equal(throughPlaneOf(box('ceiling', { x: 0, y: -40, z: 0 }), drawn(box('ceiling', { x: 0, y: -40, z: 0 })), 'b'), 10, 'its top 10 above the ceiling')
|
|
87
|
+
assert.equal(throughPlaneOf(box('wall', { x: 0, y: 0, z: 45 }), drawn(box('wall', { x: 0, y: 0, z: 45 })), 'b'), 5, 'its back 5 into the wall')
|
|
88
|
+
assert.equal(throughPlaneOf(box('center', { x: 0, y: -500, z: 0 }), drawn(box('center', { x: 0, y: -500, z: 0 })), 'b'), 0, 'a figure placed by its centre has no plane')
|
|
89
|
+
/* Turned 45° about Z, its lowest corner is √2·50 under its middle. */
|
|
90
|
+
const turned = box('floor', { x: 0, y: 60, z: 0 }, { x: 0, y: 0, z: 45 })
|
|
91
|
+
assert.equal(throughPlaneOf(turned, drawn(turned), 'b'), Math.round((50 * Math.SQRT2 - 60) * 10) / 10, 'its real corner, not its upright box')
|
|
92
|
+
const fastened = applyFigureAuthoring(box('floor', { x: 0, y: 20, z: 0 }), { kind: 'attach', part: 'b', to: { plane: 'mounting-plane' } })
|
|
93
|
+
assert.equal(throughPlaneOf(fastened, drawn(fastened), 'b'), 0, 'its fastening places it')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/*
|
|
97
|
+
A part whose height is placed by a rule keeps nothing a handle could keep (kept 'none'): only its size is written. The
|
|
98
|
+
drag draws exactly that, frame by frame -- it does not look held at the opposite side and then land elsewhere (ADR-0097
|
|
99
|
+
§4.3: a drag whose frames differ from its release is not done; chief architect 10-20).
|
|
100
|
+
*/
|
|
101
|
+
test('a position placed by a rule: each frame draws what the release writes -- the size alone, kept none', () => {
|
|
102
|
+
/* A command's result is sealed (ADR-0119): the rule goes into a copy. Its height now reads its x. */
|
|
103
|
+
const ruled = structuredClone(figure()) as FigureAsset
|
|
104
|
+
;(ruled.document.model as unknown as { nodes: Array<{ id: string; args: string[] }> }).nodes.find(n => n.id === 'box.pose')!.args[1] = 'box.position.x'
|
|
105
|
+
const keep: FigureSizeKeep = { keep: 'opposite', axis: 'y', side: 1 }
|
|
106
|
+
const drag = startSizeDrag(ruled, 'box', {}, keep)
|
|
107
|
+
for (const height of [160, 240]) {
|
|
108
|
+
const sizes = [set('box.size.y', height)]
|
|
109
|
+
const frame = drag.frame(sizes)
|
|
110
|
+
assert.deepEqual(figureSizeEditOf(ruled, 'box', sizes, keep), { changes: sizes, kept: 'none' }, `${height}: the size alone`)
|
|
111
|
+
assert.deepEqual(frame.changes, sizes, `${height}: the frame writes the same`)
|
|
112
|
+
assert.deepEqual(posesOf(partsAtFrame(drawn(ruled), frame.moved)), posesOf(committed(ruled, frame.changes)), `${height}: drawn as written`)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
@@ -37,22 +37,21 @@ test("no layout yet: a form on the selected part's top, and the equipment preset
|
|
|
37
37
|
assert.equal(layout, undefined)
|
|
38
38
|
const faces = anchorFacesOf(form.anchors, compileFigureAsset(asset).evaluate().geometry as never)
|
|
39
39
|
assert.deepEqual(faces, { fork: { width: 1200, depth: 900 } })
|
|
40
|
-
const
|
|
41
|
-
const presets = slotPresetsOf(form.anchors, say)
|
|
40
|
+
const presets = slotPresetsOf(form.anchors)
|
|
42
41
|
assert.deepEqual(presets.map(p => p.key)[0], 'single', 'a single slot first: where an author starts')
|
|
43
42
|
assert.equal(presets.length, 9)
|
|
44
43
|
const size = { ...faces.fork!, slotHeight: 1000 }
|
|
45
44
|
const single = presets[0]!.build(size, {})
|
|
46
45
|
assert.deepEqual(single.tiers[0]!, { tierNo: 1, anchorId: 'fork', mount: 'ON', position: { x: 0, y: 0, z: 0 }, rotation: 0, layout: { pattern: 'SINGLE', slot: { allowed: { width: 1200, depth: 900, height: 1000 } } } })
|
|
47
46
|
assert.deepEqual(presets.find(p => p.key === 'floorBlock')!.params, [
|
|
48
|
-
{ key: 'rows',
|
|
49
|
-
{ key: 'cols',
|
|
50
|
-
{ key: 'stackLimit',
|
|
47
|
+
{ key: 'rows', integer: true },
|
|
48
|
+
{ key: 'cols', integer: true },
|
|
49
|
+
{ key: 'stackLimit', integer: true }
|
|
51
50
|
])
|
|
52
51
|
/* Two anchors: the twin fork stands one tier on each. A bottom anchor hangs its single slot. */
|
|
53
|
-
const twin = slotPresetsOf([{ id: 'left', part: 'a', face: 'top' }, { id: 'right', part: 'b', face: 'top' }]
|
|
52
|
+
const twin = slotPresetsOf([{ id: 'left', part: 'a', face: 'top' }, { id: 'right', part: 'b', face: 'top' }])
|
|
54
53
|
assert.deepEqual(twin.find(p => p.key === 'craneTwinFork')!.build(size, {}).tiers.map(t => t.anchorId), ['left', 'right'])
|
|
55
|
-
assert.equal(slotPresetsOf([{ id: 'under', part: 'fork', face: 'bottom' }]
|
|
54
|
+
assert.equal(slotPresetsOf([{ id: 'under', part: 'fork', face: 'bottom' }])[0]!.build(size, {}).tiers[0]!.mount, 'HANG')
|
|
56
55
|
})
|
|
57
56
|
|
|
58
57
|
test('the editor is told what figure-model asks beyond its own checks: every slot names a type', () => {
|
|
@@ -103,7 +102,7 @@ test('refusals are said from their details, never the kernel\'s sentence', () =>
|
|
|
103
102
|
})
|
|
104
103
|
|
|
105
104
|
test('the slot layout panel starts from presets that keep where they started as a record', () => {
|
|
106
|
-
const [single] = slotPresetsOf([{ id: 'fork', part: 'fork', face: 'top' }]
|
|
105
|
+
const [single] = slotPresetsOf([{ id: 'fork', part: 'fork', face: 'top' }])
|
|
107
106
|
assert.deepEqual(single!.build({ width: 1200, depth: 900, slotHeight: 1000 }, {}).preset, { key: 'single', size: { width: 1200, depth: 900, slotHeight: 1000 }, params: {} })
|
|
108
107
|
})
|
|
109
108
|
|
|
@@ -115,3 +114,9 @@ test('the places words are in every language', () => {
|
|
|
115
114
|
assert.ok(t[key], `${lang} ${key}`)
|
|
116
115
|
}
|
|
117
116
|
})
|
|
117
|
+
|
|
118
|
+
/* A preset's name is the shared editor's (label.slot-layout.preset.*): figure-ui keeps no second copy of it. */
|
|
119
|
+
test('the preset names are the slot layout editor\'s, not a copy here', () => {
|
|
120
|
+
for (const lang of ['en', 'es', 'ja', 'ko', 'ms', 'zh'])
|
|
121
|
+
assert.deepEqual(Object.keys(JSON.parse(read(`../translations/${lang}.json`))).filter(key => key.includes('slot-preset')), [], lang)
|
|
122
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -201,8 +201,11 @@
|
|
|
201
201
|
"figure.label.section-editor-across": "{axis} width (mm)",
|
|
202
202
|
"figure.label.section-editor-diameter": "diameter (mm)",
|
|
203
203
|
"figure.text.section-editor-linked": "Follows another value.",
|
|
204
|
-
"figure.text.
|
|
205
|
-
"figure.text.
|
|
204
|
+
"figure.text.size-keeps-centre": "keeps the centre",
|
|
205
|
+
"figure.text.size-keeps-fastening": "keeps where it is fastened",
|
|
206
|
+
"figure.text.size-keeps-none": "keeps its worked-out position",
|
|
207
|
+
"figure.text.size-through-plane": "{part} goes {mm} mm past the {plane}.",
|
|
208
|
+
"figure.button.fasten-to-plane": "Fasten to the {plane}",
|
|
206
209
|
"figure.text.section-issue-corner": "At this width the corner radius can be at most {max} mm. Widen it or make the corner smaller.",
|
|
207
210
|
"figure.text.section-issue-length": "The push length must be more than 0.",
|
|
208
211
|
"figure.text.section-issue-cavity-walls": "The inside walls take the whole width. Widen it or make the walls thinner.",
|
|
@@ -559,6 +562,7 @@
|
|
|
559
562
|
"figure.label.fasten-what": "Which part",
|
|
560
563
|
"figure.label.fasten-face-on-part": "Which face of {part}",
|
|
561
564
|
"figure.label.fasten-face-on-plane": "Where on the {plane}",
|
|
565
|
+
"figure.label.fasten-face-on-surface": "Where on {part}",
|
|
562
566
|
"figure.label.fasten-how": "How it sits",
|
|
563
567
|
"figure.label.facing": "How faces meet",
|
|
564
568
|
"figure.text.fasten-summary-gap": "gap {mm} mm",
|
|
@@ -576,6 +580,11 @@
|
|
|
576
580
|
"figure.label.face-top": "top",
|
|
577
581
|
"figure.label.face-back": "back",
|
|
578
582
|
"figure.label.face-front": "front",
|
|
583
|
+
"figure.label.surface-around": "curved side",
|
|
584
|
+
"figure.label.surface-toward": "curved surface",
|
|
585
|
+
"figure.label.surface-seen-end": "{part} seen from its end",
|
|
586
|
+
"figure.label.surface-seen-above": "from above",
|
|
587
|
+
"figure.label.surface-seen-side": "from the side",
|
|
579
588
|
"figure.text.motion-explained": "How this part moves. Added on top of its fastening, never instead of it.",
|
|
580
589
|
"figure.label.motion-none": "does not move",
|
|
581
590
|
"figure.label.motion-slide": "slide",
|
|
@@ -702,6 +711,10 @@
|
|
|
702
711
|
"figure.text.row-blocked-on-plane": "Fasten it to another part, not the mounting plane, to arrange it in a row along that part.",
|
|
703
712
|
"figure.text.row-blocked-at-origin": "It stands at another part's origin. Fasten it to a face of that part to arrange it in a row along it.",
|
|
704
713
|
"figure.text.row-blocked-holds-parts": "Parts are fastened to this part, so it cannot be arranged in a row. Arrange it first; parts fastened after ride every copy.",
|
|
714
|
+
"figure.text.row-blocked-on-surface": "It is fastened to a point on a curved surface, so it cannot be arranged in a row. Fasten it to a face to arrange it in a row.",
|
|
715
|
+
"figure.text.aim-self": "{part} cannot be fastened to itself.",
|
|
716
|
+
"figure.text.aim-cannot": "{part} has no face or curved surface to fasten to.",
|
|
717
|
+
"figure.text.aim-not-here": "That spot on {part} cannot be fastened to; choose a face.",
|
|
705
718
|
"figure.button.go-fasten": "go to fastening",
|
|
706
719
|
"figure.text.row-count-pitch": "{count} × {part} · along {axis} · pitch {pitch} mm (centre to centre)",
|
|
707
720
|
"figure.text.row-count-uneven": "{count} × {part} · along {axis} · unevenly spaced",
|
|
@@ -709,8 +722,7 @@
|
|
|
709
722
|
"figure.text.row-ends": "{low} mm left at the low end · {high} mm at the high end",
|
|
710
723
|
"figure.text.row-ends-unknown": "What is left at the ends could not be read.",
|
|
711
724
|
"figure.text.row-companions": "copied along: {parts}",
|
|
712
|
-
"figure.
|
|
713
|
-
"figure.button.declare-instance-size": "set the range it stretches over",
|
|
725
|
+
"figure.button.declare-instance-size": "let a drawing resize it",
|
|
714
726
|
"figure.text.proposal-applied": "applied {n} commands as one step.",
|
|
715
727
|
"figure.text.proposal-saved-into": "Saved into: {names}",
|
|
716
728
|
"figure.text.proposal-needs-modeller": "this proposal can be applied only while its figure is open in the modeller.",
|
|
@@ -842,9 +854,6 @@
|
|
|
842
854
|
"figure.text.held-on-part": "Attached to the {otherFace} of {other}",
|
|
843
855
|
"figure.button.open-fastening": "Open attachment settings",
|
|
844
856
|
"figure.button.detach-held": "Detach",
|
|
845
|
-
"figure.text.gizmo-size-turned-fastening": "{part} is attached turned, so its size cannot be dragged.",
|
|
846
|
-
"figure.text.gizmo-size-tilted": "{part} is tilted, so its size cannot be dragged.",
|
|
847
|
-
"figure.button.to-size-fields": "Go to size",
|
|
848
857
|
"figure.label.align-input": "Follows its field ({field})",
|
|
849
858
|
"figure.label.tab-short-properties": "Properties",
|
|
850
859
|
"figure.label.tab-short-capability": "Capability",
|
|
@@ -1004,19 +1013,6 @@
|
|
|
1004
1013
|
"figure.text.slot-layout-issues": "the value at {path} in the slot layout is not valid.",
|
|
1005
1014
|
"figure.text.slot-accepts-empty": "slot {slot} takes no type.",
|
|
1006
1015
|
"figure.text.slot-id-taken": "{slot} is already the name of a place in this figure.",
|
|
1007
|
-
"figure.label.slot-preset-single": "single slot",
|
|
1008
|
-
"figure.label.slot-preset-agvTandem": "AGV tandem (front/rear)",
|
|
1009
|
-
"figure.label.slot-preset-agvParallel": "AGV parallel (left/right)",
|
|
1010
|
-
"figure.label.slot-preset-agv2x2": "AGV 2×2",
|
|
1011
|
-
"figure.label.slot-preset-bufferPort2x2": "buffer port 2×2",
|
|
1012
|
-
"figure.label.slot-preset-multiBufferPort": "multi-buffer port (4)",
|
|
1013
|
-
"figure.label.slot-preset-dualTierConveyor": "dual-tier conveyor",
|
|
1014
|
-
"figure.label.slot-preset-craneTwinFork": "crane twin fork",
|
|
1015
|
-
"figure.label.slot-preset-floorBlock": "floor block",
|
|
1016
|
-
"figure.label.slot-preset-param-upperElevation": "upper tier elevation (mm)",
|
|
1017
|
-
"figure.label.slot-preset-param-rows": "rows",
|
|
1018
|
-
"figure.label.slot-preset-param-cols": "columns",
|
|
1019
|
-
"figure.label.slot-preset-param-stackLimit": "stack limit",
|
|
1020
1016
|
"figure.label.placement": "fastening",
|
|
1021
1017
|
"figure.label.motion-groups": "motion groups",
|
|
1022
1018
|
"figure.label.motion-group": "motion group",
|
|
@@ -1187,7 +1183,7 @@
|
|
|
1187
1183
|
"figure.text.size-and-space-declared-to-follow": "the figure size was set from its parts, {x} × {y} × {z} mm, and the space it takes was fitted to them.",
|
|
1188
1184
|
"figure.button.release-blocked": "{count} blocks release",
|
|
1189
1185
|
"figure.label.preview-way-one": "one",
|
|
1190
|
-
"figure.label.preview-way-eight": "
|
|
1186
|
+
"figure.label.preview-way-eight": "various sizes",
|
|
1191
1187
|
"figure.text.preview-needs-space": "set the space it takes to try other sizes.",
|
|
1192
1188
|
"figure.text.released-figure-not-deletable": "{name} has been released and cannot be deleted. retire it in its details.",
|
|
1193
1189
|
"figure.label.retired": "retired",
|
|
@@ -1205,5 +1201,12 @@
|
|
|
1205
1201
|
"figure.button.make-public": "make public",
|
|
1206
1202
|
"figure.button.make-private": "make private",
|
|
1207
1203
|
"figure.text.category-choose": "choose a category",
|
|
1208
|
-
"figure.text.property-section-failed": "the section {section} could not be drawn: {reason}"
|
|
1204
|
+
"figure.text.property-section-failed": "the section {section} could not be drawn: {reason}",
|
|
1205
|
+
"figure.label.board-size": "size on a drawing",
|
|
1206
|
+
"figure.text.board-size-fixed": "On a drawing it is always drawn at {x} × {y} × {z} mm.",
|
|
1207
|
+
"figure.text.board-size-fixed-explained": "Press it to let a drawing resize this figure. Pick a part and press 「fit to the figure size」 to make that part grow with it.",
|
|
1208
|
+
"figure.text.board-size-range-explained": "A drawing can resize it within these ranges. Only the parts that follow its size grow with it; the rest stay as they are.",
|
|
1209
|
+
"figure.text.board-size-followers": "parts that change with its size: {parts}",
|
|
1210
|
+
"figure.text.board-size-followers-more": "{first} and {rest} more",
|
|
1211
|
+
"figure.text.board-size-no-followers": "no part changes with its size"
|
|
1209
1212
|
}
|