@wandelbots/wandelbots-js-react-components 2.27.1 → 2.28.0-pr.feature-add-program-control-component.367.28afd72
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/ProgramControl.d.ts +43 -0
- package/dist/components/ProgramControl.d.ts.map +1 -0
- 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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8283 -9714
- 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/ProgramControl.tsx +217 -0
- 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/i18n/locales/de/translations.json +5 -1
- package/src/i18n/locales/en/translations.json +5 -1
- package/src/index.ts +2 -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-pr.feature-add-program-control-component.367.28afd72",
|
|
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
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Pause, PlayArrow, Stop } from "@mui/icons-material"
|
|
2
|
+
import { Box, Button, Typography, useTheme } from "@mui/material"
|
|
3
|
+
import { observer } from "mobx-react-lite"
|
|
4
|
+
import { useTranslation } from "react-i18next"
|
|
5
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
6
|
+
|
|
7
|
+
export type ProgramState = "idle" | "running" | "paused" | "stopping"
|
|
8
|
+
|
|
9
|
+
export interface ProgramControlProps {
|
|
10
|
+
/** The current state of the program control */
|
|
11
|
+
state: ProgramState
|
|
12
|
+
/** Callback fired when the run/resume button is clicked */
|
|
13
|
+
onRun: () => void
|
|
14
|
+
/** Callback fired when the pause button is clicked (only available in 'with_pause' variant) */
|
|
15
|
+
onPause?: () => void
|
|
16
|
+
/** Callback fired when the stop button is clicked */
|
|
17
|
+
onStop: () => void
|
|
18
|
+
/**
|
|
19
|
+
* Function to reset the component from 'stopping' state back to 'idle'.
|
|
20
|
+
* This must be called manually by the user when requiresManualReset is true.
|
|
21
|
+
*/
|
|
22
|
+
onReset?: () => void
|
|
23
|
+
/**
|
|
24
|
+
* When true, the component will stay in 'stopping' state until onReset is called manually.
|
|
25
|
+
* When false (default), auto-resets to 'idle' after 2 seconds.
|
|
26
|
+
*/
|
|
27
|
+
requiresManualReset?: boolean
|
|
28
|
+
/**
|
|
29
|
+
* Variant of the component:
|
|
30
|
+
* - 'with_pause': Shows run/pause/stop buttons (default)
|
|
31
|
+
* - 'without_pause': Shows only run/stop buttons
|
|
32
|
+
*/
|
|
33
|
+
variant?: "with_pause" | "without_pause"
|
|
34
|
+
/** Additional CSS class name */
|
|
35
|
+
className?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ButtonConfig {
|
|
39
|
+
enabled: boolean
|
|
40
|
+
label: string
|
|
41
|
+
color: string
|
|
42
|
+
onClick: () => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A control component for program execution with run, pause, and stop functionality.
|
|
47
|
+
*
|
|
48
|
+
* Features:
|
|
49
|
+
* - State machine with idle, running, paused, and stopping states
|
|
50
|
+
* - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
|
|
51
|
+
* - Optional manual reset functionality
|
|
52
|
+
* - Responsive design with 110px circular buttons
|
|
53
|
+
* - Material-UI theming integration
|
|
54
|
+
*/
|
|
55
|
+
export const ProgramControl = externalizeComponent(
|
|
56
|
+
observer(
|
|
57
|
+
({
|
|
58
|
+
state,
|
|
59
|
+
onRun,
|
|
60
|
+
onPause,
|
|
61
|
+
onStop,
|
|
62
|
+
onReset,
|
|
63
|
+
requiresManualReset = false,
|
|
64
|
+
variant = "with_pause",
|
|
65
|
+
className,
|
|
66
|
+
}: ProgramControlProps) => {
|
|
67
|
+
const theme = useTheme()
|
|
68
|
+
const { t } = useTranslation()
|
|
69
|
+
|
|
70
|
+
const getButtonConfigs = (): ButtonConfig[] => {
|
|
71
|
+
const baseConfigs: Record<string, ButtonConfig> = {
|
|
72
|
+
run: {
|
|
73
|
+
enabled: state === "idle" || state === "paused",
|
|
74
|
+
label:
|
|
75
|
+
state === "paused"
|
|
76
|
+
? t("ProgramControl.Resume.bt")
|
|
77
|
+
: t("ProgramControl.Start.bt"),
|
|
78
|
+
color: theme.palette.success.main,
|
|
79
|
+
onClick: onRun,
|
|
80
|
+
},
|
|
81
|
+
pause: {
|
|
82
|
+
enabled: state === "running",
|
|
83
|
+
label: t("ProgramControl.Pause.bt"),
|
|
84
|
+
color: "#FFFFFF33",
|
|
85
|
+
onClick: onPause || (() => {}),
|
|
86
|
+
},
|
|
87
|
+
stop: {
|
|
88
|
+
enabled: state === "running" || state === "paused",
|
|
89
|
+
label: t("ProgramControl.Stop.bt"),
|
|
90
|
+
color: theme.palette.error.main,
|
|
91
|
+
onClick: onStop,
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (variant === "without_pause") {
|
|
96
|
+
return [baseConfigs.run, baseConfigs.stop]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [baseConfigs.run, baseConfigs.pause, baseConfigs.stop]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const getButtonIcon = (index: number) => {
|
|
103
|
+
const iconProps = { sx: { fontSize: "55px" } }
|
|
104
|
+
|
|
105
|
+
if (variant === "without_pause") {
|
|
106
|
+
return index === 0 ? (
|
|
107
|
+
<PlayArrow {...iconProps} />
|
|
108
|
+
) : (
|
|
109
|
+
<Stop {...iconProps} />
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
switch (index) {
|
|
114
|
+
case 0:
|
|
115
|
+
return <PlayArrow {...iconProps} />
|
|
116
|
+
case 1:
|
|
117
|
+
return <Pause {...iconProps} />
|
|
118
|
+
case 2:
|
|
119
|
+
return <Stop {...iconProps} />
|
|
120
|
+
default:
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const buttonConfigs = getButtonConfigs()
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Box
|
|
129
|
+
className={className}
|
|
130
|
+
sx={{
|
|
131
|
+
display: "flex",
|
|
132
|
+
flexDirection: "column",
|
|
133
|
+
alignItems: "center",
|
|
134
|
+
gap: 2,
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Box
|
|
138
|
+
sx={{
|
|
139
|
+
display: "flex",
|
|
140
|
+
gap: "40px",
|
|
141
|
+
flexWrap: "wrap",
|
|
142
|
+
justifyContent: "center",
|
|
143
|
+
alignItems: "center",
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{buttonConfigs.map((config, index) => (
|
|
147
|
+
<Box
|
|
148
|
+
key={config.label}
|
|
149
|
+
sx={{
|
|
150
|
+
display: "flex",
|
|
151
|
+
flexDirection: "column",
|
|
152
|
+
alignItems: "center",
|
|
153
|
+
gap: 1,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<Button
|
|
157
|
+
variant="contained"
|
|
158
|
+
disabled={
|
|
159
|
+
!config.enabled ||
|
|
160
|
+
(state === "stopping" && !requiresManualReset)
|
|
161
|
+
}
|
|
162
|
+
onClick={config.onClick}
|
|
163
|
+
sx={{
|
|
164
|
+
width: "110px",
|
|
165
|
+
height: "110px",
|
|
166
|
+
borderRadius: "110px",
|
|
167
|
+
backgroundColor: config.color,
|
|
168
|
+
opacity:
|
|
169
|
+
config.enabled &&
|
|
170
|
+
!(state === "stopping" && !requiresManualReset)
|
|
171
|
+
? 1
|
|
172
|
+
: 0.3,
|
|
173
|
+
"&:hover": {
|
|
174
|
+
backgroundColor: config.color,
|
|
175
|
+
opacity:
|
|
176
|
+
config.enabled &&
|
|
177
|
+
!(state === "stopping" && !requiresManualReset)
|
|
178
|
+
? 0.8
|
|
179
|
+
: 0.3,
|
|
180
|
+
},
|
|
181
|
+
"&:disabled": {
|
|
182
|
+
backgroundColor: config.color,
|
|
183
|
+
opacity: 0.3,
|
|
184
|
+
},
|
|
185
|
+
minWidth: "110px",
|
|
186
|
+
flexShrink: 0,
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
{getButtonIcon(index)}
|
|
190
|
+
</Button>
|
|
191
|
+
|
|
192
|
+
<Typography
|
|
193
|
+
variant="body1"
|
|
194
|
+
sx={{
|
|
195
|
+
color:
|
|
196
|
+
config.enabled &&
|
|
197
|
+
!(state === "stopping" && !requiresManualReset)
|
|
198
|
+
? config.color
|
|
199
|
+
: theme.palette.text.disabled,
|
|
200
|
+
textAlign: "center",
|
|
201
|
+
opacity:
|
|
202
|
+
config.enabled &&
|
|
203
|
+
!(state === "stopping" && !requiresManualReset)
|
|
204
|
+
? 1
|
|
205
|
+
: 0.3,
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{config.label}
|
|
209
|
+
</Typography>
|
|
210
|
+
</Box>
|
|
211
|
+
))}
|
|
212
|
+
</Box>
|
|
213
|
+
</Box>
|
|
214
|
+
)
|
|
215
|
+
},
|
|
216
|
+
),
|
|
217
|
+
)
|
|
@@ -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
|
+
})
|