@things-factory/figure-ui 10.1.265 → 10.1.267
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/align.ts +13 -1
- package/client/modeller/clips.ts +90 -1
- package/client/modeller/editor-styles.ts +36 -0
- package/client/modeller/editor.ts +53 -6
- package/client/modeller/figure-clips.ts +82 -34
- package/client/modeller/joint-drag.ts +32 -2
- package/dist-client/modeller/align.d.ts +5 -0
- package/dist-client/modeller/align.js +10 -1
- package/dist-client/modeller/align.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +54 -5
- package/dist-client/modeller/clips.js +89 -0
- package/dist-client/modeller/clips.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +36 -0
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +7 -0
- package/dist-client/modeller/editor.js +58 -5
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-clips.d.ts +13 -1
- package/dist-client/modeller/figure-clips.js +87 -34
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/joint-drag.d.ts +21 -2
- package/dist-client/modeller/joint-drag.js +20 -1
- package/dist-client/modeller/joint-drag.js.map +1 -1
- package/dist-client/modeller/links.d.ts +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/test/align.test.ts +18 -0
- package/test/clips.test.ts +83 -0
- package/test/joint-drag.test.ts +49 -0
- package/test/joint-frames.test.ts +3 -2
- package/translations/en.json +22 -2
- package/translations/es.json +22 -2
- package/translations/ja.json +22 -2
- package/translations/ko.json +22 -2
- package/translations/ms.json +22 -2
- package/translations/zh.json +22 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.267",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"clean": "npm run clean:server && npm run clean:client"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@hatiolab/figure-model": "^0.12.
|
|
31
|
-
"@hatiolab/things-scene": "^10.1.
|
|
30
|
+
"@hatiolab/figure-model": "^0.12.4",
|
|
31
|
+
"@hatiolab/things-scene": "^10.1.111",
|
|
32
32
|
"@material/web": "^2.0.0",
|
|
33
33
|
"@operato/data-grist": "^10.29.69",
|
|
34
34
|
"@operato/graphql": "^10.29.43",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"@operato/slot-layout-editor": "^10.29.58",
|
|
43
43
|
"@operato/styles": "^10.0.0",
|
|
44
44
|
"@operato/utils": "^10.29.43",
|
|
45
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
46
|
-
"@things-factory/figure-service": "^10.1.
|
|
47
|
-
"@things-factory/figure-viewer": "^10.1.
|
|
48
|
-
"@things-factory/setting-base": "^10.1.
|
|
45
|
+
"@things-factory/ai-assistant": "^10.1.267",
|
|
46
|
+
"@things-factory/figure-service": "^10.1.267",
|
|
47
|
+
"@things-factory/figure-viewer": "^10.1.266",
|
|
48
|
+
"@things-factory/setting-base": "^10.1.267",
|
|
49
49
|
"three": "^0.185.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "155585edfa5de984ec1d045b0378f8ffd623a6f4"
|
|
52
52
|
}
|
package/test/align.test.ts
CHANGED
|
@@ -131,3 +131,21 @@ test('다리처럼 아래 붙은 것이 있으면 대칭 전에 묻는다 —
|
|
|
131
131
|
const made = editFigurePart(a, mirrorEditOf('fl-hip', across.form))
|
|
132
132
|
assert.deepEqual(made.appearance.map(x => x.target).filter(t => t.startsWith('fr-')).sort(), ['fr-hip', 'fr-shin'])
|
|
133
133
|
})
|
|
134
|
+
|
|
135
|
+
test('대칭 양식은 함께 복사될 클립을 말하고, 복사본은 그 클립에서 같은 값으로 움직인다 (2026-11-09)', async () => {
|
|
136
|
+
const { mirroredClipsOf } = await import('../client/modeller/align.ts')
|
|
137
|
+
let a = probe({ x: 0, y: 300, z: 0 })
|
|
138
|
+
for (const id of ['fl-hip', 'fl-shin']) a = addFigureBox(a, { id, size: { x: 40, y: 100, z: 40 }, position: { x: 0, y: 0, z: 0 } })
|
|
139
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'body', to: { plane: 'mounting-plane' } })
|
|
140
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'fl-hip', to: { part: 'body', face: 'bottom' }, align: { x: { mm: 60 } } })
|
|
141
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'fl-shin', to: { part: 'fl-hip', face: 'bottom' } })
|
|
142
|
+
const shin = a.joints!.find(j => j.body1.part === 'fl-shin')!
|
|
143
|
+
a = applyFigureAuthoring(a, { kind: 'set-joint', joint: { ...shin, motion: [{ type: 'revolute', axis: 'Z', lowerLimit: 0, upperLimit: 120, state: { id: 'fl-knee' } }] } })
|
|
144
|
+
a = editFigurePart(a, { kind: 'set-driver', state: 'fl-knee', clip: 'walk', duration: 2, keys: [{ seconds: 0, value: 0 }, { seconds: 1, value: 90 }, { seconds: 2, value: 0 }], interpolation: 'smooth' })
|
|
145
|
+
assert.deepEqual(mirroredClipsOf(a, 'fl-hip', true), ['walk'], '아래 붙은 무릎의 클립')
|
|
146
|
+
assert.deepEqual(mirroredClipsOf(a, 'fl-hip', false), [], '엉덩이만 복사하면 클립은 따라오지 않는다')
|
|
147
|
+
const made = editFigurePart(a, mirrorEditOf('fl-hip', mirrorPlanOf(a, 'fl-hip', 'x').form))
|
|
148
|
+
const track = made.drivers!.find(d => d.clip === 'walk' && d.state === 'fr-knee')
|
|
149
|
+
assert.deepEqual(track && [track.keys, track.interpolation], [[{ at: 0, value: 0 }, { at: 0.5, value: 90 }, { at: 1, value: 0 }], 'smooth'])
|
|
150
|
+
assert.match(readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8'), /mirroredClipsOf\(this\.shown\(\)!\.asset, m\.part, m\.form\.chain\)/)
|
|
151
|
+
})
|
package/test/clips.test.ts
CHANGED
|
@@ -91,3 +91,86 @@ test('a track copied onto another control a while later; a clip copy takes a fre
|
|
|
91
91
|
assert.equal(freeClipNameOf(asset, 'turn'), 'turn-copy')
|
|
92
92
|
assert.equal(freeClipNameOf(editFigurePart(asset, { kind: 'set-clip', clip: 'turn', name: 'turn-copy' }), 'turn-copy'), 'turn-copy-copy')
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
/* Review 2026-11-09: a track starts from the pose, a pose is kept in one step, a clip on a played control joins a group. */
|
|
96
|
+
import { newClipEdits, newTrackKeys, rememberedPoseEdits } from '../client/modeller/clips.ts'
|
|
97
|
+
|
|
98
|
+
const applied = (asset: FigureAsset, edits: unknown[]) => edits.reduce<FigureAsset>((a, e) => editFigurePart(a, e as never), asset)
|
|
99
|
+
|
|
100
|
+
test('새 트랙은 지금 자세를 처음부터 끝까지 유지 — 범위 왕복은 따로 고르는 프리셋', () => {
|
|
101
|
+
assert.deepEqual(newTrackKeys({ min: -180, max: 180 }, 35, 2), [{ seconds: 0, value: 35 }, { seconds: 2, value: 35 }])
|
|
102
|
+
assert.deepEqual(newTrackKeys({ min: 0, max: 60 }, 20, 3, 'range'), [{ seconds: 0, value: 0 }, { seconds: 1.5, value: 60 }, { seconds: 3, value: 0 }])
|
|
103
|
+
assert.deepEqual(newTrackKeys({ min: 5, max: 9 }, undefined, 2), [{ seconds: 0, value: 5 }, { seconds: 2, value: 5 }], '값을 모르면 범위의 시작')
|
|
104
|
+
/* The panel's wiring: the default button holds the pose, the range one is the preset. */
|
|
105
|
+
const panel = read('../client/modeller/figure-clips.ts')
|
|
106
|
+
assert.match(panel, /newTrackKeys\(input!, this\.values\[stateId!\], duration\)\)/)
|
|
107
|
+
assert.match(panel, /newTrackKeys\(input!, this\.values\[stateId!\], duration, 'range'\)/)
|
|
108
|
+
/* Several edits go up as one step, and the editor applies them as one. */
|
|
109
|
+
assert.match(read('../client/modeller/editor.ts'), /<figure-clips[\s\S]*?@part-edits=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.applyRecipe\(e\.detail\.edits\)\)\}[\s\S]*?<\/figure-clips>/)
|
|
110
|
+
assert.doesNotMatch(panel, /value: input!\.min \},\s*\{ seconds: duration, value: input!\.max/)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('재생 중인 조작값에 새 클립 — 그룹 없는 클립과 새 동작 그룹으로, 있는 그룹엔 그 그룹으로', () => {
|
|
114
|
+
const closed = editFigurePart(machine(), { kind: 'set-driver', state: 'shift', clip: '닫기', duration: 2, keys: [{ seconds: 0, value: 60 }, { seconds: 2, value: 0 }] })
|
|
115
|
+
const free = newClipEdits(closed, 'spin', '돌기', 2, [{ seconds: 0, value: 0 }, { seconds: 2, value: 90 }])
|
|
116
|
+
assert.deepEqual(free.join, { kind: 'free' })
|
|
117
|
+
assert.equal(free.edits.length, 1)
|
|
118
|
+
|
|
119
|
+
const open = newClipEdits(closed, 'shift', '열기', 2, [{ seconds: 0, value: 0 }, { seconds: 2, value: 60 }])
|
|
120
|
+
assert.equal(open.join.kind, 'new-group')
|
|
121
|
+
const grouped = applied(closed, open.edits)
|
|
122
|
+
assert.deepEqual(grouped.motionGroups, [{ id: 'motion', label: 'shift', clips: ['닫기', '열기'], defaultClip: '닫기', transitionSeconds: 0.2 }])
|
|
123
|
+
assert.deepEqual(clipsOf(grouped).map(c => c.name), ['닫기', '열기'])
|
|
124
|
+
|
|
125
|
+
/* A third clip on the same control goes into the group already there; no second group is made. */
|
|
126
|
+
const half = newClipEdits(grouped, 'shift', '반열기', 2, [{ seconds: 0, value: 0 }, { seconds: 2, value: 30 }])
|
|
127
|
+
assert.deepEqual(half.join, { kind: 'group', group: 'motion', label: 'shift' })
|
|
128
|
+
assert.deepEqual(applied(grouped, half.edits).motionGroups!.map(g => g.clips), [['닫기', '열기', '반열기']])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('이 자세 기억하기 — 바뀐 조작값만, 한 묶음으로; 다른 동작이 쓰는 조작값은 이름만', () => {
|
|
132
|
+
const asset = editFigurePart(machine(), { kind: 'set-driver', state: 'shift', clip: 'cycle', duration: 4, keys: [{ seconds: 0, value: 0 }, { seconds: 4, value: 60 }] })
|
|
133
|
+
const base = { shift: 30, spin: 0 }
|
|
134
|
+
/* At 2 s the clip has shift at 30: unchanged, no key. spin moved to 45 since: a new track from 0 to 45 at 2 s. */
|
|
135
|
+
const kept = rememberedPoseEdits(asset, 'cycle', 2, { shift: 30, spin: 45 }, base)
|
|
136
|
+
assert.deepEqual(kept.skipped, [])
|
|
137
|
+
assert.deepEqual(kept.edits, [{ kind: 'set-driver', state: 'spin', clip: 'cycle', keys: [{ seconds: 0, value: 0 }, { seconds: 2, value: 45 }] }])
|
|
138
|
+
const after = applied(asset, kept.edits)
|
|
139
|
+
assert.equal(poseAt(after, 'cycle', 2).find(c => c.input === 'spin')!.value, 45)
|
|
140
|
+
|
|
141
|
+
/* shift moved off the clip's curve: a key there, in the same step. */
|
|
142
|
+
const both = rememberedPoseEdits(after, 'cycle', 2, { shift: 50, spin: 45 }, { shift: 30, spin: 45 })
|
|
143
|
+
assert.equal(both.edits.length, 1)
|
|
144
|
+
assert.deepEqual(clipsOf(applied(after, both.edits))[0]!.tracks.find(t => t.state === 'shift')!.keys, [{ seconds: 0, value: 0 }, { seconds: 2, value: 50 }, { seconds: 4, value: 60 }])
|
|
145
|
+
|
|
146
|
+
/* Nothing moved: nothing to keep. */
|
|
147
|
+
assert.deepEqual(rememberedPoseEdits(asset, 'cycle', 2, { shift: 30, spin: 0 }, base).edits, [], '재생하지 않는 조작값도 기준 자세 그대로면 트랙을 만들지 않는다')
|
|
148
|
+
assert.deepEqual(rememberedPoseEdits(after, 'cycle', 2, { shift: 30, spin: 45 }, { shift: 30, spin: 45 }).edits, [])
|
|
149
|
+
|
|
150
|
+
/* At 0 a new track holds the pose throughout. */
|
|
151
|
+
assert.deepEqual(rememberedPoseEdits(asset, 'cycle', 0, { shift: 0, spin: 10 }, base).edits, [{ kind: 'set-driver', state: 'spin', clip: 'cycle', keys: [{ seconds: 0, value: 10 }, { seconds: 4, value: 10 }] }])
|
|
152
|
+
|
|
153
|
+
/* spin played by another clip in no group: not taken, named. */
|
|
154
|
+
const other = editFigurePart(asset, { kind: 'set-driver', state: 'spin', clip: 'turn', duration: 2, keys: [{ seconds: 0, value: 0 }, { seconds: 2, value: 90 }] })
|
|
155
|
+
const told = rememberedPoseEdits(other, 'cycle', 2, { shift: 30, spin: 45 }, base)
|
|
156
|
+
assert.deepEqual(told, { edits: [], skipped: ['spin'] })
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('키 사이 움직임 — 트랙마다 일정한 속도 · 부드럽게 · 즉시 전환을 고르고, 부드러운 트랙은 키 시점으로만 민다 (2026-11-09)', async () => {
|
|
160
|
+
const { shiftOffKeyOf } = await import('../client/modeller/clips.ts')
|
|
161
|
+
const asset = editFigurePart(machine(), { kind: 'set-driver', state: 'shift', clip: 'cycle', duration: 4, keys: [{ seconds: 0, value: 0 }, { seconds: 1, value: 60 }, { seconds: 4, value: 0 }], interpolation: 'smooth' })
|
|
162
|
+
const track = clipsOf(asset)[0]!.tracks[0]!
|
|
163
|
+
assert.equal(track.interpolation, 'smooth', '읽은 트랙이 보간을 가진다')
|
|
164
|
+
/* Keys at 0, 1, 4 s: the new start may be at 1 s (3 s later) or at 0 s. */
|
|
165
|
+
assert.deepEqual(shiftOffKeyOf(track, 4, 1), [0, 3])
|
|
166
|
+
assert.equal(shiftOffKeyOf(track, 4, 3), null)
|
|
167
|
+
assert.equal(shiftOffKeyOf(track, 4, -1), null, '−1 초는 3 초와 같다')
|
|
168
|
+
assert.equal(shiftOffKeyOf({ ...track, interpolation: 'linear' }, 4, 1), null, '직선은 어디로든')
|
|
169
|
+
/* What the panel allows, the kernel takes: the same curve 3 s later. */
|
|
170
|
+
const { copiedTrackEdit } = await import('../client/modeller/clips.ts')
|
|
171
|
+
assert.doesNotThrow(() => copiedTrackEdit(asset, clipsOf(asset)[0]!, track, 'shift', 3))
|
|
172
|
+
assert.throws(() => copiedTrackEdit(asset, clipsOf(asset)[0]!, track, 'shift', 1))
|
|
173
|
+
const panel = read('../client/modeller/figure-clips.ts')
|
|
174
|
+
assert.match(panel, /\['linear', 'smooth', 'step'\] as const/)
|
|
175
|
+
assert.match(panel, /\?disabled=\$\{this\.locked \|\| same \|\| !!fits\}/)
|
|
176
|
+
})
|
package/test/joint-drag.test.ts
CHANGED
|
@@ -102,3 +102,52 @@ test('크기 끌기는 관절이 아니라 부품의 치수 — 따라가는 치
|
|
|
102
102
|
assert.deepEqual(gizmoChanges('link', reach, { scale: scaled.scale }, sizes), [{ kind: 'set-design', input: 'link.size.y', unit: 'mm', value: 420 }])
|
|
103
103
|
assert.deepEqual(gizmoChanges('link', reach, { scale: [1, 1, 1.5] }, sizes), [], '따라가는 깊이만 끌면 아무것도 쓰지 않는다 — 편집기가 까닭을 말한다')
|
|
104
104
|
})
|
|
105
|
+
|
|
106
|
+
/* Review 2026-11-09: a drag poses a joint that moves; where it sits moves only while 「관절 위치 편집」 is on. */
|
|
107
|
+
import { jointHandleOf } from '../client/modeller/joint-drag.ts'
|
|
108
|
+
import { readFileSync } from 'node:fs'
|
|
109
|
+
import { fileURLToPath } from 'node:url'
|
|
110
|
+
|
|
111
|
+
test('움직이는 관절은 끌면 자세만 — 관절 위치는 「관절 위치 편집」을 켰을 때만 옮긴다', () => {
|
|
112
|
+
const bend = column({ motion: [{ type: 'revolute', axis: 'Z', lowerLimit: -90, upperLimit: 90 }] })
|
|
113
|
+
const part = drawn(bend, 'link')
|
|
114
|
+
const up = { position: [part.pose.t[0]!, part.pose.t[1]! + 25, part.pose.t[2]!] as [number, number, number] }
|
|
115
|
+
assert.deepEqual(jointDragOf(bend, 'link', part as never, up, {}), { kind: 'refused', reason: 'place-off', joint: 'bend' })
|
|
116
|
+
assert.deepEqual(jointDragOf(bend, 'link', part as never, up, {}, { place: true }), { kind: 'joint', joint: { ...bend.joints!.find(j => j.id === 'bend')!, origin: { gap: 25 } } })
|
|
117
|
+
/* Turning still poses it, placing on or off. */
|
|
118
|
+
assert.equal(jointDragOf(bend, 'link', part as never, { rotation: turnedZ(30) }, {}, { place: true })?.kind, 'value')
|
|
119
|
+
|
|
120
|
+
/* A slide: along its axis is its value; across it is its place, only while placing. */
|
|
121
|
+
const slide = column({ motion: [{ type: 'prismatic', axis: 'X', lowerLimit: -100, upperLimit: 100 }] })
|
|
122
|
+
const s = drawn(slide, 'link')
|
|
123
|
+
assert.equal(jointDragOf(slide, 'link', s as never, { position: [s.pose.t[0]! + 40, s.pose.t[1]!, s.pose.t[2]!] }, {})?.kind, 'value')
|
|
124
|
+
const across = { position: [s.pose.t[0]!, s.pose.t[1]! + 25, s.pose.t[2]!] as [number, number, number] }
|
|
125
|
+
assert.equal(jointDragOf(slide, 'link', s as never, across, {})?.kind, 'refused')
|
|
126
|
+
assert.equal(jointDragOf(slide, 'link', s as never, across, {}, { place: true })?.kind, 'joint')
|
|
127
|
+
|
|
128
|
+
/* A fixed joint has no pose: a drag places it, as before. */
|
|
129
|
+
const still = column({})
|
|
130
|
+
assert.equal(jointDragOf(still, 'link', drawn(still, 'link') as never, up, {})?.kind, 'joint')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('선택한 부품의 관절이 무엇으로 움직이는지 — 회전 · 이동 · 따라감 · 축 여럿', () => {
|
|
134
|
+
assert.deepEqual(jointHandleOf(column({ motion: [{ type: 'revolute', axis: 'Z' }] }), 'link'), { kind: 'turn', joint: 'bend' })
|
|
135
|
+
assert.deepEqual(jointHandleOf(column({ motion: [{ type: 'prismatic', axis: 'X', lowerLimit: 0, upperLimit: 10 }] }), 'link'), { kind: 'slide', joint: 'bend' })
|
|
136
|
+
assert.deepEqual(jointHandleOf(column({ motion: [{ type: 'revolute', axis: 'Z' }, { type: 'revolute', axis: 'X' }] }), 'link'), { kind: 'several-axes', joint: 'bend' })
|
|
137
|
+
assert.equal(jointHandleOf(column({}), 'link'), null, '고정 관절은 자세가 없다')
|
|
138
|
+
assert.equal(jointHandleOf(column({}), 'post'), null, '관절에 달리지 않은 부품')
|
|
139
|
+
|
|
140
|
+
/* A finger that follows another: its leader's part, to select it. */
|
|
141
|
+
let pair = column({ motion: [{ type: 'revolute', axis: 'Z', lowerLimit: -90, upperLimit: 90 }] })
|
|
142
|
+
pair = addFigureBox(pair, { id: 'tip', size: { x: 40, y: 100, z: 40 }, position: { x: 0, y: 550, z: 0 } })
|
|
143
|
+
pair = applyFigureAuthoring(pair, { kind: 'set-joint', joint: { id: 'curl', body0: { part: 'link', face: 'top' }, body1: { part: 'tip' }, motion: [{ type: 'revolute', axis: 'Z', mimic: { motion: 'bend', multiplier: -1 } }] } as FigureJoint })
|
|
144
|
+
assert.deepEqual(jointHandleOf(pair, 'tip'), { kind: 'mimic', joint: 'curl', leader: 'bend', leaderPart: 'link' })
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('편집기 배선 — 끌기는 관절 위치 편집 상태를 넘기고, 거절은 켜는 단추 · 따라가는 관절 선택 단추로', () => {
|
|
148
|
+
const editor = readFileSync(fileURLToPath(new URL('../client/modeller/editor.ts', import.meta.url)), 'utf8')
|
|
149
|
+
assert.match(editor, /jointDragOf\(asset, ownerOf\(id\), drawn, result, shown\.inputs, \{ place: this\.jointPlacing \}\)/)
|
|
150
|
+
assert.match(editor, /reason === 'place-off'[\s\S]{0,300}this\.refusals\.point\([\s\S]{0,200}figure\.button\.joint-place-on/)
|
|
151
|
+
assert.match(editor, /reason === 'mimic'[\s\S]{0,800}figure\.button\.select-leader/)
|
|
152
|
+
assert.match(editor, /if \(_changed\.has\('selected'\)\) \{[^}]*this\.jointPlacing = false/)
|
|
153
|
+
})
|
|
@@ -89,7 +89,8 @@ function roundTrips(asset: FigureAsset, id: string) {
|
|
|
89
89
|
/* 30 along Z: the figure's, or at an origin the parent's own Z, as the offset is measured. */
|
|
90
90
|
const z = 'at' in j.body0 ? [0, 1, 2].map(i => drawn(asset, j.body0.part).pose.r[i]![2]!) : [0, 0, 1]
|
|
91
91
|
const by = z.map(v => v * 30)
|
|
92
|
-
|
|
92
|
+
/* A drag that moves where the joint sits: 「관절 위치 편집」 on (review 2026-11-09). */
|
|
93
|
+
const drag = jointDragOf(asset, part, at as never, { position: at.pose.t.map((v, i) => v + by[i]!) as [number, number, number] }, {}, { place: true })
|
|
93
94
|
assert.equal(drag?.kind, 'joint', `${id}: ${JSON.stringify(drag)}`)
|
|
94
95
|
const dragged = reopen(set(asset, (drag as { joint: FigureJoint }).joint))
|
|
95
96
|
out.drag = jointOf(dragged, id)
|
|
@@ -201,7 +202,7 @@ test('frames: every form path keeps the vector axis, pivot, offset (summed too),
|
|
|
201
202
|
}
|
|
202
203
|
/* A summed offset term is not a number to add to: dragged across it, the drag is refused; along the others it stays. */
|
|
203
204
|
const tool = drawn(asset, 'tool')
|
|
204
|
-
const up = jointDragOf(asset, 'tool', tool as never, { position: [tool.pose.t[0]!, tool.pose.t[1]! + 30, tool.pose.t[2]!] }, {})
|
|
205
|
+
const up = jointDragOf(asset, 'tool', tool as never, { position: [tool.pose.t[0]!, tool.pose.t[1]! + 30, tool.pose.t[2]!] }, {}, { place: true })
|
|
205
206
|
assert.deepEqual(up, { kind: 'refused', reason: 'origin-follows', joint: 'tool' })
|
|
206
207
|
})
|
|
207
208
|
|
package/translations/en.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "No places yet. Choose a part and add one here.",
|
|
664
664
|
"figure.text.opening-model": "Opening the model.",
|
|
665
665
|
"figure.text.saved": "Saved",
|
|
666
|
-
"figure.text.clips-explained": "
|
|
666
|
+
"figure.text.clips-explained": "Changes controls over time. Move the time bar to see the pose at that moment. Change the pose with the control sliders or the handles, then press “Record the current pose”: every control that changed is recorded as a key at that moment.",
|
|
667
667
|
"figure.label.clip-duration": "Length (s)",
|
|
668
668
|
"figure.label.clip-time": "Clip time",
|
|
669
669
|
"figure.label.clip-loop": "After the end",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "With this value a part's size comes to 0 or less. If a part follows this value, check its share or difference.",
|
|
1382
1382
|
"figure.button.pipe-end-same": "one size all along",
|
|
1383
1383
|
"figure.text.catalog-conflict": "Another user changed these first ({changes}). Your values were not saved. Save again to replace them with yours.",
|
|
1384
|
-
"figure.text.catalog-value-empty": "empty"
|
|
1384
|
+
"figure.text.catalog-value-empty": "empty",
|
|
1385
|
+
"figure.button.remember-pose": "Record the current pose at {seconds} s",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "Nothing has changed to record. Change a control, then record.",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "These controls are used by another clip and will not be recorded: {controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "The clip “{clip}” already uses this control. “{clip}” and “{name}” will be put in the motion group “{group}”, so that only one plays at a time.",
|
|
1389
|
+
"figure.text.track-joins-group": "The clip “{name}” will be added to the motion group “{group}”, which uses this control. Clips of one group play one at a time.",
|
|
1390
|
+
"figure.button.add-track-range": "Add a sweep over the range",
|
|
1391
|
+
"figure.text.add-track-range-explained": "Adds a track that goes from the control’s minimum to its maximum and back. “Add” adds a track that holds the current pose.",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "The joint {joint} can be moved only while “Edit joint position” is on. Nothing was changed.",
|
|
1393
|
+
"figure.button.joint-place-on": "Edit joint position",
|
|
1394
|
+
"figure.text.joint-placing": "Editing the joint position. Use the move handle to move the joint {joint}.",
|
|
1395
|
+
"figure.button.joint-place-off": "Done",
|
|
1396
|
+
"figure.button.select-leader": "Select the joint {leader}",
|
|
1397
|
+
"figure.text.joint-handle-turn": "Joint {joint}: change its angle with the rotate handle (E).",
|
|
1398
|
+
"figure.text.joint-handle-slide": "Joint {joint}: change its position along the axis with the move handle (W).",
|
|
1399
|
+
"figure.label.clip-interpolation": "Between keys",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "Steady speed",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "Ease in and out",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "Jump",
|
|
1403
|
+
"figure.text.copy-track-off-key": "A smooth track can be shifted only so that it starts at one of its keys. Possible values: {seconds} s",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "The motion in {clips} is copied to the copy too."
|
|
1385
1405
|
}
|
package/translations/es.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "Aún no hay lugares. Elija una pieza y añada uno aquí.",
|
|
664
664
|
"figure.text.opening-model": "Abriendo el modelo.",
|
|
665
665
|
"figure.text.saved": "Guardado",
|
|
666
|
-
"figure.text.clips-explained": "
|
|
666
|
+
"figure.text.clips-explained": "Cambia los controles a lo largo del tiempo. Mueva la barra de tiempo para ver la postura en ese momento. Cambie la postura con los deslizadores de control o las asas y pulse «Registrar la postura actual»: todos los controles cambiados se registran como claves en ese momento.",
|
|
667
667
|
"figure.label.clip-duration": "Duración (s)",
|
|
668
668
|
"figure.label.clip-time": "Tiempo del clip",
|
|
669
669
|
"figure.label.clip-loop": "Después del final",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "Con este valor, el tamaño de una pieza queda en 0 o menos. Si alguna pieza sigue este valor, revise su proporción o diferencia.",
|
|
1382
1382
|
"figure.button.pipe-end-same": "un solo grosor",
|
|
1383
1383
|
"figure.text.catalog-conflict": "Otro usuario cambió estos campos primero ({changes}). Sus valores no se guardaron. Guarde de nuevo para reemplazarlos por los suyos.",
|
|
1384
|
-
"figure.text.catalog-value-empty": "vacío"
|
|
1384
|
+
"figure.text.catalog-value-empty": "vacío",
|
|
1385
|
+
"figure.button.remember-pose": "Registrar la postura actual en {seconds} s",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "No hay cambios que registrar. Cambie un control y luego regístrelo.",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "Estos controles los usa otro clip y no se registrarán: {controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "El clip «{clip}» ya usa este control. «{clip}» y «{name}» se agruparán en el grupo de movimiento «{group}», para que solo se reproduzca uno a la vez.",
|
|
1389
|
+
"figure.text.track-joins-group": "El clip «{name}» se añadirá al grupo de movimiento «{group}», que usa este control. Los clips de un grupo se reproducen de uno en uno.",
|
|
1390
|
+
"figure.button.add-track-range": "Añadir ida y vuelta por el rango",
|
|
1391
|
+
"figure.text.add-track-range-explained": "Añade una pista que va del mínimo al máximo del control y vuelve. «Añadir» añade una pista que mantiene la postura actual.",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "La posición de la articulación {joint} solo se puede mover con «Editar posición de la articulación» activado. No se ha cambiado nada.",
|
|
1393
|
+
"figure.button.joint-place-on": "Editar posición de la articulación",
|
|
1394
|
+
"figure.text.joint-placing": "Editando la posición de la articulación. Use el asa de mover para mover la articulación {joint}.",
|
|
1395
|
+
"figure.button.joint-place-off": "Terminar",
|
|
1396
|
+
"figure.button.select-leader": "Seleccionar la articulación {leader}",
|
|
1397
|
+
"figure.text.joint-handle-turn": "Articulación {joint}: cambie su ángulo con el asa de rotar (E).",
|
|
1398
|
+
"figure.text.joint-handle-slide": "Articulación {joint}: cambie su posición a lo largo del eje con el asa de mover (W).",
|
|
1399
|
+
"figure.label.clip-interpolation": "Entre claves",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "Velocidad constante",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "Arranque y parada suaves",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "Salto",
|
|
1403
|
+
"figure.text.copy-track-off-key": "Una pista suave solo puede desplazarse para que empiece en una de sus claves. Valores posibles: {seconds} s",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "El movimiento de {clips} también se copia en la copia."
|
|
1385
1405
|
}
|
package/translations/ja.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "置き場所はまだありません。部品を選んでここで追加します。",
|
|
664
664
|
"figure.text.opening-model": "モデルを開いています。",
|
|
665
665
|
"figure.text.saved": "保存済み",
|
|
666
|
-
"figure.text.clips-explained": "
|
|
666
|
+
"figure.text.clips-explained": "操作値を時間に沿って変更します。タイムバーを動かすと、その時点の姿勢が表示されます。操作値スライダーまたはハンドルで姿勢を変更し「現在の姿勢を記録」を押すと、変更された操作値がすべてその時点のキーとして記録されます。",
|
|
667
667
|
"figure.label.clip-duration": "長さ (秒)",
|
|
668
668
|
"figure.label.clip-time": "クリップの時刻",
|
|
669
669
|
"figure.label.clip-loop": "終了後",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "この値では部品のサイズが 0 以下になります。この値に従う部品があれば、その比率・差を確認してください。",
|
|
1382
1382
|
"figure.button.pipe-end-same": "太さを一定に",
|
|
1383
1383
|
"figure.text.catalog-conflict": "他のユーザーが先に変更した項目があります({changes})。入力した値は保存されていません。もう一度保存すると入力した値に変更されます。",
|
|
1384
|
-
"figure.text.catalog-value-empty": "空"
|
|
1384
|
+
"figure.text.catalog-value-empty": "空",
|
|
1385
|
+
"figure.button.remember-pose": "{seconds}秒に現在の姿勢を記録",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "記録する変更はありません。操作値を変更してから記録してください。",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "次の操作値は別のクリップで使用中のため記録されません: {controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "クリップ「{clip}」がすでにこの操作値を使用しています。「{clip}」と「{name}」を動作グループ「{group}」にまとめ、一度に一つだけ再生されるようにします。",
|
|
1389
|
+
"figure.text.track-joins-group": "クリップ「{name}」は、この操作値を使用する動作グループ「{group}」に追加されます。同じグループのクリップは一度に一つだけ再生されます。",
|
|
1390
|
+
"figure.button.add-track-range": "範囲往復で追加",
|
|
1391
|
+
"figure.text.add-track-range-explained": "操作値の最小値から最大値まで行って戻るトラックを追加します。「追加」は現在の姿勢を保つトラックを追加します。",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "関節 {joint} の位置は「関節位置の編集」がオンのときだけ移動できます。変更はありません。",
|
|
1393
|
+
"figure.button.joint-place-on": "関節位置の編集",
|
|
1394
|
+
"figure.text.joint-placing": "関節位置を編集中です。移動ハンドルで関節 {joint} の位置を移動できます。",
|
|
1395
|
+
"figure.button.joint-place-off": "編集終了",
|
|
1396
|
+
"figure.button.select-leader": "関節 {leader} を選択",
|
|
1397
|
+
"figure.text.joint-handle-turn": "関節 {joint}: 回転ハンドル(E)で角度を変更します。",
|
|
1398
|
+
"figure.text.joint-handle-slide": "関節 {joint}: 移動ハンドル(W)で軸方向の位置を変更します。",
|
|
1399
|
+
"figure.label.clip-interpolation": "キー間の動き",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "一定の速度",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "滑らかに発進・停止",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "即時切り替え",
|
|
1403
|
+
"figure.text.copy-track-off-key": "滑らかに動くトラックは、キーのある時点に合わせてのみ時間をずらせます。可能な値: {seconds}秒",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "「{clips}」クリップの動きもコピーに一緒に複製されます。"
|
|
1385
1405
|
}
|
package/translations/ko.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "자리가 없습니다. 부품을 선택한 후 이 탭에서 추가해 주세요.",
|
|
664
664
|
"figure.text.opening-model": "피규어를 여는 중입니다.",
|
|
665
665
|
"figure.text.saved": "저장됨",
|
|
666
|
-
"figure.text.clips-explained": "조작값을 시간에 따라
|
|
666
|
+
"figure.text.clips-explained": "조작값을 시간에 따라 변경합니다. 시간 막대를 옮기면 그 시점의 자세가 표시됩니다. 조작값 슬라이더나 손잡이로 자세를 변경한 뒤 「현재 자세 기록」을 누르면, 변경된 조작값이 모두 그 시점의 키로 기록됩니다.",
|
|
667
667
|
"figure.label.clip-duration": "길이 (초)",
|
|
668
668
|
"figure.label.clip-time": "클립 시각",
|
|
669
669
|
"figure.label.clip-loop": "끝난 뒤",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "이 값이면 부품의 크기가 0 이하가 됩니다. 이 값을 따르는 부품이 있다면 그 비율·차이를 확인해 주세요.",
|
|
1382
1382
|
"figure.button.pipe-end-same": "한 굵기로",
|
|
1383
1383
|
"figure.text.catalog-conflict": "다른 사용자가 먼저 변경한 항목이 있습니다({changes}). 입력한 값은 저장되지 않았습니다. 다시 저장하면 입력한 값으로 변경됩니다.",
|
|
1384
|
-
"figure.text.catalog-value-empty": "비어 있음"
|
|
1384
|
+
"figure.text.catalog-value-empty": "비어 있음",
|
|
1385
|
+
"figure.button.remember-pose": "{seconds}초에 현재 자세 기록",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "기록할 변경 사항이 없습니다. 조작값을 변경한 뒤 기록하세요.",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "다음 조작값은 다른 클립에서 사용 중이므로 기록되지 않습니다: {controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "「{clip}」 클립이 이미 이 조작값을 사용합니다. 「{clip}」 클립과 「{name}」 클립을 「{group}」 동작 그룹으로 묶어 한 번에 하나만 재생되도록 합니다.",
|
|
1389
|
+
"figure.text.track-joins-group": "「{name}」 클립은 이 조작값을 사용하는 「{group}」 동작 그룹에 추가됩니다. 같은 그룹의 클립은 한 번에 하나만 재생됩니다.",
|
|
1390
|
+
"figure.button.add-track-range": "범위 왕복으로 추가",
|
|
1391
|
+
"figure.text.add-track-range-explained": "조작값의 최소값에서 최대값까지 갔다가 돌아오는 트랙을 추가합니다. 「재생 추가」는 현재 자세를 유지하는 트랙을 추가합니다.",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "{joint} 관절의 위치는 「관절 위치 편집」을 켠 상태에서만 옮길 수 있습니다. 변경된 내용은 없습니다.",
|
|
1393
|
+
"figure.button.joint-place-on": "관절 위치 편집",
|
|
1394
|
+
"figure.text.joint-placing": "관절 위치 편집 중입니다. 이동 손잡이로 {joint} 관절의 위치를 옮길 수 있습니다.",
|
|
1395
|
+
"figure.button.joint-place-off": "편집 종료",
|
|
1396
|
+
"figure.button.select-leader": "{leader} 관절 선택",
|
|
1397
|
+
"figure.text.joint-handle-turn": "{joint} 관절: 회전 손잡이(E)로 각도를 변경합니다.",
|
|
1398
|
+
"figure.text.joint-handle-slide": "{joint} 관절: 이동 손잡이(W)로 축 방향 위치를 변경합니다.",
|
|
1399
|
+
"figure.label.clip-interpolation": "키 사이 움직임",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "일정한 속도",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "부드럽게 출발·정지",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "즉시 전환",
|
|
1403
|
+
"figure.text.copy-track-off-key": "부드럽게 움직이는 트랙은 키가 있는 시점에 맞춰서만 시간을 밀 수 있습니다. 가능한 값: {seconds}초",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "「{clips}」 클립의 동작도 복사본에 함께 복사됩니다."
|
|
1385
1405
|
}
|
package/translations/ms.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "Belum ada tempat. Pilih bahagian dan tambah di sini.",
|
|
664
664
|
"figure.text.opening-model": "Membuka model.",
|
|
665
665
|
"figure.text.saved": "Disimpan",
|
|
666
|
-
"figure.text.clips-explained": "
|
|
666
|
+
"figure.text.clips-explained": "Mengubah kawalan mengikut masa. Gerakkan bar masa untuk melihat postur pada saat itu. Ubah postur dengan peluncur kawalan atau pemegang, kemudian tekan “Rekod postur semasa”: setiap kawalan yang berubah direkodkan sebagai kunci pada saat itu.",
|
|
667
667
|
"figure.label.clip-duration": "Panjang (s)",
|
|
668
668
|
"figure.label.clip-time": "Masa klip",
|
|
669
669
|
"figure.label.clip-loop": "Selepas tamat",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "Dengan nilai ini saiz sesuatu bahagian menjadi 0 atau kurang. Jika ada bahagian mengikut nilai ini, semak nisbah atau bezanya.",
|
|
1382
1382
|
"figure.button.pipe-end-same": "satu saiz sepanjangnya",
|
|
1383
1383
|
"figure.text.catalog-conflict": "Pengguna lain telah mengubah item ini terlebih dahulu ({changes}). Nilai anda belum disimpan. Simpan sekali lagi untuk menggantikannya dengan nilai anda.",
|
|
1384
|
-
"figure.text.catalog-value-empty": "kosong"
|
|
1384
|
+
"figure.text.catalog-value-empty": "kosong",
|
|
1385
|
+
"figure.button.remember-pose": "Rekod postur semasa pada {seconds} s",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "Tiada perubahan untuk direkodkan. Ubah kawalan, kemudian rekod.",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "Kawalan ini digunakan oleh klip lain dan tidak akan direkodkan: {controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "Klip “{clip}” sudah menggunakan kawalan ini. “{clip}” dan “{name}” akan dimasukkan ke dalam kumpulan gerakan “{group}”, supaya hanya satu dimainkan pada satu masa.",
|
|
1389
|
+
"figure.text.track-joins-group": "Klip “{name}” akan ditambah ke kumpulan gerakan “{group}” yang menggunakan kawalan ini. Klip dalam satu kumpulan dimainkan satu demi satu.",
|
|
1390
|
+
"figure.button.add-track-range": "Tambah pergi-balik julat",
|
|
1391
|
+
"figure.text.add-track-range-explained": "Menambah trek yang bergerak dari nilai minimum kawalan ke maksimum dan kembali. “Tambah” menambah trek yang mengekalkan postur semasa.",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "Kedudukan sendi {joint} hanya boleh dialihkan semasa “Edit kedudukan sendi” dihidupkan. Tiada apa-apa diubah.",
|
|
1393
|
+
"figure.button.joint-place-on": "Edit kedudukan sendi",
|
|
1394
|
+
"figure.text.joint-placing": "Sedang mengedit kedudukan sendi. Gunakan pemegang alih untuk mengalihkan sendi {joint}.",
|
|
1395
|
+
"figure.button.joint-place-off": "Selesai",
|
|
1396
|
+
"figure.button.select-leader": "Pilih sendi {leader}",
|
|
1397
|
+
"figure.text.joint-handle-turn": "Sendi {joint}: ubah sudutnya dengan pemegang putar (E).",
|
|
1398
|
+
"figure.text.joint-handle-slide": "Sendi {joint}: ubah kedudukannya di sepanjang paksi dengan pemegang alih (W).",
|
|
1399
|
+
"figure.label.clip-interpolation": "Antara kunci",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "Kelajuan tetap",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "Mula dan henti lancar",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "Lompat",
|
|
1403
|
+
"figure.text.copy-track-off-key": "Trek lancar hanya boleh dianjak supaya bermula pada salah satu kuncinya. Nilai yang boleh: {seconds} s",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "Gerakan dalam {clips} turut disalin ke salinan."
|
|
1385
1405
|
}
|
package/translations/zh.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"figure.text.no-places": "还没有位置。选择一个部件后在此添加。",
|
|
664
664
|
"figure.text.opening-model": "正在打开模型。",
|
|
665
665
|
"figure.text.saved": "已保存",
|
|
666
|
-
"figure.text.clips-explained": "
|
|
666
|
+
"figure.text.clips-explained": "随时间改变控制值。移动时间条即可查看该时刻的姿态。用控制值滑块或手柄改变姿态后,按“记录当前姿态”,所有变更的控制值都会记录为该时刻的关键帧。",
|
|
667
667
|
"figure.label.clip-duration": "时长 (秒)",
|
|
668
668
|
"figure.label.clip-time": "片段时刻",
|
|
669
669
|
"figure.label.clip-loop": "结束后",
|
|
@@ -1381,5 +1381,25 @@
|
|
|
1381
1381
|
"figure.text.section-size-not-positive": "使用此值时,部件的尺寸会小于或等于 0。如有部件跟随此值,请检查其比例或差值。",
|
|
1382
1382
|
"figure.button.pipe-end-same": "整段同一粗细",
|
|
1383
1383
|
"figure.text.catalog-conflict": "有项目已被其他用户先行修改({changes})。您输入的值尚未保存。再次保存将改为您输入的值。",
|
|
1384
|
-
"figure.text.catalog-value-empty": "空"
|
|
1384
|
+
"figure.text.catalog-value-empty": "空",
|
|
1385
|
+
"figure.button.remember-pose": "在 {seconds} 秒记录当前姿态",
|
|
1386
|
+
"figure.text.remember-pose-nothing": "没有可记录的变更。请先改变控制值再记录。",
|
|
1387
|
+
"figure.text.remember-pose-skipped": "以下控制值正被其他剪辑使用,不会被记录:{controls}",
|
|
1388
|
+
"figure.text.track-makes-group": "剪辑“{clip}”已使用此控制值。“{clip}”与“{name}”将归入动作组“{group}”,一次只播放一个。",
|
|
1389
|
+
"figure.text.track-joins-group": "剪辑“{name}”将加入使用此控制值的动作组“{group}”。同一组的剪辑一次只播放一个。",
|
|
1390
|
+
"figure.button.add-track-range": "按范围往返添加",
|
|
1391
|
+
"figure.text.add-track-range-explained": "添加从控制值最小值到最大值再返回的轨道。“添加”会添加保持当前姿态的轨道。",
|
|
1392
|
+
"figure.text.gizmo-joint-place-off": "只有开启“编辑关节位置”时才能移动关节 {joint} 的位置。未做任何更改。",
|
|
1393
|
+
"figure.button.joint-place-on": "编辑关节位置",
|
|
1394
|
+
"figure.text.joint-placing": "正在编辑关节位置。可用移动手柄移动关节 {joint} 的位置。",
|
|
1395
|
+
"figure.button.joint-place-off": "结束编辑",
|
|
1396
|
+
"figure.button.select-leader": "选择关节 {leader}",
|
|
1397
|
+
"figure.text.joint-handle-turn": "关节 {joint}:用旋转手柄(E)改变角度。",
|
|
1398
|
+
"figure.text.joint-handle-slide": "关节 {joint}:用移动手柄(W)改变沿轴的位置。",
|
|
1399
|
+
"figure.label.clip-interpolation": "关键帧之间",
|
|
1400
|
+
"figure.label.clip-interpolation-linear": "匀速",
|
|
1401
|
+
"figure.label.clip-interpolation-smooth": "平滑启停",
|
|
1402
|
+
"figure.label.clip-interpolation-step": "立即切换",
|
|
1403
|
+
"figure.text.copy-track-off-key": "平滑运动的轨道只能平移到其关键帧所在的时刻。可用值:{seconds} 秒",
|
|
1404
|
+
"figure.text.mirror-copies-clips": "剪辑“{clips}”中的动作也会一并复制到副本。"
|
|
1385
1405
|
}
|