@operato/scene-manufacturing 10.0.0-beta.30 → 10.0.0-beta.32
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/dist/robot-arm-3d.d.ts +15 -2
- package/dist/robot-arm-3d.js +58 -21
- package/dist/robot-arm-3d.js.map +1 -1
- package/dist/robot-arm.d.ts +101 -6
- package/dist/robot-arm.js +178 -30
- package/dist/robot-arm.js.map +1 -1
- package/dist/tact-timer.d.ts +30 -4
- package/dist/tact-timer.js +3 -0
- package/dist/tact-timer.js.map +1 -1
- package/package.json +9 -4
package/dist/robot-arm.d.ts
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
3
|
-
import type { SlotDef } from '@hatiolab/things-scene';
|
|
3
|
+
import type { SlotDef, State, Material3D } from '@hatiolab/things-scene';
|
|
4
4
|
import { type Alignment, type Heights, type LegendBinding, type MoveOptions, type PlacementArchetype } from '@operato/scene-base';
|
|
5
|
+
import { RobotArm3D } from './robot-arm-3d.js';
|
|
6
|
+
import { type ChainPresetName } from './robot-arm-styles.js';
|
|
7
|
+
import type { ChainElement } from './robot-arm-ik.js';
|
|
8
|
+
import type { GripperConfig, LinkShape, JointHousing, StyleName } from './robot-arm-styles.js';
|
|
9
|
+
declare module '@hatiolab/things-scene' {
|
|
10
|
+
interface ComponentEventMap {
|
|
11
|
+
/** TCP 가 목표 도달 시 발생 (pickAndPlace 시퀀싱용). */
|
|
12
|
+
'tcp-reached': [info: {
|
|
13
|
+
component: Component;
|
|
14
|
+
}];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
export type RobotArmStatus = 'idle' | 'moving' | 'gripping' | 'warn' | 'error';
|
|
18
|
+
/** RobotArm 컴포넌트 state */
|
|
19
|
+
export interface RobotArmState extends State {
|
|
20
|
+
style?: StyleName;
|
|
21
|
+
chainPreset?: ChainPresetName;
|
|
22
|
+
chain?: ChainElement[];
|
|
23
|
+
joints?: number[] | Record<string, number>;
|
|
24
|
+
linkShape?: LinkShape;
|
|
25
|
+
jointHousing?: JointHousing;
|
|
26
|
+
linkRadius?: number;
|
|
27
|
+
color?: string | number;
|
|
28
|
+
accentColor?: string | number;
|
|
29
|
+
gripper?: GripperConfig;
|
|
30
|
+
gripperType?: GripperConfig['type'];
|
|
31
|
+
target?: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
z: number;
|
|
35
|
+
yaw?: number;
|
|
36
|
+
};
|
|
37
|
+
showReachSphere?: boolean;
|
|
38
|
+
jointSpeed?: number;
|
|
39
|
+
status?: RobotArmStatus;
|
|
40
|
+
material3d?: Material3D;
|
|
41
|
+
}
|
|
6
42
|
declare const Base: typeof Component & {
|
|
7
43
|
new (...args: any[]): Component & {
|
|
8
44
|
isMover: boolean;
|
|
@@ -12,6 +48,17 @@ declare const Base: typeof Component & {
|
|
|
12
48
|
};
|
|
13
49
|
};
|
|
14
50
|
export default class RobotArm extends Base {
|
|
51
|
+
get state(): RobotArmState;
|
|
52
|
+
_realObject: RobotArm3D | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Phase H — pickup contract 호환성 검사용. 보편적 'gripper' type — vacuum,
|
|
55
|
+
* 2-finger, 3-finger 등 다양한 gripper 모두 이 type 으로 매칭. carrier 가
|
|
56
|
+
* 더 세분화된 호환성을 원하면 `pickupFramesFor` override 로 size 등 추가 검증.
|
|
57
|
+
*
|
|
58
|
+
* (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
|
|
59
|
+
* TS4113 회피.)
|
|
60
|
+
*/
|
|
61
|
+
get toolType(): string;
|
|
15
62
|
static legends: Record<string, LegendBinding>;
|
|
16
63
|
/**
|
|
17
64
|
* Robot arms sit on the floor (or mount to a ceiling/wall — that's a
|
|
@@ -132,13 +179,20 @@ export default class RobotArm extends Base {
|
|
|
132
179
|
*
|
|
133
180
|
* 1. TCP → APPROACH above place (carrier's xy, +clearance)
|
|
134
181
|
* 2. TCP → place (descend straight down to drop point)
|
|
135
|
-
* 3. open gripper +
|
|
182
|
+
* 3. open gripper + dispatch (carrier → holder)
|
|
136
183
|
*
|
|
137
184
|
* Pose resolution: TCP lands at where the carrier's top WILL BE after
|
|
138
185
|
* the drop, so the carrier lands with its bottom on the holder's attach
|
|
139
186
|
* surface. Computed as `holder.attachFrame.world.y + carrier.depth` (so
|
|
140
187
|
* yOffset = carrierDepth, not halfDepth like pick).
|
|
141
188
|
*
|
|
189
|
+
* Handoff: if this arm has `dispatch` (ContainerCapacity) and the holder
|
|
190
|
+
* exposes `receive`, route via dispatch — slot tracking, `transfer-received`
|
|
191
|
+
* trigger (which downstream conveyance components like InductStation listen
|
|
192
|
+
* to for belt-animation kickoff), and `canReceive` pre-flight all flow
|
|
193
|
+
* through the standard contract. Falls back to raw `holder.reparent()` for
|
|
194
|
+
* holders without ContainerCapacity (plain Spot, etc.). Mirrors Mover.place.
|
|
195
|
+
*
|
|
142
196
|
* @see pick — same option semantics.
|
|
143
197
|
*/
|
|
144
198
|
place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>;
|
|
@@ -158,13 +212,42 @@ export default class RobotArm extends Base {
|
|
|
158
212
|
*/
|
|
159
213
|
/** Short alias for `pickAndPlace`. Same arguments, same behavior. */
|
|
160
214
|
pnp(carrier: Component, placeHolder: Component, options?: MoveOptions): Promise<void>;
|
|
215
|
+
/**
|
|
216
|
+
* Return all joints to the configured home pose (state.joints / state.jointN).
|
|
217
|
+
*
|
|
218
|
+
* IK target 을 클리어하고 forward joint 제어로 부드럽게 휴식 포즈로 복귀.
|
|
219
|
+
* `pickAndPlace` 의 마지막 단계로 자동 호출되며, 사용자가 명시적으로 호출도 가능.
|
|
220
|
+
*
|
|
221
|
+
* **인터럽트 가능**: 집에 도달하기 전에 새 `pick`/`place` 가 호출되면 (`set('target', ...)`)
|
|
222
|
+
* `_retargetIk()` 가 발동되어 smoothing 이 home 으로 가던 걸 멈추고 새 IK target 쪽으로
|
|
223
|
+
* redirect. mid-flight joint 값을 새 IK 의 시작점으로 사용해 끊김 없이 전환. tcp-reached
|
|
224
|
+
* 가 redirect 후의 새 target 에서 발생하면 goHome 의 await 도 함께 resolve (정상).
|
|
225
|
+
* 즉 "집으로 가다가 다음 pick 으로 자연스럽게 이어진다".
|
|
226
|
+
*
|
|
227
|
+
* @param options.timeoutMs joint smoothing 의 'tcp-reached' 대기 timeout (default 15000ms).
|
|
228
|
+
*/
|
|
229
|
+
goHome(options?: MoveOptions): Promise<void>;
|
|
230
|
+
/**
|
|
231
|
+
* pick → place → goHome 시퀀스. 마지막에 home pose 복귀가 robot-arm 의
|
|
232
|
+
* pickAndPlace 표준 동작. (Mover mixin 의 default 는 pick + place 만)
|
|
233
|
+
*/
|
|
234
|
+
pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>;
|
|
161
235
|
/**
|
|
162
236
|
* Resolve a TCP position in this arm's chain-local frame.
|
|
163
237
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
238
|
+
* Resolution priority:
|
|
239
|
+
* 1. `attachPointFor` (CarrierHolder) — canonical TCP frame for arms /
|
|
240
|
+
* Spot pads / gripper-on-gripper.
|
|
241
|
+
* 2. `deliveryWorldPosition` (Phase G9 — Holder contract). Holder 가
|
|
242
|
+
* ContainerCapacity 의 default 또는 자체 override 로 정확한 도달
|
|
243
|
+
* world XYZ 를 직접 제공. 이전 SCAFFOLDING 의 cz 공식 역연산이
|
|
244
|
+
* 여기로 이전됨.
|
|
245
|
+
* 3. Component's own `_realObject.object3d` world center.
|
|
246
|
+
* 4. 2D bounds center (only before 3D builds).
|
|
247
|
+
*
|
|
248
|
+
* `carrierForDelivery` 가 주어지면 (place 케이스) `component` 를 holder 로
|
|
249
|
+
* 간주하고 `component.deliveryWorldPosition(carrierForDelivery)` 를 query.
|
|
250
|
+
* 미지정 (pick 케이스 — `component` 자체가 carrier) 시 #3 으로.
|
|
168
251
|
*
|
|
169
252
|
* `worldYOffset` is added to the resolved world Y before projecting
|
|
170
253
|
* back to chain-local — used by pickAndPlace to lift the TCP above
|
|
@@ -180,5 +263,17 @@ export default class RobotArm extends Base {
|
|
|
180
263
|
* moment of reparent.
|
|
181
264
|
*/
|
|
182
265
|
private _yawOf;
|
|
266
|
+
/**
|
|
267
|
+
* Phase G5.2 — Delivery yaw 결정.
|
|
268
|
+
*
|
|
269
|
+
* holder 가 ContainerCapacity contract 의 deliveryWorldRotation 을 노출하면
|
|
270
|
+
* 그 world quaternion 에서 yaw 를 추출 (carrier 가 acceptance pose 와 일치
|
|
271
|
+
* 한 채 release 되도록). InductStation 같은 holder 가 sorter forward 방향을
|
|
272
|
+
* 자기 acceptanceRotation 으로 선언하면 자동으로 그 방향이 TCP yaw 가 됨.
|
|
273
|
+
*
|
|
274
|
+
* holder 가 deliveryWorldRotation 을 안 노출하거나 _realObject 미빌드 시
|
|
275
|
+
* 기존 동작 (holder 자체의 world yaw) 으로 fallback.
|
|
276
|
+
*/
|
|
277
|
+
private _deliveryYawOf;
|
|
183
278
|
}
|
|
184
279
|
export {};
|
package/dist/robot-arm.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { __decorate } from "tslib";
|
|
35
35
|
import * as THREE from 'three';
|
|
36
|
-
import { ContainerAbstract, ContainerCapacity, sceneComponent } from '@hatiolab/things-scene';
|
|
36
|
+
import { ContainerAbstract, ContainerCapacity, frameClock, sceneComponent } from '@hatiolab/things-scene';
|
|
37
37
|
import { CarrierHolder, Legendable, Mover, Placeable } from '@operato/scene-base';
|
|
38
38
|
import { RobotArm3D } from './robot-arm-3d.js';
|
|
39
39
|
import { CHAIN_PRESETS } from './robot-arm-styles.js';
|
|
@@ -145,6 +145,23 @@ const STATIC_PROPERTIES = [
|
|
|
145
145
|
// CarrierHolder.attachPointFor() (overridden below) provides 3D TCP positioning.
|
|
146
146
|
const Base = Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))));
|
|
147
147
|
let RobotArm = class RobotArm extends Base {
|
|
148
|
+
// `Base` is cast through `typeof Component` so TS sees `state` as an
|
|
149
|
+
// accessor; `declare state: …` would conflict with TS2610. Override the
|
|
150
|
+
// getter instead — runtime behavior is identical (just delegates to super).
|
|
151
|
+
get state() {
|
|
152
|
+
return super.state;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Phase H — pickup contract 호환성 검사용. 보편적 'gripper' type — vacuum,
|
|
156
|
+
* 2-finger, 3-finger 등 다양한 gripper 모두 이 type 으로 매칭. carrier 가
|
|
157
|
+
* 더 세분화된 호환성을 원하면 `pickupFramesFor` override 로 size 등 추가 검증.
|
|
158
|
+
*
|
|
159
|
+
* (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아
|
|
160
|
+
* TS4113 회피.)
|
|
161
|
+
*/
|
|
162
|
+
get toolType() {
|
|
163
|
+
return 'gripper';
|
|
164
|
+
}
|
|
148
165
|
static legends = {
|
|
149
166
|
bodyColor: { from: 'status', legend: BODY_LEGEND },
|
|
150
167
|
borderColor: { from: 'status', legend: BORDER_LEGEND },
|
|
@@ -287,8 +304,7 @@ let RobotArm = class RobotArm extends Base {
|
|
|
287
304
|
'status' in after ||
|
|
288
305
|
'bodyColor' in after ||
|
|
289
306
|
'borderColor' in after) {
|
|
290
|
-
;
|
|
291
|
-
this.clearCache?.('fillStyle');
|
|
307
|
+
this.clearCache('fillStyle');
|
|
292
308
|
this.invalidate();
|
|
293
309
|
}
|
|
294
310
|
super.onchange(after, before);
|
|
@@ -396,13 +412,20 @@ let RobotArm = class RobotArm extends Base {
|
|
|
396
412
|
*
|
|
397
413
|
* 1. TCP → APPROACH above place (carrier's xy, +clearance)
|
|
398
414
|
* 2. TCP → place (descend straight down to drop point)
|
|
399
|
-
* 3. open gripper +
|
|
415
|
+
* 3. open gripper + dispatch (carrier → holder)
|
|
400
416
|
*
|
|
401
417
|
* Pose resolution: TCP lands at where the carrier's top WILL BE after
|
|
402
418
|
* the drop, so the carrier lands with its bottom on the holder's attach
|
|
403
419
|
* surface. Computed as `holder.attachFrame.world.y + carrier.depth` (so
|
|
404
420
|
* yOffset = carrierDepth, not halfDepth like pick).
|
|
405
421
|
*
|
|
422
|
+
* Handoff: if this arm has `dispatch` (ContainerCapacity) and the holder
|
|
423
|
+
* exposes `receive`, route via dispatch — slot tracking, `transfer-received`
|
|
424
|
+
* trigger (which downstream conveyance components like InductStation listen
|
|
425
|
+
* to for belt-animation kickoff), and `canReceive` pre-flight all flow
|
|
426
|
+
* through the standard contract. Falls back to raw `holder.reparent()` for
|
|
427
|
+
* holders without ContainerCapacity (plain Spot, etc.). Mirrors Mover.place.
|
|
428
|
+
*
|
|
406
429
|
* @see pick — same option semantics.
|
|
407
430
|
*/
|
|
408
431
|
async place(carrier, holder, options = {}) {
|
|
@@ -410,14 +433,29 @@ let RobotArm = class RobotArm extends Base {
|
|
|
410
433
|
const reparentDuration = options.reparentDuration ?? 800;
|
|
411
434
|
const clearance = options.approachClearance ?? 10;
|
|
412
435
|
const carrierDepth = resolveDepth(carrier);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
436
|
+
// Phase G5.2 — holder 가 acceptanceRotation 을 선언하면 그 world equivalent
|
|
437
|
+
// (deliveryWorldRotation) 의 yaw 를 TCP yaw 로 사용. 그러면 carrier 가 holder
|
|
438
|
+
// 의 acceptance pose 와 정확히 일치한 채 release 되어 handoff 시 zero-delta
|
|
439
|
+
// (시각적 점프 없음). holder 가 미정의면 holder 자체의 world yaw 사용
|
|
440
|
+
// (이전 동작).
|
|
441
|
+
const yaw = this._deliveryYawOf(holder, carrier);
|
|
442
|
+
// Phase G9 — carrier 도 함께 전달해 holder 의 deliveryWorldPosition 을 정확히
|
|
443
|
+
// query. holder 가 contract 노출 안 하면 fallback (object3d 원점) 으로.
|
|
444
|
+
const target = this._targetFor(holder, carrierDepth, yaw, carrier); // top-of-carrier after drop
|
|
445
|
+
const approach = this._targetFor(holder, carrierDepth + clearance, yaw, carrier);
|
|
416
446
|
this.set('target', approach);
|
|
417
447
|
await waitForTcpReached(this, timeoutMs);
|
|
418
448
|
this.set('target', target);
|
|
419
449
|
await waitForTcpReached(this, timeoutMs);
|
|
420
|
-
|
|
450
|
+
const xferOptions = { animated: true, duration: reparentDuration };
|
|
451
|
+
const canDispatch = typeof this.dispatch === 'function' && typeof holder.receive === 'function';
|
|
452
|
+
if (canDispatch) {
|
|
453
|
+
await this.dispatch(carrier, holder, xferOptions);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
;
|
|
457
|
+
holder.reparent?.(carrier, xferOptions);
|
|
458
|
+
}
|
|
421
459
|
this.set('gripper', { ...(this.state.gripper ?? {}), state: 0 });
|
|
422
460
|
await sleep(reparentDuration);
|
|
423
461
|
}
|
|
@@ -439,20 +477,63 @@ let RobotArm = class RobotArm extends Base {
|
|
|
439
477
|
pnp(carrier, placeHolder, options) {
|
|
440
478
|
return this.pickAndPlace(carrier, placeHolder, options);
|
|
441
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Return all joints to the configured home pose (state.joints / state.jointN).
|
|
482
|
+
*
|
|
483
|
+
* IK target 을 클리어하고 forward joint 제어로 부드럽게 휴식 포즈로 복귀.
|
|
484
|
+
* `pickAndPlace` 의 마지막 단계로 자동 호출되며, 사용자가 명시적으로 호출도 가능.
|
|
485
|
+
*
|
|
486
|
+
* **인터럽트 가능**: 집에 도달하기 전에 새 `pick`/`place` 가 호출되면 (`set('target', ...)`)
|
|
487
|
+
* `_retargetIk()` 가 발동되어 smoothing 이 home 으로 가던 걸 멈추고 새 IK target 쪽으로
|
|
488
|
+
* redirect. mid-flight joint 값을 새 IK 의 시작점으로 사용해 끊김 없이 전환. tcp-reached
|
|
489
|
+
* 가 redirect 후의 새 target 에서 발생하면 goHome 의 await 도 함께 resolve (정상).
|
|
490
|
+
* 즉 "집으로 가다가 다음 pick 으로 자연스럽게 이어진다".
|
|
491
|
+
*
|
|
492
|
+
* @param options.timeoutMs joint smoothing 의 'tcp-reached' 대기 timeout (default 15000ms).
|
|
493
|
+
*/
|
|
494
|
+
async goHome(options = {}) {
|
|
495
|
+
const timeoutMs = options.timeoutMs ?? 15000;
|
|
496
|
+
const ro = this._realObject;
|
|
497
|
+
if (!ro?.moveJointsTo || !ro?.getHomeJoints)
|
|
498
|
+
return;
|
|
499
|
+
// IK target 제거 — 다음 rebuild 시 _smoothing.target 이 IK 로 다시 풀리지 않도록.
|
|
500
|
+
// 외부에서 set('target', ...) 호출하면 다시 IK 모드로 전환되며 redirect.
|
|
501
|
+
this.set('target', undefined);
|
|
502
|
+
ro.moveJointsTo(ro.getHomeJoints());
|
|
503
|
+
await waitForTcpReached(this, timeoutMs);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* pick → place → goHome 시퀀스. 마지막에 home pose 복귀가 robot-arm 의
|
|
507
|
+
* pickAndPlace 표준 동작. (Mover mixin 의 default 는 pick + place 만)
|
|
508
|
+
*/
|
|
509
|
+
async pickAndPlace(carrier, holder, options = {}) {
|
|
510
|
+
await this.pick(carrier, options);
|
|
511
|
+
await this.place(carrier, holder, options);
|
|
512
|
+
await this.goHome(options);
|
|
513
|
+
}
|
|
442
514
|
/**
|
|
443
515
|
* Resolve a TCP position in this arm's chain-local frame.
|
|
444
516
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
517
|
+
* Resolution priority:
|
|
518
|
+
* 1. `attachPointFor` (CarrierHolder) — canonical TCP frame for arms /
|
|
519
|
+
* Spot pads / gripper-on-gripper.
|
|
520
|
+
* 2. `deliveryWorldPosition` (Phase G9 — Holder contract). Holder 가
|
|
521
|
+
* ContainerCapacity 의 default 또는 자체 override 로 정확한 도달
|
|
522
|
+
* world XYZ 를 직접 제공. 이전 SCAFFOLDING 의 cz 공식 역연산이
|
|
523
|
+
* 여기로 이전됨.
|
|
524
|
+
* 3. Component's own `_realObject.object3d` world center.
|
|
525
|
+
* 4. 2D bounds center (only before 3D builds).
|
|
526
|
+
*
|
|
527
|
+
* `carrierForDelivery` 가 주어지면 (place 케이스) `component` 를 holder 로
|
|
528
|
+
* 간주하고 `component.deliveryWorldPosition(carrierForDelivery)` 를 query.
|
|
529
|
+
* 미지정 (pick 케이스 — `component` 자체가 carrier) 시 #3 으로.
|
|
449
530
|
*
|
|
450
531
|
* `worldYOffset` is added to the resolved world Y before projecting
|
|
451
532
|
* back to chain-local — used by pickAndPlace to lift the TCP above
|
|
452
533
|
* the target (approach waypoints) and to land it on the carrier's
|
|
453
534
|
* top face rather than its volumetric center.
|
|
454
535
|
*/
|
|
455
|
-
_targetFor(component, worldYOffset = 0, yaw) {
|
|
536
|
+
_targetFor(component, worldYOffset = 0, yaw, carrierForDelivery) {
|
|
456
537
|
const ro3d = this._realObject;
|
|
457
538
|
let baseWp;
|
|
458
539
|
// Prefer holder's attach frame when available.
|
|
@@ -465,10 +546,24 @@ let RobotArm = class RobotArm extends Base {
|
|
|
465
546
|
}
|
|
466
547
|
}
|
|
467
548
|
if (!baseWp) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
549
|
+
// Phase G9 — Holder.deliveryWorldPosition contract 를 우선 사용 (place
|
|
550
|
+
// 케이스: carrierForDelivery 가 주어진 경우). ContainerCapacity 가 default
|
|
551
|
+
// 로 receivingSurfaceY/geometricOffsetY 보정 + 표면 변환을 제공하므로
|
|
552
|
+
// robot-arm 이 cz 공식 역연산을 직접 알지 않아도 됨.
|
|
553
|
+
if (carrierForDelivery) {
|
|
554
|
+
const deliveryPos = component.deliveryWorldPosition?.(carrierForDelivery);
|
|
555
|
+
if (deliveryPos) {
|
|
556
|
+
baseWp = new THREE.Vector3(deliveryPos.x, deliveryPos.y, deliveryPos.z);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// Fallback (pick 케이스 또는 holder 가 contract 미노출): component 자체의
|
|
560
|
+
// obj3d world center.
|
|
561
|
+
if (!baseWp) {
|
|
562
|
+
const obj = component._realObject?.object3d;
|
|
563
|
+
if (obj) {
|
|
564
|
+
obj.updateWorldMatrix(true, false);
|
|
565
|
+
baseWp = obj.getWorldPosition(new THREE.Vector3());
|
|
566
|
+
}
|
|
472
567
|
}
|
|
473
568
|
}
|
|
474
569
|
if (!ro3d || !baseWp) {
|
|
@@ -496,34 +591,87 @@ let RobotArm = class RobotArm extends Base {
|
|
|
496
591
|
const e = new THREE.Euler().setFromQuaternion(q, 'YXZ');
|
|
497
592
|
return e.y;
|
|
498
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Phase G5.2 — Delivery yaw 결정.
|
|
596
|
+
*
|
|
597
|
+
* holder 가 ContainerCapacity contract 의 deliveryWorldRotation 을 노출하면
|
|
598
|
+
* 그 world quaternion 에서 yaw 를 추출 (carrier 가 acceptance pose 와 일치
|
|
599
|
+
* 한 채 release 되도록). InductStation 같은 holder 가 sorter forward 방향을
|
|
600
|
+
* 자기 acceptanceRotation 으로 선언하면 자동으로 그 방향이 TCP yaw 가 됨.
|
|
601
|
+
*
|
|
602
|
+
* holder 가 deliveryWorldRotation 을 안 노출하거나 _realObject 미빌드 시
|
|
603
|
+
* 기존 동작 (holder 자체의 world yaw) 으로 fallback.
|
|
604
|
+
*/
|
|
605
|
+
_deliveryYawOf(holder, carrier) {
|
|
606
|
+
const deliveryRot = holder.deliveryWorldRotation?.(carrier);
|
|
607
|
+
if (deliveryRot) {
|
|
608
|
+
const q = new THREE.Quaternion(deliveryRot.x, deliveryRot.y, deliveryRot.z, deliveryRot.w);
|
|
609
|
+
const e = new THREE.Euler().setFromQuaternion(q, 'YXZ');
|
|
610
|
+
return e.y;
|
|
611
|
+
}
|
|
612
|
+
return this._yawOf(holder);
|
|
613
|
+
}
|
|
499
614
|
};
|
|
500
615
|
RobotArm = __decorate([
|
|
501
616
|
sceneComponent('robot-arm')
|
|
502
617
|
], RobotArm);
|
|
503
618
|
export default RobotArm;
|
|
504
619
|
/**
|
|
505
|
-
* Resolve when the robot arm fires `tcp-reached`, reject on
|
|
506
|
-
*
|
|
507
|
-
*
|
|
620
|
+
* Resolve when the robot arm fires `tcp-reached`, reject on **simulation-time**
|
|
621
|
+
* timeout. 3D smoothing 은 frameClock 의 simDt 로 진행 (sim-time-driven),
|
|
622
|
+
* 따라서 timeout 도 sim-time 으로 누적해야 sim speed 변경에 일관 동작:
|
|
623
|
+
* - speed=1 → sim time = real time (기존과 동일)
|
|
624
|
+
* - speed=2 → IK 도 2× 빨라지고 timeout 도 2× 빠르게 도달 (real time 기준)
|
|
625
|
+
* - speed=0.5 → IK 가 2× 느려지지만 timeout 도 sim 기준이라 spurious
|
|
626
|
+
* reject 없음 (이전 real-time setTimeout 으로는 1 미만 speed
|
|
627
|
+
* 에서 IK 도달 전에 timeout 발화하던 버그 수정)
|
|
628
|
+
* - paused → simDt=0 누적도 정지 → 일시정지 동안 무한 대기 (의도)
|
|
508
629
|
*/
|
|
509
630
|
function waitForTcpReached(component, timeoutMs) {
|
|
510
631
|
return new Promise((resolve, reject) => {
|
|
511
|
-
let
|
|
632
|
+
let elapsedSim = 0;
|
|
633
|
+
let unsub = null;
|
|
634
|
+
const cleanup = () => {
|
|
635
|
+
component.off('tcp-reached', onReached);
|
|
636
|
+
if (unsub) {
|
|
637
|
+
unsub();
|
|
638
|
+
unsub = null;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
512
641
|
const onReached = () => {
|
|
513
|
-
|
|
514
|
-
component.off?.('tcp-reached', onReached);
|
|
642
|
+
cleanup();
|
|
515
643
|
resolve();
|
|
516
644
|
};
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
645
|
+
component.on('tcp-reached', onReached);
|
|
646
|
+
unsub = frameClock.subscribe(simDt => {
|
|
647
|
+
elapsedSim += simDt;
|
|
648
|
+
if (elapsedSim >= timeoutMs) {
|
|
649
|
+
cleanup();
|
|
650
|
+
reject(new Error('TCP reach timeout'));
|
|
651
|
+
}
|
|
652
|
+
}, { description: 'robot-arm tcp-reached timeout', component });
|
|
523
653
|
});
|
|
524
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* sim-time aware sleep — frameClock 의 simDt 를 누적해 ms 도달 시 resolve.
|
|
657
|
+
* speed=0.5 이면 real time 으로 2배 걸리지만 sim time 으로는 동일 — animation
|
|
658
|
+
* 시간과 일관. paused 시 무한 대기 (의도). waitForTcpReached 와 같은 시간축.
|
|
659
|
+
*/
|
|
525
660
|
function sleep(ms) {
|
|
526
|
-
return new Promise(resolve =>
|
|
661
|
+
return new Promise(resolve => {
|
|
662
|
+
let elapsedSim = 0;
|
|
663
|
+
let unsub = null;
|
|
664
|
+
unsub = frameClock.subscribe(simDt => {
|
|
665
|
+
elapsedSim += simDt;
|
|
666
|
+
if (elapsedSim >= ms) {
|
|
667
|
+
if (unsub) {
|
|
668
|
+
unsub();
|
|
669
|
+
unsub = null;
|
|
670
|
+
}
|
|
671
|
+
resolve();
|
|
672
|
+
}
|
|
673
|
+
}, { description: 'robot-arm sim-sleep' });
|
|
674
|
+
});
|
|
527
675
|
}
|
|
528
676
|
function numOr(v, dflt) {
|
|
529
677
|
return typeof v === 'number' && Number.isFinite(v) ? v : dflt;
|