@things-factory/figure-ui 10.1.49 → 10.1.51
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/graphql/v3-store.ts +2 -1
- package/client/modeller/figure-history-panel.ts +9 -1
- package/client/modeller/figure-v3-first-save.ts +98 -0
- package/client/modeller/figure-v3-links.ts +177 -31
- package/client/modeller/figure-v3-parts.ts +1 -0
- package/client/modeller/figure-v3-properties.ts +2 -1
- package/client/modeller/v3-canvas.ts +61 -7
- package/client/modeller/v3-editor.ts +96 -21
- package/client/modeller/v3-first-save.ts +34 -0
- package/client/modeller/v3-gizmo.ts +52 -3
- package/client/modeller/v3-joint-drag.ts +116 -0
- package/client/modeller/v3-joint-fan.ts +24 -11
- package/client/modeller/v3-keys.ts +12 -1
- package/client/modeller/v3-links.ts +119 -8
- package/client/modeller/v3-live.ts +11 -0
- package/client/modeller/v3-product-session.ts +4 -2
- package/client/modeller/v3-refusals.ts +21 -0
- package/client/pages/figure-v3-modeller-page.ts +57 -8
- package/dist-client/graphql/v3-store.js +2 -1
- package/dist-client/graphql/v3-store.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.js +9 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-v3-capability.d.ts +1 -1
- package/dist-client/modeller/figure-v3-clips.d.ts +1 -1
- package/dist-client/modeller/figure-v3-controls.d.ts +1 -1
- package/dist-client/modeller/figure-v3-first-save.d.ts +20 -0
- package/dist-client/modeller/figure-v3-first-save.js +118 -0
- package/dist-client/modeller/figure-v3-first-save.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +14 -1
- package/dist-client/modeller/figure-v3-links.js +169 -28
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +1 -1
- package/dist-client/modeller/figure-v3-parts.js +1 -0
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
- package/dist-client/modeller/figure-v3-properties.js +2 -1
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/v3-canvas.d.ts +11 -0
- package/dist-client/modeller/v3-canvas.js +61 -7
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +1 -1
- package/dist-client/modeller/v3-editor.d.ts +13 -1
- package/dist-client/modeller/v3-editor.js +96 -21
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-first-save.d.ts +17 -0
- package/dist-client/modeller/v3-first-save.js +27 -0
- package/dist-client/modeller/v3-first-save.js.map +1 -0
- package/dist-client/modeller/v3-gizmo.d.ts +18 -0
- package/dist-client/modeller/v3-gizmo.js +45 -2
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-joint-drag.d.ts +19 -0
- package/dist-client/modeller/v3-joint-drag.js +97 -0
- package/dist-client/modeller/v3-joint-drag.js.map +1 -0
- package/dist-client/modeller/v3-joint-fan.js +24 -11
- package/dist-client/modeller/v3-joint-fan.js.map +1 -1
- package/dist-client/modeller/v3-keys.d.ts +9 -1
- package/dist-client/modeller/v3-keys.js +3 -0
- package/dist-client/modeller/v3-keys.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +72 -3
- package/dist-client/modeller/v3-links.js +89 -6
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +6 -0
- package/dist-client/modeller/v3-live.js +10 -0
- package/dist-client/modeller/v3-live.js.map +1 -1
- package/dist-client/modeller/v3-product-session.d.ts +4 -1
- package/dist-client/modeller/v3-product-session.js +2 -2
- package/dist-client/modeller/v3-product-session.js.map +1 -1
- package/dist-client/modeller/v3-refusals.d.ts +4 -0
- package/dist-client/modeller/v3-refusals.js +21 -0
- package/dist-client/modeller/v3-refusals.js.map +1 -0
- package/dist-client/pages/figure-v3-modeller-page.d.ts +4 -0
- package/dist-client/pages/figure-v3-modeller-page.js +56 -8
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -5
- package/test/browser/build.mjs +62 -0
- package/test/browser/modeller-page.js +198 -0
- package/test/browser/modeller.check.mjs +358 -0
- package/test/browser/serve.mjs +43 -0
- package/test/joints.ts +33 -0
- package/test/v3-clips.test.ts +3 -2
- package/test/v3-controls.test.ts +3 -2
- package/test/v3-fields.test.ts +3 -2
- package/test/v3-first-save.test.ts +84 -0
- package/test/v3-gizmo.test.ts +38 -5
- package/test/v3-joint-drag.test.ts +93 -0
- package/test/v3-joint-fan.test.ts +16 -6
- package/test/v3-keys.test.ts +15 -2
- package/test/v3-links.test.ts +114 -14
- package/test/v3-live.test.ts +23 -4
- package/test/v3-maturity.test.ts +2 -6
- package/test/v3-refusals.test.ts +28 -0
- package/translations/en.json +40 -5
- package/translations/ja.json +39 -4
- package/translations/ko.json +39 -4
- package/translations/ms.json +39 -4
- package/translations/zh.json +39 -4
package/test/v3-links.test.ts
CHANGED
|
@@ -53,30 +53,23 @@ test('붙이기 — 누구에게 붙었나, 붙일 수 있는 모양인가', ()
|
|
|
53
53
|
test('움직임 — 미끄러짐 · 돌기, 축, 무엇이 모느냐를 읽는다', () => {
|
|
54
54
|
const asset = pair()
|
|
55
55
|
assert.equal(motionOf(asset, 'block'), undefined)
|
|
56
|
-
const sliding =
|
|
57
|
-
kind: 'add-motion',
|
|
58
|
-
part: 'block',
|
|
59
|
-
motion: { kind: 'slide', axis: 'x' },
|
|
60
|
-
state: { id: 'travel', unit: 'mm', min: 0, max: 300, start: 0 }
|
|
61
|
-
})
|
|
56
|
+
const sliding = moving(asset, 'block', { kind: 'slide', axis: 'x', id: 'travel', min: 0, max: 300, start: 0 })
|
|
62
57
|
const motion = motionOf(sliding, 'block')!
|
|
63
58
|
assert.equal(motion.kind, 'slide')
|
|
64
59
|
assert.equal(motion.axis, 'x')
|
|
65
60
|
assert.equal(motion.control?.id, 'travel')
|
|
66
61
|
assert.equal(motion.control?.max, 300)
|
|
67
|
-
const turning =
|
|
68
|
-
kind: 'add-motion',
|
|
69
|
-
part: 'block',
|
|
70
|
-
motion: { kind: 'turn', axis: 'y' },
|
|
71
|
-
state: { id: 'swing', unit: 'deg', min: -90, max: 90, start: 0 }
|
|
72
|
-
})
|
|
62
|
+
const turning = moving(asset, 'block', { kind: 'turn', axis: 'y', id: 'swing', min: -90, max: 90, start: 0 })
|
|
73
63
|
assert.deepEqual([motionOf(turning, 'block')!.kind, motionOf(turning, 'block')!.axis], ['turn', 'y'])
|
|
74
|
-
|
|
64
|
+
const joint = sliding.joints!.find(j => j.body1.part === 'block')!
|
|
65
|
+
const still = applyV3Authoring(sliding, { kind: 'set-joint', joint: { id: joint.id, type: 'fixed', body0: joint.body0, body1: joint.body1 } })
|
|
66
|
+
assert.equal(motionOf(still, 'block'), undefined, 'a fixed joint does not move')
|
|
75
67
|
})
|
|
76
68
|
|
|
77
69
|
import { readFileSync } from 'node:fs'
|
|
78
70
|
import { fileURLToPath } from 'node:url'
|
|
79
71
|
import { choiceOf } from '../client/modeller/v3-links.ts'
|
|
72
|
+
import { moving } from './joints.ts'
|
|
80
73
|
|
|
81
74
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
82
75
|
|
|
@@ -235,7 +228,7 @@ test('놓일 수 있는 크기 — 남은 만큼 채우던 부품을 비례로
|
|
|
235
228
|
test('관절이 있는 도형에는 한 비율로 키우기를 권하고, 선언하면 규칙으로 보인다(수석 아키텍트 판정 09-24)', () => {
|
|
236
229
|
let asset = sizedPair()
|
|
237
230
|
assert.equal(turnsAboutAJoint(asset), false)
|
|
238
|
-
asset =
|
|
231
|
+
asset = moving(asset, 'flange', { kind: 'turn', axis: 'y', id: 'flange.turn', min: 0, max: 90 })
|
|
239
232
|
assert.equal(turnsAboutAJoint(asset), true)
|
|
240
233
|
const editor = read('../client/modeller/v3-editor.ts')
|
|
241
234
|
const rule = editor.slice(editor.indexOf(' private renderSizingRule('), editor.indexOf(' /** Choose a part,'))
|
|
@@ -244,3 +237,110 @@ test('관절이 있는 도형에는 한 비율로 키우기를 권하고, 선언
|
|
|
244
237
|
assert.ok(rule.includes("this.linkCommand({ kind: 'scale-uniformly' })"), '누르면 선언한다 — 사람이 정한다')
|
|
245
238
|
})
|
|
246
239
|
|
|
240
|
+
|
|
241
|
+
test('붙일 때 면 위 어디에 — 그 면을 가로지르는 두 축만, 바닥에는 모서리가 없다 (문을 틀 모서리에 달기, 09-25)', async () => {
|
|
242
|
+
const { alignAxesOf, alignOf } = await import('../client/modeller/v3-links.ts')
|
|
243
|
+
let two = createV3Asset('Two')
|
|
244
|
+
two = addV3Box(two, { id: 'jamb', size: { x: 100, y: 2100, z: 150 }, position: { x: 0, y: 1050, z: 0 } })
|
|
245
|
+
two = addV3Box(two, { id: 'leaf', size: { x: 900, y: 2000, z: 40 }, position: { x: 600, y: 1000, z: 0 } })
|
|
246
|
+
const right = { part: 'jamb', face: 'right' }
|
|
247
|
+
assert.deepEqual(alignAxesOf(two, 'leaf', right), [{ axis: 'y', edges: true }, { axis: 'z', edges: true }])
|
|
248
|
+
assert.deepEqual(alignAxesOf(two, 'leaf', 'plane'), [{ axis: 'x', edges: false }, { axis: 'z', edges: false }])
|
|
249
|
+
/* A door on the jamb's right face: bottoms level, fronts level. The x choice is along the face and is not sent. */
|
|
250
|
+
assert.deepEqual(alignOf(two, 'leaf', right, { x: 'max', y: 'min', z: 'max' }, {}), { y: 'min', z: 'max' })
|
|
251
|
+
assert.deepEqual(alignOf(two, 'leaf', { part: 'jamb', face: 'top' }, { x: 'mm', z: 'centre' }, { x: 120 }), { x: { mm: 120 } })
|
|
252
|
+
assert.equal(alignOf(two, 'leaf', 'plane', { x: 'min' }, {}), undefined, '바닥에는 맞출 모서리가 없다')
|
|
253
|
+
assert.equal(alignOf(two, 'leaf', { part: 'jamb', face: 'top' }, {}, {}), undefined)
|
|
254
|
+
|
|
255
|
+
/* What the form sends is what hangs the part on the edge: the door's pivot is on the jamb's front edge line. */
|
|
256
|
+
let asset = createV3Asset('Door')
|
|
257
|
+
asset = addV3Box(asset, { id: 'jamb', size: { x: 100, y: 2100, z: 150 }, position: { x: 0, y: 1050, z: 0 } })
|
|
258
|
+
asset = addV3Box(asset, { id: 'leaf', size: { x: 900, y: 2000, z: 40 }, position: { x: 600, y: 1000, z: 0 } })
|
|
259
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'jamb', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
260
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'leaf', face: 'left', to: { part: 'jamb', face: 'right' }, align: alignOf(asset, 'leaf', { part: 'jamb', face: 'right' }, { y: 'min', z: 'max' }, {}) })
|
|
261
|
+
const { compileV3Asset } = await import('@hatiolab/figure-model')
|
|
262
|
+
const leaf = compileV3Asset(asset).evaluate().geometry.find(g => g.id === 'leaf')!
|
|
263
|
+
assert.deepEqual(leaf.pose.t.map(Math.round), [500, 1000, 55], '문짝 아래가 문틀 아래에, 앞이 문틀 앞에')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('따라 움직이는 관절 — 같은 방식으로 움직이고 제 조작값이 있는 관절만 고르고, 기록에는 배율과 더하기만 (집게 두 손가락, 09-25)', async () => {
|
|
267
|
+
const { leadersFor, motionJoint } = await import('../client/modeller/v3-links.ts')
|
|
268
|
+
const { compileV3Asset } = await import('@hatiolab/figure-model')
|
|
269
|
+
let asset = createV3Asset('Gripper')
|
|
270
|
+
asset = addV3Box(asset, { id: 'palm', size: { x: 240, y: 70, z: 160 }, position: { x: 0, y: 35, z: 0 } })
|
|
271
|
+
for (const id of ['left', 'right']) asset = addV3Box(asset, { id, size: { x: 30, y: 140, z: 100 }, position: { x: 0, y: 200, z: 0 } })
|
|
272
|
+
asset = addV3Box(asset, { id: 'wrist', size: { x: 60, y: 60, z: 60 }, position: { x: 0, y: 400, z: 0 } })
|
|
273
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'palm', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
274
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'left', face: 'bottom', to: { part: 'palm', face: 'top' }, align: { x: 'min' } })
|
|
275
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'right', face: 'bottom', to: { part: 'palm', face: 'top' }, align: { x: 'max' } })
|
|
276
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'wrist', face: 'top', to: { part: 'palm', face: 'bottom' } })
|
|
277
|
+
const form = { kind: 'slide' as const, axis: 'x' as const, id: 'grip', min: 0, max: 60, follow: '', multiplier: 1, offset: 0 }
|
|
278
|
+
const jointOf = (part: string) => asset.joints!.find(j => j.body1.part === part)!
|
|
279
|
+
asset = applyV3Authoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf('left'), form, 'left') })
|
|
280
|
+
asset = applyV3Authoring(asset, { kind: 'set-joint', joint: motionJoint(jointOf('wrist'), { ...form, kind: 'turn', axis: 'y', id: 'twist', min: -90, max: 90 }, 'wrist') })
|
|
281
|
+
|
|
282
|
+
assert.deepEqual(leadersFor(asset, 'right', 'slide').map(j => j.id), ['left'], '미끄러지는 관절은 미끄러지는 관절을 따른다')
|
|
283
|
+
assert.deepEqual(leadersFor(asset, 'right', 'turn').map(j => j.id), ['wrist'])
|
|
284
|
+
assert.deepEqual(leadersFor(asset, 'left', 'slide'), [], '제 관절은 고르지 않는다')
|
|
285
|
+
|
|
286
|
+
const follower = motionJoint(jointOf('right'), { ...form, id: 'ignored', follow: 'left', multiplier: -1 }, 'right')
|
|
287
|
+
assert.deepEqual(follower.mimic, { joint: 'left', multiplier: -1 })
|
|
288
|
+
assert.equal(follower.state, undefined, '따라가는 관절은 제 조작값이 없다')
|
|
289
|
+
assert.equal(follower.lowerLimit, undefined)
|
|
290
|
+
asset = applyV3Authoring(asset, { kind: 'set-joint', joint: follower })
|
|
291
|
+
assert.deepEqual(leadersFor(asset, 'wrist', 'slide').map(j => j.id), ['left'], '따라가는 관절은 앞장서지 않는다')
|
|
292
|
+
|
|
293
|
+
const x = (id: string, grip: number) => Math.round(compileV3Asset(asset).evaluate({ grip }).geometry.find(g => g.id === id)!.pose.t[0]!)
|
|
294
|
+
assert.equal(x('left', 40) - x('left', 0), 40)
|
|
295
|
+
assert.equal(x('right', 40) - x('right', 0), -40, '오른손은 거울로 따라 닫힌다')
|
|
296
|
+
const { motionOf } = await import('../client/modeller/v3-links.ts')
|
|
297
|
+
assert.deepEqual(motionOf(asset, 'right')?.follows, { joint: 'left', multiplier: -1 }, '화면은 무엇을 따르는지 읽는다')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('면은 도형이 정한다 — 문틀(portal)은 바깥 여섯에 개구부 셋, 공은 없음, 원뿔대는 위아래뿐 (2026-09-25)', async () => {
|
|
301
|
+
const { facesOf, faceAxisOf, canFasten, alignAxesOf } = await import('../client/modeller/v3-links.ts')
|
|
302
|
+
let asset = createV3Asset('Doorway')
|
|
303
|
+
asset = addV3Primitive(asset, {
|
|
304
|
+
id: 'frame',
|
|
305
|
+
kind: 'portal',
|
|
306
|
+
dimensions: { left: -550, right: 550, bottom: 0, top: 2200, openingLeft: -450, openingRight: 450, openingTop: 2100, depth: 150 },
|
|
307
|
+
position: { x: 0, y: 0, z: 0 }
|
|
308
|
+
})
|
|
309
|
+
asset = addV3Primitive(asset, { id: 'ball', kind: 'sphere', dimensions: { radiusX: 50, radiusY: 50, radiusZ: 50 }, position: { x: 0, y: 0, z: 0 } })
|
|
310
|
+
asset = addV3Primitive(asset, { id: 'cone', kind: 'frustum', dimensions: { radiusTop: 20, radiusBottom: 60, height: 100 }, position: { x: 0, y: 0, z: 0 } })
|
|
311
|
+
asset = addV3Box(asset, { id: 'leaf', size: { x: 890, y: 2095, z: 40 }, position: { x: 0, y: 0, z: 0 } })
|
|
312
|
+
assert.deepEqual(facesOf(asset, 'frame'), ['left', 'right', 'bottom', 'top', 'back', 'front', 'opening-left', 'opening-right', 'opening-top'])
|
|
313
|
+
assert.equal(faceAxisOf(asset, 'frame', 'opening-top'), 'y')
|
|
314
|
+
assert.deepEqual(facesOf(asset, 'ball'), [])
|
|
315
|
+
assert.equal(canFasten(asset, 'ball'), false, '공은 붙일 면이 없다')
|
|
316
|
+
assert.equal(canFasten(asset, 'frame'), true)
|
|
317
|
+
assert.deepEqual(facesOf(asset, 'cone'), ['bottom', 'top'])
|
|
318
|
+
assert.deepEqual(alignAxesOf(asset, 'leaf', { part: 'frame', face: 'opening-left' }), [{ axis: 'y', edges: true }, { axis: 'z', edges: true }])
|
|
319
|
+
assert.deepEqual(alignAxesOf(asset, 'leaf', { part: 'cone', face: 'top' }), [{ axis: 'x', edges: false }, { axis: 'z', edges: false }], '옆면이 평면이 아니면 모서리도 없다')
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('되풀이는 붙어 있고 움직이지 않고 아무것도 매달지 않은 부품에 — 붙인 면을 가로지르는 축으로 (빈틈 B, 2026-09-25)', async () => {
|
|
323
|
+
const { rowAxesOf, rowOf } = await import('../client/modeller/v3-links.ts')
|
|
324
|
+
let asset = createV3Asset('Conveyor')
|
|
325
|
+
asset = addV3Box(asset, { id: 'frame', size: { x: 2000, y: 100, z: 600 }, position: { x: 0, y: 0, z: 0 } })
|
|
326
|
+
asset = addV3Box(asset, { id: 'slat', size: { x: 60, y: 20, z: 560 }, position: { x: 0, y: 0, z: 0 } })
|
|
327
|
+
assert.deepEqual(rowAxesOf(asset, 'slat'), [], '붙지 않은 부품은 되풀이를 권하지 않는다')
|
|
328
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'frame', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
329
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'slat', face: 'bottom', to: { part: 'frame', face: 'top' } })
|
|
330
|
+
assert.deepEqual(rowAxesOf(asset, 'frame'), [], '바닥에 선 부품은 채울 길이가 없다')
|
|
331
|
+
assert.deepEqual(rowAxesOf(asset, 'slat'), ['x', 'z'], '윗면을 가로지르는 두 축')
|
|
332
|
+
asset = applyV3Authoring(asset, { kind: 'make-repeat', part: 'slat', axis: 'x', pitch: 100 })
|
|
333
|
+
assert.deepEqual(rowOf(asset, 'slat'), { axis: 'X', pitch: 100 })
|
|
334
|
+
assert.deepEqual(rowAxesOf(asset, 'slat'), [], '이미 되풀이면 다시 권하지 않는다')
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('비튼 부품에는 모서리 맞춤을 내놓지도 보내지도 않는다 — 가운데서 거리만 (빈틈 F, 2026-09-25)', async () => {
|
|
338
|
+
const { alignAxesOf, alignOf } = await import('../client/modeller/v3-links.ts')
|
|
339
|
+
let asset = createV3Asset('Fan')
|
|
340
|
+
asset = addV3Box(asset, { id: 'hub', size: { x: 120, y: 120, z: 80 }, position: { x: 0, y: 0, z: 0 } })
|
|
341
|
+
asset = addV3Box(asset, { id: 'blade', size: { x: 300, y: 60, z: 8 }, position: { x: 0, y: 0, z: 0 } })
|
|
342
|
+
const front = { part: 'hub', face: 'front' }
|
|
343
|
+
assert.deepEqual(alignAxesOf(asset, 'blade', front, true), [{ axis: 'x', edges: false }, { axis: 'y', edges: false }])
|
|
344
|
+
assert.deepEqual(alignOf(asset, 'blade', front, { x: 'max', y: 'mm' }, { y: 50 }, true), { y: { mm: 50 } }, '끝 맞춤은 빠지고 거리만 간다')
|
|
345
|
+
assert.deepEqual(alignOf(asset, 'blade', front, { x: 'max' }, {}), { x: 'max' }, '비틀지 않으면 그대로')
|
|
346
|
+
})
|
package/test/v3-live.test.ts
CHANGED
|
@@ -7,16 +7,17 @@ import assert from 'node:assert/strict'
|
|
|
7
7
|
import { readFileSync } from 'node:fs'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
|
|
10
|
-
import { addV3Box, applyV3Authoring, createV3Asset } from '@hatiolab/figure-model'
|
|
10
|
+
import { addV3Box, applyV3Authoring, compileV3Asset, createV3Asset } from '@hatiolab/figure-model'
|
|
11
11
|
|
|
12
|
-
import { liveOverridesOf, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/v3-live.ts'
|
|
12
|
+
import { liveOverridesOf, liveOverSession, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from '../client/modeller/v3-live.ts'
|
|
13
|
+
import { moving } from './joints.ts'
|
|
13
14
|
|
|
14
15
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
15
16
|
|
|
16
17
|
const slider = () => {
|
|
17
18
|
let asset = createV3Asset('Slider')
|
|
18
19
|
asset = addV3Box(asset, { id: 'block', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
19
|
-
return
|
|
20
|
+
return moving(asset, 'block', { kind: 'slide', axis: 'x', id: 'shift', min: 0, max: 60, start: 0 })
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
test('끄는 값으로 그 자세를 바로 그린다 — 도형은 그대로', () => {
|
|
@@ -73,7 +74,7 @@ test('속성창 숫자 칸은 치는 동안 알리고, 이름표를 끌 수 있
|
|
|
73
74
|
assert.ok(editor.includes('@input=${(e: Event) => this.typing(field as never, e)}'))
|
|
74
75
|
assert.ok(editor.includes('@pointerdown=${(e: PointerEvent) => this.scrub(group.cells[axis] as never, e, unavailable)}'))
|
|
75
76
|
for (const event of ['@v3-state-live=', '@v3-states-live=', '@v3-part-live=']) assert.ok(editor.includes(event), event)
|
|
76
|
-
assert.ok(editor.includes('
|
|
77
|
+
assert.ok(editor.includes('liveOverSession(asset as V3Asset, this.currentInputs(), this.live)'), '캔버스는 세션 위에 끄는 값을 그린다 (행동은 아래 시험)')
|
|
77
78
|
const run = editor.slice(editor.indexOf(' private run(action'), editor.indexOf(' private hold('))
|
|
78
79
|
assert.ok(run.includes('this.live = NOTHING_LIVE'), '기록하거나 거절되면 끄는 값을 버린다')
|
|
79
80
|
const material = read('../client/modeller/figure-v3-material.ts')
|
|
@@ -97,3 +98,21 @@ test('클립 트랙의 머리는 이름 한 줄, 그 아래 반복 방식과 지
|
|
|
97
98
|
assert.ok(head.includes('width: 30px;'), '지우기는 작은 버튼')
|
|
98
99
|
})
|
|
99
100
|
|
|
101
|
+
test('끄는 동안은 세션이 지금 든 자세 위에 그린다 — 다른 관절이 저장된 각도로 돌아가지 않는다', () => {
|
|
102
|
+
/* Two joints: the author turned one with its control; then drags the other. */
|
|
103
|
+
let asset = slider()
|
|
104
|
+
/* An arm on a post of its own, turning about the post's left edge -- so a swing moves it, and the slide does not. */
|
|
105
|
+
asset = addV3Box(asset, { id: 'post', size: { x: 100, y: 100, z: 100 }, position: { x: 400, y: 50, z: 0 } })
|
|
106
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'post', face: 'bottom', to: { plane: 'mounting-plane' }, align: { x: { mm: 400 } } })
|
|
107
|
+
asset = addV3Box(asset, { id: 'arm', size: { x: 100, y: 20, z: 20 }, position: { x: 400, y: 110, z: 0 } })
|
|
108
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'arm', face: 'bottom', to: { part: 'post', face: 'top' }, align: { x: 'min' } })
|
|
109
|
+
asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'swing', min: -180, max: 180 })
|
|
110
|
+
const current = { ...asset.designInputs, ...asset.stateDefaults, swing: 90 }
|
|
111
|
+
const armAt = (parts: { id: string; pose: { t: number[] } }[]) => parts.find(p => p.id === 'arm')!.pose.t.map(v => Math.round(v))
|
|
112
|
+
const committed = armAt(compileV3Asset({ ...asset, stateDefaults: { ...asset.stateDefaults, swing: 90 } }).evaluate().geometry as never)
|
|
113
|
+
const dragging = armAt(liveOverSession(asset, current, { values: { shift: 30 } }) as never)
|
|
114
|
+
assert.deepEqual(dragging, committed, '사용자 2026-09-24: 「조작을 끝내면 완전히 다른 위치로 된다」 — swing 90 이 끄는 동안에도 남는다')
|
|
115
|
+
const overSaved = armAt(livePartsOf(asset, { values: { shift: 30 } }) as never)
|
|
116
|
+
assert.notDeepEqual(overSaved, committed, '전제: 저장된 기본값 위에 그리면 swing 이 0 으로 돌아간다')
|
|
117
|
+
})
|
|
118
|
+
|
package/test/v3-maturity.test.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
v3ReportOf
|
|
26
26
|
} from '../client/modeller/v3-maturity.ts'
|
|
27
27
|
import { capacityFrom } from '../client/modeller/scene-budget.ts'
|
|
28
|
+
import { moving } from './joints.ts'
|
|
28
29
|
|
|
29
30
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
30
31
|
const verdict = read('../client/modeller/figure-v3-verdict.ts')
|
|
@@ -109,12 +110,7 @@ test('상태를 따라 움직이는 부품을 센다 — V2 의 「따로 움직
|
|
|
109
110
|
asset = addV3Box(asset, cube('arm', 200))
|
|
110
111
|
assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 0, '전제: 움직임이 없으면 0')
|
|
111
112
|
|
|
112
|
-
asset =
|
|
113
|
-
kind: 'add-motion',
|
|
114
|
-
part: 'arm',
|
|
115
|
-
motion: { kind: 'turn', axis: 'y' },
|
|
116
|
-
state: { id: 'arm.turn', unit: 'deg', min: 0, max: 90 }
|
|
117
|
-
})
|
|
113
|
+
asset = moving(asset, 'arm', { kind: 'turn', axis: 'y', id: 'arm.turn', min: 0, max: 90 })
|
|
118
114
|
assert.equal(v3ReportOf(asset)!.score.budgets.find(b => b.key === 'independent')!.used, 1, '도는 팔 하나')
|
|
119
115
|
})
|
|
120
116
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 크기 범위 거절을 사람의 말로 (사용자 2026-09-24: 「what is this warning message means?」 — 커널의 영어 원문이 떴다).
|
|
3
|
+
*/
|
|
4
|
+
import { test } from 'node:test'
|
|
5
|
+
import assert from 'node:assert/strict'
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
9
|
+
import { sizeRangeRefusalOf } from '../client/modeller/v3-refusals.ts'
|
|
10
|
+
|
|
11
|
+
const ko = JSON.parse(readFileSync(fileURLToPath(new URL('../translations/ko.json', import.meta.url)), 'utf8'))
|
|
12
|
+
const say = (key: string, params: Record<string, string>) => ko[key].replace(/\{(\w+)\}/g, (_: string, k: string) => params[k] ?? '')
|
|
13
|
+
const refusal = (message: string) => Object.assign(new Error(message), { code: 'INSTANCE_SIZE_RANGE' })
|
|
14
|
+
|
|
15
|
+
test('화성 트레이의 거절 — 축 · 숫자 · 부품 · 고치는 곳', () => {
|
|
16
|
+
const said = sizeRangeRefusalOf(refusal('INSTANCE_SIZE_RANGE: size.x: below 8.82353 mm a length goes to zero (cell); declare the smallest size at 8.82353 mm or more'), say)!
|
|
17
|
+
assert.ok(said.includes('X 축을') && said.includes('8.8 mm') && said.includes('cell 부품') && said.includes('다시 재기'), said)
|
|
18
|
+
assert.ok(!/declare|below/.test(said))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('위쪽 끝도, 부품 이름이 없어도', () => {
|
|
22
|
+
const said = sizeRangeRefusalOf(refusal('INSTANCE_SIZE_RANGE: size.y: above 15000 mm the figure cannot be drawn; declare the largest size at 15000 mm or less'), say)!
|
|
23
|
+
assert.ok(said.includes('Y 축을') && said.includes('15,000 mm'), said)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('다른 거절은 건드리지 않는다', () => {
|
|
27
|
+
assert.equal(sizeRangeRefusalOf(Object.assign(new Error('x'), { code: 'EDIT_TARGET' }), say), null)
|
|
28
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -66,9 +66,19 @@
|
|
|
66
66
|
"figure.text.repeat-what-pitch-gives": "{count} at a length of {length} mm · placed up to {longest} mm long ({limit} in a row).",
|
|
67
67
|
"figure.text.repeat-pitch-too-small": "the pitch must be at least {min} mm: at the authored length of {length} mm it would make more than {limit}.",
|
|
68
68
|
"figure.text.gizmo-joint-places": "a joint places {part}. move it with its control in the motion tab.",
|
|
69
|
+
"figure.label.motion-continuous": "turn without end",
|
|
70
|
+
"figure.text.fasten-before-moving": "fasten it to another part or the floor first; then it can move.",
|
|
71
|
+
"figure.text.joint-range-holds-zero": "the range must hold 0, the rest pose.",
|
|
72
|
+
"figure.text.gizmo-joint-mimic": "the {joint} joint follows {leader}. move {leader}.",
|
|
73
|
+
"figure.text.gizmo-joint-fixed-turn": "the {joint} joint does not turn. give it a turn in the motion tab.",
|
|
74
|
+
"figure.text.gizmo-joint-turned-slide": "the {joint} joint only slides. use the move handle.",
|
|
75
|
+
"figure.text.gizmo-joint-origin-follows": "the {joint} joint's place follows another part. change it under fastening in the motion tab.",
|
|
76
|
+
"figure.text.gizmo-joint-off-axis": "the {joint} joint turns only about {axis}. use the {axis} rotate handle.",
|
|
77
|
+
"figure.text.gizmo-size-follows": "{part}'s {axis} size follows another measure. change it under sizes in the motion tab.",
|
|
69
78
|
"figure.text.gizmo-fastening-places": "{part} is fastened to a face of another part, which places it. detach it first to move it.",
|
|
70
|
-
"figure.text.
|
|
71
|
-
"figure.text.
|
|
79
|
+
"figure.text.instance-range-can-widen": "measured again with the parts as they are: {ranges} mm.",
|
|
80
|
+
"figure.text.size-range-too-small": "below {mm} mm on {axis} the {part} part has no length, yet the figure is declared to be placed smaller, so this change cannot be made. use \"measure again\" under the sizes it can be placed at.",
|
|
81
|
+
"figure.text.size-range-too-large": "above {mm} mm on {axis} the {part} part cannot be drawn, yet the figure is declared to be placed larger, so this change cannot be made. use \"measure again\" under the sizes it can be placed at.",
|
|
72
82
|
"figure.text.mounting-face-needs-space": "declare the space first to choose one.",
|
|
73
83
|
"figure.text.part-is-referenced": "'{part}' cannot be deleted: another part is fastened to it or follows its size. Undo that link first.",
|
|
74
84
|
"figure.text.part-name-invalid": "'{name}' cannot be a part name. Start with a letter and use letters, digits, - and _.",
|
|
@@ -374,6 +384,9 @@
|
|
|
374
384
|
"figure.text.designate-in-properties": "nothing designated yet. select a part and set its role in the properties tab.",
|
|
375
385
|
"figure.text.detail-level-explained": "S for signs and posts, M for most equipment, L for the large equipment a view centres on. the level sets the part limit.",
|
|
376
386
|
"figure.text.discard-changes-and-start-new": "there are unsaved changes. discard them and start a new figure?",
|
|
387
|
+
"figure.text.leave-without-saving": "there are unsaved changes. discard them and leave?",
|
|
388
|
+
"figure.button.leave-without-saving": "discard and leave",
|
|
389
|
+
"figure.button.keep-editing": "keep editing",
|
|
377
390
|
"figure.text.does-not-block": "does not block",
|
|
378
391
|
"figure.text.emissive-explained": "how bright the surface itself is. 0 is unlit, and it does not light its surroundings.",
|
|
379
392
|
"figure.text.enter-a-type-name": "enter a type name.",
|
|
@@ -515,7 +528,7 @@
|
|
|
515
528
|
"figure.text.tools-explained": "from left: centre X, centre Z, to floor, mirror X, mirror Z, snap to grid.",
|
|
516
529
|
"figure.text.triangle-count": "{count} triangles",
|
|
517
530
|
"figure.text.two-keys-at-least": "add at least two keys.",
|
|
518
|
-
"figure.text.type-name-already-taken": "the type name {type} is already in use. pick another one
|
|
531
|
+
"figure.text.type-name-already-taken": "the type name {type} is already in use. pick another one.",
|
|
519
532
|
"figure.text.type-name-cannot-change": "the type name cannot be changed after creation.",
|
|
520
533
|
"figure.text.type-name-will-not-release": "this name cannot be released. use capitals, digits and single underscores, 3 to 32 characters (e.g. AGV_LIFTER).",
|
|
521
534
|
"figure.text.unit-is-a-word-not-a-rule": "a display unit (e.g. mm, deg, %). it is not used in calculation.",
|
|
@@ -551,7 +564,7 @@
|
|
|
551
564
|
"figure.label.facing-flush": "same plane",
|
|
552
565
|
"figure.label.gap": "gap (mm)",
|
|
553
566
|
"figure.label.align": "within the face",
|
|
554
|
-
"figure.label.align-centre": "
|
|
567
|
+
"figure.label.align-centre": "centred",
|
|
555
568
|
"figure.label.align-min": "low edge",
|
|
556
569
|
"figure.label.align-max": "high edge",
|
|
557
570
|
"figure.button.fasten": "Fasten",
|
|
@@ -673,5 +686,27 @@
|
|
|
673
686
|
"figure.label.group-figure": "Figure",
|
|
674
687
|
"figure.label.group-standing": "Where it stands",
|
|
675
688
|
"figure.label.group-catalog": "Catalogue",
|
|
676
|
-
"figure.button.control-settings": "Name, range and time"
|
|
689
|
+
"figure.button.control-settings": "Name, range and time",
|
|
690
|
+
"figure.label.align-x": "left-right",
|
|
691
|
+
"figure.label.align-y": "up-down",
|
|
692
|
+
"figure.label.align-z": "front-back",
|
|
693
|
+
"figure.label.align-x-min": "level with left edge",
|
|
694
|
+
"figure.label.align-x-max": "level with right edge",
|
|
695
|
+
"figure.label.align-y-min": "level with bottom edge",
|
|
696
|
+
"figure.label.align-y-max": "level with top edge",
|
|
697
|
+
"figure.label.align-z-min": "level with back edge",
|
|
698
|
+
"figure.label.align-z-max": "level with front edge",
|
|
699
|
+
"figure.label.align-mm": "offset from centre (mm)",
|
|
700
|
+
"figure.label.motion-follow": "moves with",
|
|
701
|
+
"figure.label.motion-own-control": "its own control",
|
|
702
|
+
"figure.label.motion-multiplier": "multiplier",
|
|
703
|
+
"figure.label.motion-offset": "offset",
|
|
704
|
+
"figure.label.motion-follows": "moves with {joint} × {multiplier} + {offset}",
|
|
705
|
+
"figure.label.face-opening-left": "opening left",
|
|
706
|
+
"figure.label.face-opening-right": "opening right",
|
|
707
|
+
"figure.label.face-opening-top": "opening top",
|
|
708
|
+
"figure.label.repeat-fills": "as many as fill the length",
|
|
709
|
+
"figure.button.make-repeat": "make a row",
|
|
710
|
+
"figure.button.remove-repeat": "remove the row",
|
|
711
|
+
"figure.label.twist": "twist (°)"
|
|
677
712
|
}
|
package/translations/ja.json
CHANGED
|
@@ -64,9 +64,19 @@
|
|
|
64
64
|
"figure.text.repeat-what-pitch-gives": "長さ {length} mm で {count} 個 · 最大 {longest} mm まで置けます (1 列 {limit} 個)。",
|
|
65
65
|
"figure.text.repeat-pitch-too-small": "pitch は {min} mm 以上にしてください。作成した長さ {length} mm で {limit} 個を超えるためです。",
|
|
66
66
|
"figure.text.gizmo-joint-places": "{part} は関節に付いていて、関節が位置を決めます。動きタブの操作値で動かしてください。",
|
|
67
|
+
"figure.label.motion-continuous": "無限回転",
|
|
68
|
+
"figure.text.fasten-before-moving": "先に他の部品か床に取り付けると、動きを付けられます。",
|
|
69
|
+
"figure.text.joint-range-holds-zero": "範囲に 0(基本姿勢)を含めてください。",
|
|
70
|
+
"figure.text.gizmo-joint-mimic": "{joint} 関節は {leader} に従います。{leader} を動かしてください。",
|
|
71
|
+
"figure.text.gizmo-joint-fixed-turn": "{joint} 関節は回りません。動きタブで回転を付けてください。",
|
|
72
|
+
"figure.text.gizmo-joint-turned-slide": "{joint} 関節はスライドのみです。移動ハンドルを使ってください。",
|
|
73
|
+
"figure.text.gizmo-joint-origin-follows": "{joint} 関節の位置は他の部品に従います。動きタブの取り付けで変えてください。",
|
|
74
|
+
"figure.text.gizmo-joint-off-axis": "{joint} 関節は {axis} 軸でのみ回ります。{axis} の回転ハンドルを使ってください。",
|
|
75
|
+
"figure.text.gizmo-size-follows": "{part} の {axis} 寸法は他の寸法に従います。動きタブの寸法で変えてください。",
|
|
67
76
|
"figure.text.gizmo-fastening-places": "{part} は他の部品の面に付いていて、その面が位置を決めます。動かすには先に外してください。",
|
|
68
|
-
"figure.text.
|
|
69
|
-
"figure.text.
|
|
77
|
+
"figure.text.instance-range-can-widen": "今の部品で測り直すと {ranges} mm です。",
|
|
78
|
+
"figure.text.size-range-too-small": "この図形は {axis} を {mm} mm より小さくすると {part} 部品の長さが 0 になりますが、それより小さく置けると宣言されているため、この変更はできません。「置けるサイズ」で測り直すと範囲が合います。",
|
|
79
|
+
"figure.text.size-range-too-large": "この図形は {axis} を {mm} mm より大きくすると {part} 部品を描けませんが、それより大きく置けると宣言されているため、この変更はできません。「置けるサイズ」で測り直すと範囲が合います。",
|
|
70
80
|
"figure.text.mounting-face-needs-space": "先に空間を宣言すると選べます。",
|
|
71
81
|
"figure.text.part-is-referenced": "'{part}' には他の部品が取り付けられているか、そのサイズに従っているため削除できません。先にその連結を外してください。",
|
|
72
82
|
"figure.text.part-name-invalid": "'{name}' は部品名に使えません。英字で始め、英字・数字・-・_ のみ使えます。",
|
|
@@ -372,6 +382,9 @@
|
|
|
372
382
|
"figure.text.designate-in-properties": "まだ指定された部品がありません。部品を選び、プロパティタブで「能力での役割」を指定してください。",
|
|
373
383
|
"figure.text.detail-level-explained": "S は標識・柱、M はほとんどの設備、L は画面の中心となる大きな設備に使います。等級で部品数の上限が決まります。",
|
|
374
384
|
"figure.text.discard-changes-and-start-new": "保存していない変更があります。破棄して新しく始めますか?",
|
|
385
|
+
"figure.text.leave-without-saving": "保存していない変更があります。破棄して移動しますか?",
|
|
386
|
+
"figure.button.leave-without-saving": "破棄して移動",
|
|
387
|
+
"figure.button.keep-editing": "編集を続ける",
|
|
375
388
|
"figure.text.does-not-block": "止めないもの",
|
|
376
389
|
"figure.text.emissive-explained": "面そのものの明るさです。0 なら光らず、周囲は照らしません。",
|
|
377
390
|
"figure.text.enter-a-type-name": "タイプ名を入力してください。",
|
|
@@ -547,7 +560,7 @@
|
|
|
547
560
|
"figure.label.facing-flush": "same plane",
|
|
548
561
|
"figure.label.gap": "gap (mm)",
|
|
549
562
|
"figure.label.align": "within the face",
|
|
550
|
-
"figure.label.align-centre": "
|
|
563
|
+
"figure.label.align-centre": "中央",
|
|
551
564
|
"figure.label.align-min": "low edge",
|
|
552
565
|
"figure.label.align-max": "high edge",
|
|
553
566
|
"figure.button.fasten": "Fasten",
|
|
@@ -673,5 +686,27 @@
|
|
|
673
686
|
"figure.label.group-figure": "図形",
|
|
674
687
|
"figure.label.group-standing": "置き方",
|
|
675
688
|
"figure.label.group-catalog": "一覧情報",
|
|
676
|
-
"figure.button.control-settings": "名前・範囲・時間"
|
|
689
|
+
"figure.button.control-settings": "名前・範囲・時間",
|
|
690
|
+
"figure.label.align-x": "左右の位置",
|
|
691
|
+
"figure.label.align-y": "上下の位置",
|
|
692
|
+
"figure.label.align-z": "前後の位置",
|
|
693
|
+
"figure.label.align-x-min": "左端に揃える",
|
|
694
|
+
"figure.label.align-x-max": "右端に揃える",
|
|
695
|
+
"figure.label.align-y-min": "下端に揃える",
|
|
696
|
+
"figure.label.align-y-max": "上端に揃える",
|
|
697
|
+
"figure.label.align-z-min": "後端に揃える",
|
|
698
|
+
"figure.label.align-z-max": "前端に揃える",
|
|
699
|
+
"figure.label.align-mm": "中央からの距離 (mm)",
|
|
700
|
+
"figure.label.motion-follow": "連動",
|
|
701
|
+
"figure.label.motion-own-control": "自分の操作値",
|
|
702
|
+
"figure.label.motion-multiplier": "倍率",
|
|
703
|
+
"figure.label.motion-offset": "オフセット",
|
|
704
|
+
"figure.label.motion-follows": "{joint} に連動 × {multiplier} + {offset}",
|
|
705
|
+
"figure.label.face-opening-left": "開口部 左",
|
|
706
|
+
"figure.label.face-opening-right": "開口部 右",
|
|
707
|
+
"figure.label.face-opening-top": "開口部 上",
|
|
708
|
+
"figure.label.repeat-fills": "長さを満たすだけ",
|
|
709
|
+
"figure.button.make-repeat": "繰り返しを作る",
|
|
710
|
+
"figure.button.remove-repeat": "繰り返しをなくす",
|
|
711
|
+
"figure.label.twist": "ひねり (°)"
|
|
677
712
|
}
|
package/translations/ko.json
CHANGED
|
@@ -66,9 +66,19 @@
|
|
|
66
66
|
"figure.text.repeat-what-pitch-gives": "길이 {length} mm 에서 {count}개 · 최대 길이 {longest} mm 까지 놓입니다 (한 줄 {limit}개).",
|
|
67
67
|
"figure.text.repeat-pitch-too-small": "pitch 는 {min} mm 이상이어야 합니다. 저작한 길이 {length} mm 에서 {limit}개를 넘기 때문입니다.",
|
|
68
68
|
"figure.text.gizmo-joint-places": "{part} 부품은 관절에 달려 있어 관절이 자리를 정합니다. 움직임 탭의 조작값으로 움직이세요.",
|
|
69
|
+
"figure.label.motion-continuous": "끝없이 회전",
|
|
70
|
+
"figure.text.fasten-before-moving": "먼저 다른 부품이나 바닥에 붙여야 움직임을 줄 수 있습니다.",
|
|
71
|
+
"figure.text.joint-range-holds-zero": "범위에 0이 들어가야 합니다. 0이 기본 자세입니다.",
|
|
72
|
+
"figure.text.gizmo-joint-mimic": "{joint} 관절은 {leader} 관절을 따라 움직입니다. {leader}을(를) 움직이세요.",
|
|
73
|
+
"figure.text.gizmo-joint-fixed-turn": "{joint} 관절은 돌지 않습니다. 돌리려면 움직임 탭에서 회전을 주세요.",
|
|
74
|
+
"figure.text.gizmo-joint-turned-slide": "{joint} 관절은 미끄러지기만 합니다. 이동 손잡이로 움직이세요.",
|
|
75
|
+
"figure.text.gizmo-joint-origin-follows": "{joint} 관절의 자리는 다른 부품을 따라 정해집니다. 움직임 탭의 붙이기에서 바꾸세요.",
|
|
76
|
+
"figure.text.gizmo-joint-off-axis": "{joint} 관절은 {axis}축으로만 돕니다. {axis} 회전 손잡이를 쓰세요.",
|
|
77
|
+
"figure.text.gizmo-size-follows": "{part}의 {axis} 치수는 다른 치수를 따라 정해집니다. 움직임 탭의 치수에서 바꾸세요.",
|
|
69
78
|
"figure.text.gizmo-fastening-places": "{part} 부품은 다른 부품의 면에 붙어 있어 그 면이 자리를 정합니다. 옮기려면 먼저 떼세요.",
|
|
70
|
-
"figure.text.
|
|
71
|
-
"figure.text.
|
|
79
|
+
"figure.text.instance-range-can-widen": "지금 부품으로 다시 재면 {ranges} mm 입니다.",
|
|
80
|
+
"figure.text.size-range-too-small": "이 도형은 {axis} 축을 {mm} mm 보다 작게 하면 {part} 부품의 길이가 0 이 되는데, 그보다 작게 놓일 수 있다고 선언되어 있어 이 변경을 할 수 없습니다. 「놓일 수 있는 크기」에서 다시 재기를 누르면 범위가 맞춰집니다.",
|
|
81
|
+
"figure.text.size-range-too-large": "이 도형은 {axis} 축을 {mm} mm 보다 크게 하면 {part} 부품을 그릴 수 없는데, 그보다 크게 놓일 수 있다고 선언되어 있어 이 변경을 할 수 없습니다. 「놓일 수 있는 크기」에서 다시 재기를 누르면 범위가 맞춰집니다.",
|
|
72
82
|
"figure.text.mounting-face-needs-space": "공간을 먼저 선언해야 고를 수 있습니다.",
|
|
73
83
|
"figure.text.part-is-referenced": "'{part}' 에 다른 부품이 붙어 있거나 그 크기를 따르고 있어 지울 수 없습니다. 그 연결을 먼저 푸세요.",
|
|
74
84
|
"figure.text.part-name-invalid": "'{name}' 은 부품 이름으로 쓸 수 없습니다. 영문자로 시작하고 영문자·숫자·-·_ 만 씁니다.",
|
|
@@ -374,6 +384,9 @@
|
|
|
374
384
|
"figure.text.designate-in-properties": "아직 지정된 부품이 없습니다. 부품을 선택하고 속성 탭에서 「능력에서의 역할」을 지정하세요.",
|
|
375
385
|
"figure.text.detail-level-explained": "S 는 표지·기둥, M 은 대부분의 설비, L 은 화면의 중심이 되는 큰 설비에 씁니다. 등급에 따라 부품 수 한도가 정해집니다.",
|
|
376
386
|
"figure.text.discard-changes-and-start-new": "저장하지 않은 변경이 있습니다. 버리고 새로 시작할까요?",
|
|
387
|
+
"figure.text.leave-without-saving": "저장하지 않은 변경이 있습니다. 버리고 나갈까요?",
|
|
388
|
+
"figure.button.leave-without-saving": "버리고 나가기",
|
|
389
|
+
"figure.button.keep-editing": "계속 편집",
|
|
377
390
|
"figure.text.does-not-block": "막지는 않는 것",
|
|
378
391
|
"figure.text.emissive-explained": "면 자체의 밝기입니다. 0 이면 빛나지 않고, 주변은 밝히지 않습니다.",
|
|
379
392
|
"figure.text.enter-a-type-name": "타입 이름을 입력하세요.",
|
|
@@ -515,7 +528,7 @@
|
|
|
515
528
|
"figure.text.tools-explained": "왼쪽부터 X 가운데, Z 가운데, 바닥에 붙이기, X 대칭, Z 대칭, 눈금 맞춤입니다.",
|
|
516
529
|
"figure.text.triangle-count": "삼각형 {count}개",
|
|
517
530
|
"figure.text.two-keys-at-least": "키를 두 개 이상 추가하세요.",
|
|
518
|
-
"figure.text.type-name-already-taken": "타입 이름 {type} 은 이미 쓰이고 있습니다.
|
|
531
|
+
"figure.text.type-name-already-taken": "타입 이름 {type} 은 이미 쓰이고 있습니다. 다른 이름을 정해 주세요.",
|
|
519
532
|
"figure.text.type-name-cannot-change": "타입 이름은 만든 뒤에 바꿀 수 없습니다.",
|
|
520
533
|
"figure.text.type-name-will-not-release": "이 이름으로는 발행할 수 없습니다. 영문 대문자·숫자·밑줄(연속 불가)로 3~32자를 입력하세요(예: AGV_LIFTER).",
|
|
521
534
|
"figure.text.unit-is-a-word-not-a-rule": "표시용 단위입니다 (예: mm · deg · %). 값 계산에는 쓰지 않습니다.",
|
|
@@ -673,5 +686,27 @@
|
|
|
673
686
|
"figure.label.group-figure": "도형",
|
|
674
687
|
"figure.label.group-standing": "놓이는 자리",
|
|
675
688
|
"figure.label.group-catalog": "목록 정보",
|
|
676
|
-
"figure.button.control-settings": "이름 · 범위 · 시간"
|
|
689
|
+
"figure.button.control-settings": "이름 · 범위 · 시간",
|
|
690
|
+
"figure.label.align-x": "좌우 자리",
|
|
691
|
+
"figure.label.align-y": "위아래 자리",
|
|
692
|
+
"figure.label.align-z": "앞뒤 자리",
|
|
693
|
+
"figure.label.align-x-min": "왼쪽 끝 맞춤",
|
|
694
|
+
"figure.label.align-x-max": "오른쪽 끝 맞춤",
|
|
695
|
+
"figure.label.align-y-min": "아래 끝 맞춤",
|
|
696
|
+
"figure.label.align-y-max": "위 끝 맞춤",
|
|
697
|
+
"figure.label.align-z-min": "뒤 끝 맞춤",
|
|
698
|
+
"figure.label.align-z-max": "앞 끝 맞춤",
|
|
699
|
+
"figure.label.align-mm": "가운데서 거리 (mm)",
|
|
700
|
+
"figure.label.motion-follow": "따라 움직임",
|
|
701
|
+
"figure.label.motion-own-control": "제 조작값으로",
|
|
702
|
+
"figure.label.motion-multiplier": "배율",
|
|
703
|
+
"figure.label.motion-offset": "더하기",
|
|
704
|
+
"figure.label.motion-follows": "따라 움직임: {joint} × {multiplier} + {offset}",
|
|
705
|
+
"figure.label.face-opening-left": "개구부 왼쪽",
|
|
706
|
+
"figure.label.face-opening-right": "개구부 오른쪽",
|
|
707
|
+
"figure.label.face-opening-top": "개구부 위",
|
|
708
|
+
"figure.label.repeat-fills": "길이를 채우는 만큼",
|
|
709
|
+
"figure.button.make-repeat": "되풀이 만들기",
|
|
710
|
+
"figure.button.remove-repeat": "되풀이 없애기",
|
|
711
|
+
"figure.label.twist": "비틀기 (°)"
|
|
677
712
|
}
|
package/translations/ms.json
CHANGED
|
@@ -64,9 +64,19 @@
|
|
|
64
64
|
"figure.text.repeat-what-pitch-gives": "{count} pada panjang {length} mm · boleh diletakkan sehingga {longest} mm ({limit} sebaris).",
|
|
65
65
|
"figure.text.repeat-pitch-too-small": "pitch mesti sekurang-kurangnya {min} mm: pada panjang asal {length} mm ia akan melebihi {limit}.",
|
|
66
66
|
"figure.text.gizmo-joint-places": "sendi menentukan kedudukan {part}. gerakkannya dengan kawalannya di tab gerakan.",
|
|
67
|
+
"figure.label.motion-continuous": "putaran tanpa henti",
|
|
68
|
+
"figure.text.fasten-before-moving": "lekatkan pada bahagian lain atau lantai dahulu; kemudian ia boleh bergerak.",
|
|
69
|
+
"figure.text.joint-range-holds-zero": "julat mesti mengandungi 0, pose rehat.",
|
|
70
|
+
"figure.text.gizmo-joint-mimic": "sendi {joint} mengikut {leader}. gerakkan {leader}.",
|
|
71
|
+
"figure.text.gizmo-joint-fixed-turn": "sendi {joint} tidak berputar. berikan putaran di tab gerakan.",
|
|
72
|
+
"figure.text.gizmo-joint-turned-slide": "sendi {joint} hanya menggelongsor. gunakan pemegang alih.",
|
|
73
|
+
"figure.text.gizmo-joint-origin-follows": "kedudukan sendi {joint} mengikut bahagian lain. ubahnya di bawah pelekatan dalam tab gerakan.",
|
|
74
|
+
"figure.text.gizmo-joint-off-axis": "sendi {joint} hanya berputar pada paksi {axis}. gunakan pemegang putar {axis}.",
|
|
75
|
+
"figure.text.gizmo-size-follows": "saiz {axis} {part} mengikut ukuran lain. ubahnya di bawah saiz dalam tab gerakan.",
|
|
67
76
|
"figure.text.gizmo-fastening-places": "{part} dilekatkan pada muka bahagian lain, yang menentukan kedudukannya. tanggalkan dahulu untuk menggerakkannya.",
|
|
68
|
-
"figure.text.
|
|
69
|
-
"figure.text.
|
|
77
|
+
"figure.text.instance-range-can-widen": "diukur semula dengan bahagian semasa: {ranges} mm.",
|
|
78
|
+
"figure.text.size-range-too-small": "di bawah {mm} mm pada {axis} bahagian {part} tiada panjang, namun figura diisytiharkan boleh diletakkan lebih kecil, jadi perubahan ini tidak boleh dibuat. gunakan \"ukur semula\" di bawah saiz yang boleh diletakkan.",
|
|
79
|
+
"figure.text.size-range-too-large": "di atas {mm} mm pada {axis} bahagian {part} tidak boleh dilukis, namun figura diisytiharkan boleh diletakkan lebih besar, jadi perubahan ini tidak boleh dibuat. gunakan \"ukur semula\" di bawah saiz yang boleh diletakkan.",
|
|
70
80
|
"figure.text.mounting-face-needs-space": "isytiharkan ruang dahulu untuk memilih.",
|
|
71
81
|
"figure.text.part-is-referenced": "'{part}' tidak boleh dipadam: bahagian lain dipasang padanya atau mengikut saiznya. Leraikan pautan itu dahulu.",
|
|
72
82
|
"figure.text.part-name-invalid": "'{name}' tidak boleh menjadi nama bahagian. Mulakan dengan huruf dan gunakan huruf, digit, - dan _.",
|
|
@@ -372,6 +382,9 @@
|
|
|
372
382
|
"figure.text.designate-in-properties": "belum ada yang ditetapkan. pilih bahagian dan tetapkan peranannya dalam tab sifat.",
|
|
373
383
|
"figure.text.detail-level-explained": "S untuk papan tanda dan tiang, M untuk kebanyakan peralatan, L untuk peralatan besar yang menjadi tumpuan. tahap menentukan had bahagian.",
|
|
374
384
|
"figure.text.discard-changes-and-start-new": "ada perubahan belum disimpan. buang dan mulakan yang baharu?",
|
|
385
|
+
"figure.text.leave-without-saving": "ada perubahan yang belum disimpan. buang dan keluar?",
|
|
386
|
+
"figure.button.leave-without-saving": "buang dan keluar",
|
|
387
|
+
"figure.button.keep-editing": "teruskan menyunting",
|
|
375
388
|
"figure.text.does-not-block": "tidak menghalang",
|
|
376
389
|
"figure.text.emissive-explained": "kecerahan permukaan itu sendiri. 0 bermaksud tidak bercahaya, dan ia tidak menerangi sekeliling.",
|
|
377
390
|
"figure.text.enter-a-type-name": "masukkan nama jenis.",
|
|
@@ -547,7 +560,7 @@
|
|
|
547
560
|
"figure.label.facing-flush": "same plane",
|
|
548
561
|
"figure.label.gap": "gap (mm)",
|
|
549
562
|
"figure.label.align": "within the face",
|
|
550
|
-
"figure.label.align-centre": "
|
|
563
|
+
"figure.label.align-centre": "di tengah",
|
|
551
564
|
"figure.label.align-min": "low edge",
|
|
552
565
|
"figure.label.align-max": "high edge",
|
|
553
566
|
"figure.button.fasten": "Fasten",
|
|
@@ -673,5 +686,27 @@
|
|
|
673
686
|
"figure.label.group-figure": "Rajah",
|
|
674
687
|
"figure.label.group-standing": "Kedudukan letak",
|
|
675
688
|
"figure.label.group-catalog": "Maklumat katalog",
|
|
676
|
-
"figure.button.control-settings": "Nama, julat dan masa"
|
|
689
|
+
"figure.button.control-settings": "Nama, julat dan masa",
|
|
690
|
+
"figure.label.align-x": "kiri-kanan",
|
|
691
|
+
"figure.label.align-y": "atas-bawah",
|
|
692
|
+
"figure.label.align-z": "depan-belakang",
|
|
693
|
+
"figure.label.align-x-min": "rata tepi kiri",
|
|
694
|
+
"figure.label.align-x-max": "rata tepi kanan",
|
|
695
|
+
"figure.label.align-y-min": "rata tepi bawah",
|
|
696
|
+
"figure.label.align-y-max": "rata tepi atas",
|
|
697
|
+
"figure.label.align-z-min": "rata tepi belakang",
|
|
698
|
+
"figure.label.align-z-max": "rata tepi depan",
|
|
699
|
+
"figure.label.align-mm": "jarak dari tengah (mm)",
|
|
700
|
+
"figure.label.motion-follow": "bergerak bersama",
|
|
701
|
+
"figure.label.motion-own-control": "kawalan sendiri",
|
|
702
|
+
"figure.label.motion-multiplier": "pendarab",
|
|
703
|
+
"figure.label.motion-offset": "ofset",
|
|
704
|
+
"figure.label.motion-follows": "bergerak bersama {joint} × {multiplier} + {offset}",
|
|
705
|
+
"figure.label.face-opening-left": "bukaan kiri",
|
|
706
|
+
"figure.label.face-opening-right": "bukaan kanan",
|
|
707
|
+
"figure.label.face-opening-top": "bukaan atas",
|
|
708
|
+
"figure.label.repeat-fills": "sebanyak yang memenuhi panjang",
|
|
709
|
+
"figure.button.make-repeat": "buat baris",
|
|
710
|
+
"figure.button.remove-repeat": "buang baris",
|
|
711
|
+
"figure.label.twist": "pusingan (°)"
|
|
677
712
|
}
|