@wandelbots/wandelbots-js-react-components 2.27.1 → 2.28.0
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/README.md +1 -1
- package/dist/Setup.d.ts +1 -1
- package/dist/Setup.d.ts.map +1 -1
- package/dist/components/3d-viewport/SafetyZonesRenderer.d.ts +2 -2
- package/dist/components/3d-viewport/SafetyZonesRenderer.d.ts.map +1 -1
- package/dist/components/3d-viewport/TrajectoryRenderer.d.ts +1 -1
- package/dist/components/3d-viewport/TrajectoryRenderer.d.ts.map +1 -1
- package/dist/components/robots/DHRobot.d.ts.map +1 -1
- package/dist/components/robots/GenericRobot.d.ts +2 -2
- package/dist/components/robots/GenericRobot.d.ts.map +1 -1
- package/dist/components/robots/Robot.d.ts +2 -2
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/components/robots/RobotAnimator.d.ts.map +1 -1
- package/dist/components/robots/RobotAnimator.test.d.ts +2 -0
- package/dist/components/robots/RobotAnimator.test.d.ts.map +1 -0
- package/dist/components/robots/SupportedRobot.d.ts +3 -3
- package/dist/components/robots/SupportedRobot.d.ts.map +1 -1
- package/dist/components/utils/interpolation.d.ts +159 -0
- package/dist/components/utils/interpolation.d.ts.map +1 -0
- package/dist/components/utils/interpolation.test.d.ts +2 -0
- package/dist/components/utils/interpolation.test.d.ts.map +1 -0
- package/dist/externalizeComponent.d.ts +1 -1
- package/dist/externalizeComponent.d.ts.map +1 -1
- package/dist/index.cjs +39 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8251 -9820
- package/dist/index.js.map +1 -1
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +33 -32
- package/src/Setup.tsx +1 -1
- package/src/components/3d-viewport/SafetyZonesRenderer.tsx +2 -2
- package/src/components/3d-viewport/TrajectoryRenderer.tsx +1 -1
- package/src/components/jogging/JoggingOptions.tsx +1 -1
- package/src/components/robots/DHRobot.tsx +37 -10
- package/src/components/robots/GenericRobot.tsx +4 -5
- package/src/components/robots/Robot.tsx +2 -2
- package/src/components/robots/RobotAnimator.test.tsx +113 -0
- package/src/components/robots/RobotAnimator.tsx +38 -23
- package/src/components/robots/SupportedRobot.tsx +3 -3
- package/src/components/utils/converters.ts +1 -1
- package/src/components/utils/interpolation.test.ts +1123 -0
- package/src/components/utils/interpolation.ts +379 -0
- package/src/externalizeComponent.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/test/setup.ts +111 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/wandelbots-js-react-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"dev:wbjs": "nodemon --watch $WBJS_PATH -e tgz --exec 'npm install file:$WBJS_PATH && storybook dev -p 6006 --no-open'",
|
|
26
26
|
"tsc": "tsc --pretty --noEmit -p stories/tsconfig.json",
|
|
27
27
|
"test": "test-storybook --url http://127.0.0.1:6006 --index-json --browsers chromium",
|
|
28
|
+
"test:unit": "vitest --workspace vitest.workspace.ts --project unit",
|
|
29
|
+
"test:unit:run": "vitest run --workspace vitest.workspace.ts --project unit",
|
|
28
30
|
"ci:test": "run-s ci:test:build ci:test:built",
|
|
29
31
|
"ci:test:build": "storybook build --quiet --test",
|
|
30
32
|
"ci:test:built": "run-p --race ci:test:server ci:test:runner",
|
|
@@ -48,45 +50,44 @@
|
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@emotion/react": "^11.14.0",
|
|
50
52
|
"@emotion/styled": "^11.14.0",
|
|
51
|
-
"@mui/icons-material": "^
|
|
52
|
-
"@mui/material": "^
|
|
53
|
-
"@react-
|
|
54
|
-
"@react-three/
|
|
55
|
-
"@react-three/fiber": "^8.18.0",
|
|
53
|
+
"@mui/icons-material": "^7.1.1",
|
|
54
|
+
"@mui/material": "^7.1.1",
|
|
55
|
+
"@react-three/drei": "^10.2.0",
|
|
56
|
+
"@react-three/fiber": "^9.1.2",
|
|
56
57
|
"@rollup/plugin-commonjs": "^28.0.2",
|
|
57
58
|
"@rollup/plugin-json": "^6.1.0",
|
|
58
59
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
59
60
|
"@rollup/plugin-terser": "^0.4.4",
|
|
60
61
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
61
|
-
"@storybook/addon-docs": "^
|
|
62
|
-
"@storybook/
|
|
63
|
-
"@storybook/
|
|
64
|
-
"@storybook/blocks": "^8.6.4",
|
|
65
|
-
"@storybook/csf-tools": "^8.6.4",
|
|
66
|
-
"@storybook/react": "^8.6.4",
|
|
67
|
-
"@storybook/react-vite": "^8.6.4",
|
|
68
|
-
"@storybook/test": "^8.6.4",
|
|
69
|
-
"@storybook/test-runner": "^0.21.3",
|
|
70
|
-
"@storybook/types": "^8.6.4",
|
|
62
|
+
"@storybook/addon-docs": "^9.0.8",
|
|
63
|
+
"@storybook/react-vite": "^9.0.8",
|
|
64
|
+
"@storybook/test-runner": "^0.23.0",
|
|
71
65
|
"@svgr/rollup": "^8.1.0",
|
|
66
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
67
|
+
"@testing-library/react": "^16.3.0",
|
|
68
|
+
"@testing-library/user-event": "^14.6.1",
|
|
72
69
|
"@types/lodash-es": "^4.17.12",
|
|
73
|
-
"@types/react": "^
|
|
70
|
+
"@types/react": "^19.1.8",
|
|
74
71
|
"@types/three": "^0.174.0",
|
|
75
72
|
"@vitejs/plugin-react": "^4.3.4",
|
|
76
|
-
"@wandelbots/nova-js": "^2.0
|
|
73
|
+
"@wandelbots/nova-js": "^2.1.0",
|
|
77
74
|
"add": "^2.0.6",
|
|
75
|
+
"eslint-plugin-storybook": "^9.0.8",
|
|
78
76
|
"glob": "^11.0.1",
|
|
79
77
|
"http-server": "^14.1.1",
|
|
80
78
|
"husky": "^9.1.7",
|
|
79
|
+
"i18next": "^25.2.1",
|
|
81
80
|
"jest-simple-dot-reporter": "^1.0.5",
|
|
82
81
|
"jest-summary-reporter": "^0.0.2",
|
|
82
|
+
"jsdom": "^26.1.0",
|
|
83
|
+
"monaco-editor": "^0.52.2",
|
|
83
84
|
"nodemon": "^3.1.9",
|
|
84
85
|
"npm-run-all": "^4.1.5",
|
|
85
86
|
"postcss": "^8.5.3",
|
|
86
87
|
"prettier-eslint": "^16.3.0",
|
|
87
88
|
"prop-types": "^15.8.1",
|
|
88
|
-
"react": "^
|
|
89
|
-
"react-dom": "^
|
|
89
|
+
"react": "^19.1.0",
|
|
90
|
+
"react-dom": "^19.1.0",
|
|
90
91
|
"rimraf": "^6.0.1",
|
|
91
92
|
"rollup": "^4.34.9",
|
|
92
93
|
"rollup-plugin-dts": "^6.1.1",
|
|
@@ -94,8 +95,7 @@
|
|
|
94
95
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
95
96
|
"rollup-plugin-postcss": "^4.0.2",
|
|
96
97
|
"semantic-release": "^24.2.3",
|
|
97
|
-
"storybook": "^
|
|
98
|
-
"storybook-dark-mode": "^4.0.2",
|
|
98
|
+
"storybook": "^9.0.8",
|
|
99
99
|
"storybook-preset-inline-svg": "^1.0.1",
|
|
100
100
|
"three": "^0.174.0",
|
|
101
101
|
"three-stdlib": "^2.35.14",
|
|
@@ -110,11 +110,12 @@
|
|
|
110
110
|
"peerDependencies": {
|
|
111
111
|
"@emotion/react": "^11.11.1",
|
|
112
112
|
"@emotion/styled": "^11.11.0",
|
|
113
|
-
"@mui/icons-material": "^6",
|
|
114
|
-
"@mui/material": "^6",
|
|
115
|
-
"@react-
|
|
116
|
-
"@react-three/
|
|
117
|
-
"
|
|
113
|
+
"@mui/icons-material": "^6 || ^7",
|
|
114
|
+
"@mui/material": "^6 || ^7",
|
|
115
|
+
"@react-three/drei": "^9.122.0 || ^10",
|
|
116
|
+
"@react-three/fiber": "^8 || ^9",
|
|
117
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
118
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
118
119
|
"three": ">=0.174",
|
|
119
120
|
"three-stdlib": ">=2"
|
|
120
121
|
},
|
|
@@ -122,9 +123,6 @@
|
|
|
122
123
|
"react-dom": {
|
|
123
124
|
"optional": true
|
|
124
125
|
},
|
|
125
|
-
"@react-spring/three": {
|
|
126
|
-
"optional": true
|
|
127
|
-
},
|
|
128
126
|
"@react-three/drei": {
|
|
129
127
|
"optional": true
|
|
130
128
|
},
|
|
@@ -145,8 +143,11 @@
|
|
|
145
143
|
"lodash-es": "^4.17.21",
|
|
146
144
|
"mobx": "^6.13.6",
|
|
147
145
|
"mobx-react-lite": "^4.1.0",
|
|
148
|
-
"react-error-boundary": "^
|
|
149
|
-
"react-i18next": "^15.
|
|
146
|
+
"react-error-boundary": "^6.0.0",
|
|
147
|
+
"react-i18next": "^15.5.2",
|
|
150
148
|
"shiki": "^3.1.0"
|
|
149
|
+
},
|
|
150
|
+
"overrides": {
|
|
151
|
+
"storybook": "$storybook"
|
|
151
152
|
}
|
|
152
153
|
}
|
package/src/Setup.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OrbitControls } from "@react-three/drei"
|
|
2
|
-
import { Canvas, type
|
|
2
|
+
import { Canvas, type CanvasProps } from "@react-three/fiber"
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { Vector3 } from "three"
|
|
5
5
|
import { PresetEnvironment } from "./components/3d-viewport/PresetEnvironment"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ThreeElements } from "@react-three/fiber"
|
|
2
2
|
import type { Geometry } from "@wandelbots/nova-api/v1"
|
|
3
3
|
import type { SafetySetupSafetyZone } from "@wandelbots/nova-js/v1"
|
|
4
4
|
import * as THREE from "three"
|
|
@@ -6,7 +6,7 @@ import { ConvexGeometry } from "three-stdlib"
|
|
|
6
6
|
|
|
7
7
|
export type SafetyZonesRendererProps = {
|
|
8
8
|
safetyZones: SafetySetupSafetyZone[]
|
|
9
|
-
} &
|
|
9
|
+
} & ThreeElements["group"]
|
|
10
10
|
|
|
11
11
|
interface CoplanarityResult {
|
|
12
12
|
isCoplanar: boolean
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export const JoggingOptions = observer(({ store }: { store: JoggingStore }) => {
|
|
13
13
|
const { t } = useTranslation()
|
|
14
|
-
const joggingOptions = []
|
|
14
|
+
const joggingOptions: React.ReactElement[] = []
|
|
15
15
|
|
|
16
16
|
function translateOrientation(orientation: OrientationId): string {
|
|
17
17
|
switch (orientation) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Line } from "@react-three/drei"
|
|
2
2
|
import type { DHParameter } from "@wandelbots/nova-api/v1"
|
|
3
|
+
import React, { useRef } from "react"
|
|
3
4
|
import type * as THREE from "three"
|
|
4
5
|
import { Matrix4, Quaternion, Vector3 } from "three"
|
|
5
6
|
import type { LineGeometry } from "three/examples/jsm/lines/LineGeometry.js"
|
|
@@ -17,6 +18,16 @@ export function DHRobot({
|
|
|
17
18
|
// reused in every update
|
|
18
19
|
const accumulatedMatrix = new Matrix4()
|
|
19
20
|
|
|
21
|
+
// Store direct references to avoid searching by name
|
|
22
|
+
const lineRefs = useRef<any[]>([])
|
|
23
|
+
const meshRefs = useRef<(THREE.Mesh | null)[]>([])
|
|
24
|
+
|
|
25
|
+
// Initialize refs array when dhParameters change
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
lineRefs.current = new Array(dhParameters.length).fill(null)
|
|
28
|
+
meshRefs.current = new Array(dhParameters.length).fill(null)
|
|
29
|
+
}, [dhParameters.length])
|
|
30
|
+
|
|
20
31
|
// Updates accumulatedMatrix with every execution
|
|
21
32
|
// Reset the matrix to identity if you start a new position update
|
|
22
33
|
function getLinePoints(
|
|
@@ -49,7 +60,7 @@ export function DHRobot({
|
|
|
49
60
|
|
|
50
61
|
function setJointLineRotation(
|
|
51
62
|
jointIndex: number,
|
|
52
|
-
line:
|
|
63
|
+
line: any, // Use any for drei Line component
|
|
53
64
|
mesh: THREE.Mesh,
|
|
54
65
|
jointValue: number,
|
|
55
66
|
) {
|
|
@@ -71,14 +82,20 @@ export function DHRobot({
|
|
|
71
82
|
|
|
72
83
|
function setRotation(joints: THREE.Object3D[], jointValues: number[]) {
|
|
73
84
|
accumulatedMatrix.identity()
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
|
|
86
|
+
// Use direct refs instead of searching by name
|
|
87
|
+
for (
|
|
88
|
+
let jointIndex = 0;
|
|
89
|
+
jointIndex < Math.min(joints.length, jointValues.length);
|
|
90
|
+
jointIndex++
|
|
91
|
+
) {
|
|
92
|
+
const line = lineRefs.current[jointIndex]
|
|
93
|
+
const mesh = meshRefs.current[jointIndex]
|
|
94
|
+
|
|
95
|
+
if (line && mesh) {
|
|
96
|
+
setJointLineRotation(jointIndex, line, mesh, jointValues[jointIndex]!)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
return (
|
|
@@ -103,12 +120,22 @@ export function DHRobot({
|
|
|
103
120
|
return (
|
|
104
121
|
<group name={jointName} key={jointName}>
|
|
105
122
|
<Line
|
|
123
|
+
ref={(ref) => {
|
|
124
|
+
lineRefs.current[index] = ref
|
|
125
|
+
}}
|
|
106
126
|
name={CHILD_LINE}
|
|
107
127
|
points={[a, b]}
|
|
108
128
|
color={"white"}
|
|
109
129
|
lineWidth={5}
|
|
110
130
|
/>
|
|
111
|
-
<mesh
|
|
131
|
+
<mesh
|
|
132
|
+
ref={(ref) => {
|
|
133
|
+
meshRefs.current[index] = ref
|
|
134
|
+
}}
|
|
135
|
+
name={CHILD_MESH}
|
|
136
|
+
key={"mesh_" + index}
|
|
137
|
+
position={b}
|
|
138
|
+
>
|
|
112
139
|
<sphereGeometry args={[0.01, 32, 32]} />
|
|
113
140
|
<meshStandardMaterial color={"black"} depthTest={true} />
|
|
114
141
|
</mesh>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { animated } from "@react-spring/three"
|
|
2
1
|
import { useGLTF } from "@react-three/drei"
|
|
3
|
-
import type {
|
|
2
|
+
import type { ThreeElements } from "@react-three/fiber"
|
|
4
3
|
import React, { useCallback } from "react"
|
|
5
4
|
import type { Group, Mesh } from "three"
|
|
6
5
|
import { type Object3D } from "three"
|
|
@@ -14,7 +13,7 @@ export type RobotModelProps = {
|
|
|
14
13
|
*/
|
|
15
14
|
postModelRender?: () => void
|
|
16
15
|
flangeRef?: React.Ref<Group>
|
|
17
|
-
} &
|
|
16
|
+
} & ThreeElements["group"]
|
|
18
17
|
|
|
19
18
|
function isMesh(node: Object3D): node is Mesh {
|
|
20
19
|
return node.type === "Mesh"
|
|
@@ -54,7 +53,7 @@ export function GenericRobot({
|
|
|
54
53
|
)
|
|
55
54
|
} else {
|
|
56
55
|
return (
|
|
57
|
-
<
|
|
56
|
+
<group
|
|
58
57
|
name={node.name}
|
|
59
58
|
key={node.uuid}
|
|
60
59
|
position={node.position}
|
|
@@ -62,7 +61,7 @@ export function GenericRobot({
|
|
|
62
61
|
ref={isFlange(node) ? flangeRef : undefined}
|
|
63
62
|
>
|
|
64
63
|
{node.children.map(renderNode)}
|
|
65
|
-
</
|
|
64
|
+
</group>
|
|
66
65
|
)
|
|
67
66
|
}
|
|
68
67
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ThreeElements } from "@react-three/fiber"
|
|
2
2
|
|
|
3
3
|
import type { ConnectedMotionGroup } from "@wandelbots/nova-js/v1"
|
|
4
4
|
import type { Group } from "three"
|
|
@@ -10,7 +10,7 @@ export type RobotProps = {
|
|
|
10
10
|
getModel?: (modelFromController: string) => string
|
|
11
11
|
flangeRef?: React.Ref<Group>
|
|
12
12
|
transparentColor?: string
|
|
13
|
-
} &
|
|
13
|
+
} & ThreeElements["group"]
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* The Robot component is a wrapper around the SupportedRobot component
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DHParameter,
|
|
3
|
+
MotionGroupStateResponse,
|
|
4
|
+
} from "@wandelbots/nova-api/v1"
|
|
5
|
+
import { describe, expect, it, vi } from "vitest"
|
|
6
|
+
import RobotAnimator from "./RobotAnimator"
|
|
7
|
+
|
|
8
|
+
// Mock the dependencies
|
|
9
|
+
vi.mock("./robotModelLogic", () => ({
|
|
10
|
+
collectJoints: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock("../utils/interpolation", () => ({
|
|
14
|
+
ValueInterpolator: vi.fn().mockImplementation(() => ({
|
|
15
|
+
setTarget: vi.fn(),
|
|
16
|
+
getCurrentValues: vi.fn(() => []),
|
|
17
|
+
destroy: vi.fn(),
|
|
18
|
+
})),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
describe("RobotAnimator", () => {
|
|
22
|
+
it("should export the component correctly", () => {
|
|
23
|
+
expect(RobotAnimator).toBeDefined()
|
|
24
|
+
expect(typeof RobotAnimator).toBe("function")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("should handle props with different numbers of joints", () => {
|
|
28
|
+
// Test that the component accepts different numbers of DH parameters
|
|
29
|
+
const mockMotionState4Joints: MotionGroupStateResponse = {
|
|
30
|
+
state: {
|
|
31
|
+
joint_position: {
|
|
32
|
+
joints: [0.1, 0.2, 0.3, 0.4],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} as any
|
|
36
|
+
|
|
37
|
+
const mockDHParameters4Joints: DHParameter[] = [
|
|
38
|
+
{ theta: 0, reverse_rotation_direction: false },
|
|
39
|
+
{ theta: 0, reverse_rotation_direction: false },
|
|
40
|
+
{ theta: 0, reverse_rotation_direction: false },
|
|
41
|
+
{ theta: 0, reverse_rotation_direction: false },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const mockMotionState7Joints: MotionGroupStateResponse = {
|
|
45
|
+
state: {
|
|
46
|
+
joint_position: {
|
|
47
|
+
joints: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
} as any
|
|
51
|
+
|
|
52
|
+
const mockDHParameters7Joints: DHParameter[] = Array(7).fill({
|
|
53
|
+
theta: 0,
|
|
54
|
+
reverse_rotation_direction: false,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// These should not throw TypeScript errors
|
|
58
|
+
expect(() => {
|
|
59
|
+
const props4 = {
|
|
60
|
+
rapidlyChangingMotionState: mockMotionState4Joints,
|
|
61
|
+
dhParameters: mockDHParameters4Joints,
|
|
62
|
+
children: null,
|
|
63
|
+
}
|
|
64
|
+
// Verify props are correctly typed
|
|
65
|
+
expect(props4.dhParameters).toHaveLength(4)
|
|
66
|
+
expect(
|
|
67
|
+
props4.rapidlyChangingMotionState.state.joint_position.joints,
|
|
68
|
+
).toHaveLength(4)
|
|
69
|
+
}).not.toThrow()
|
|
70
|
+
|
|
71
|
+
expect(() => {
|
|
72
|
+
const props7 = {
|
|
73
|
+
rapidlyChangingMotionState: mockMotionState7Joints,
|
|
74
|
+
dhParameters: mockDHParameters7Joints,
|
|
75
|
+
children: null,
|
|
76
|
+
}
|
|
77
|
+
expect(props7.dhParameters).toHaveLength(7)
|
|
78
|
+
expect(
|
|
79
|
+
props7.rapidlyChangingMotionState.state.joint_position.joints,
|
|
80
|
+
).toHaveLength(7)
|
|
81
|
+
}).not.toThrow()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("should use custom interpolation for smooth value transitions", () => {
|
|
85
|
+
// Test the rotation calculation logic
|
|
86
|
+
const dhParam1 = { theta: 0.1, reverse_rotation_direction: false }
|
|
87
|
+
const dhParam2 = { theta: -0.2, reverse_rotation_direction: true }
|
|
88
|
+
const jointValue1 = 0.5
|
|
89
|
+
const jointValue2 = 1.0
|
|
90
|
+
|
|
91
|
+
// Calculate expected rotations
|
|
92
|
+
const expectedRotation1 = 1 * jointValue1 + 0.1 // 0.6
|
|
93
|
+
const expectedRotation2 = -1 * jointValue2 + -0.2 // -1.2
|
|
94
|
+
|
|
95
|
+
expect(expectedRotation1).toBe(0.6)
|
|
96
|
+
expect(expectedRotation2).toBe(-1.2)
|
|
97
|
+
|
|
98
|
+
// Test edge cases
|
|
99
|
+
const dhParamReverse = { theta: 0, reverse_rotation_direction: true }
|
|
100
|
+
const jointValueZero = 0
|
|
101
|
+
const expectedRotationReverse = -1 * jointValueZero + 0 // 0
|
|
102
|
+
|
|
103
|
+
expect(expectedRotationReverse).toBe(0)
|
|
104
|
+
|
|
105
|
+
// Test rotation direction logic
|
|
106
|
+
const testValue = 1.5
|
|
107
|
+
const normalParam = { theta: 0, reverse_rotation_direction: false }
|
|
108
|
+
const reversedParam = { theta: 0, reverse_rotation_direction: true }
|
|
109
|
+
|
|
110
|
+
expect(1 * testValue + 0).toBe(1.5) // Normal direction
|
|
111
|
+
expect(-1 * testValue + 0).toBe(-1.5) // Reversed direction
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useThree } from "@react-three/fiber"
|
|
1
|
+
import { useFrame, useThree } from "@react-three/fiber"
|
|
3
2
|
import type {
|
|
4
3
|
DHParameter,
|
|
5
4
|
MotionGroupStateResponse,
|
|
6
5
|
} from "@wandelbots/nova-api/v1"
|
|
7
|
-
import React, { useRef } from "react"
|
|
6
|
+
import React, { useEffect, useRef } from "react"
|
|
8
7
|
import type { Group, Object3D } from "three"
|
|
9
8
|
import { useAutorun } from "../utils/hooks"
|
|
9
|
+
import { ValueInterpolator } from "../utils/interpolation"
|
|
10
10
|
import { collectJoints } from "./robotModelLogic"
|
|
11
11
|
|
|
12
12
|
type RobotAnimatorProps = {
|
|
@@ -22,11 +22,42 @@ export default function RobotAnimator({
|
|
|
22
22
|
onRotationChanged,
|
|
23
23
|
children,
|
|
24
24
|
}: RobotAnimatorProps) {
|
|
25
|
-
Globals.assign({ frameLoop: "always" })
|
|
26
25
|
const jointValues = useRef<number[]>([])
|
|
27
26
|
const jointObjects = useRef<Object3D[]>([])
|
|
27
|
+
const interpolatorRef = useRef<ValueInterpolator | null>(null)
|
|
28
28
|
const { invalidate } = useThree()
|
|
29
29
|
|
|
30
|
+
// Initialize interpolator
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const initialJointValues =
|
|
33
|
+
rapidlyChangingMotionState.state.joint_position.joints.filter(
|
|
34
|
+
(item) => item !== undefined,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
interpolatorRef.current = new ValueInterpolator(initialJointValues, {
|
|
38
|
+
tension: 120, // Controls spring stiffness - higher values create faster, more responsive motion
|
|
39
|
+
friction: 20, // Controls damping - higher values reduce oscillation and create smoother settling
|
|
40
|
+
threshold: 0.001,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
interpolatorRef.current?.destroy()
|
|
45
|
+
}
|
|
46
|
+
}, [])
|
|
47
|
+
|
|
48
|
+
// Animation loop that runs at the display's refresh rate
|
|
49
|
+
useFrame((state, delta) => {
|
|
50
|
+
if (interpolatorRef.current) {
|
|
51
|
+
const isComplete = interpolatorRef.current.update(delta)
|
|
52
|
+
setRotation()
|
|
53
|
+
|
|
54
|
+
// Trigger a re-render only if the animation is still running
|
|
55
|
+
if (!isComplete) {
|
|
56
|
+
invalidate()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
30
61
|
function setGroupRef(group: Group | null) {
|
|
31
62
|
if (!group) return
|
|
32
63
|
|
|
@@ -39,13 +70,11 @@ export default function RobotAnimator({
|
|
|
39
70
|
|
|
40
71
|
function updateJoints(newJointValues: number[]) {
|
|
41
72
|
jointValues.current = newJointValues
|
|
42
|
-
|
|
73
|
+
interpolatorRef.current?.setTarget(newJointValues)
|
|
43
74
|
}
|
|
44
75
|
|
|
45
76
|
function setRotation() {
|
|
46
|
-
const updatedJointValues =
|
|
47
|
-
(axisValues as any)[objectIndex].get(),
|
|
48
|
-
)
|
|
77
|
+
const updatedJointValues = interpolatorRef.current?.getCurrentValues() || []
|
|
49
78
|
|
|
50
79
|
if (onRotationChanged) {
|
|
51
80
|
onRotationChanged(jointObjects.current, updatedJointValues)
|
|
@@ -56,7 +85,7 @@ export default function RobotAnimator({
|
|
|
56
85
|
const rotationSign = dhParam.reverse_rotation_direction ? -1 : 1
|
|
57
86
|
|
|
58
87
|
object.rotation.y =
|
|
59
|
-
rotationSign * updatedJointValues[index]
|
|
88
|
+
rotationSign * (updatedJointValues[index] || 0) + rotationOffset
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
}
|
|
@@ -70,19 +99,5 @@ export default function RobotAnimator({
|
|
|
70
99
|
requestAnimationFrame(() => updateJoints(newJointValues))
|
|
71
100
|
})
|
|
72
101
|
|
|
73
|
-
const [axisValues, setSpring] = useSpring(() => ({
|
|
74
|
-
...Object.assign(
|
|
75
|
-
{},
|
|
76
|
-
rapidlyChangingMotionState.state.joint_position.joints,
|
|
77
|
-
),
|
|
78
|
-
onChange: () => {
|
|
79
|
-
setRotation()
|
|
80
|
-
invalidate()
|
|
81
|
-
},
|
|
82
|
-
onResolve: () => {
|
|
83
|
-
setRotation()
|
|
84
|
-
},
|
|
85
|
-
}))
|
|
86
|
-
|
|
87
102
|
return <group ref={setGroupRef}>{children}</group>
|
|
88
103
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ThreeElements } from "@react-three/fiber"
|
|
2
2
|
import type {
|
|
3
3
|
DHParameter,
|
|
4
4
|
MotionGroupStateResponse,
|
|
@@ -18,7 +18,7 @@ import { defaultGetModel } from "./robotModelLogic"
|
|
|
18
18
|
export type DHRobotProps = {
|
|
19
19
|
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
20
20
|
dhParameters: Array<DHParameter>
|
|
21
|
-
} &
|
|
21
|
+
} & ThreeElements["group"]
|
|
22
22
|
|
|
23
23
|
export type SupportedRobotProps = {
|
|
24
24
|
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
@@ -28,7 +28,7 @@ export type SupportedRobotProps = {
|
|
|
28
28
|
getModel?: (modelFromController: string) => string
|
|
29
29
|
postModelRender?: () => void
|
|
30
30
|
transparentColor?: string
|
|
31
|
-
} &
|
|
31
|
+
} & ThreeElements["group"]
|
|
32
32
|
|
|
33
33
|
export const SupportedRobot = externalizeComponent(
|
|
34
34
|
({
|