@wandelbots/wandelbots-js-react-components 1.35.0 → 1.36.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/dist/components/jogging/JoggingOptions.d.ts.map +1 -1
- package/dist/components/jogging/PoseCartesianValues.d.ts +1 -1
- package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
- package/dist/components/robots/DHRobot.d.ts.map +1 -1
- package/dist/components/robots/GenericRobot.d.ts +13 -2
- package/dist/components/robots/GenericRobot.d.ts.map +1 -1
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/components/robots/RobotAnimator.d.ts +3 -5
- package/dist/components/robots/RobotAnimator.d.ts.map +1 -1
- package/dist/components/robots/SupportedRobot.d.ts +4 -4
- package/dist/components/robots/SupportedRobot.d.ts.map +1 -1
- package/dist/components/robots/ghostStyle.d.ts +4 -0
- package/dist/components/robots/ghostStyle.d.ts.map +1 -0
- package/dist/components/robots/robotModelLogic.d.ts +25 -0
- package/dist/components/robots/robotModelLogic.d.ts.map +1 -0
- package/dist/index.cjs +30 -30
- 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 +3464 -3398
- package/dist/index.js.map +1 -1
- package/package.json +49 -34
- package/src/components/jogging/JoggingOptions.tsx +1 -0
- package/src/components/jogging/PoseCartesianValues.tsx +2 -4
- package/src/components/robots/DHRobot.tsx +2 -10
- package/src/components/robots/GenericRobot.tsx +30 -9
- package/src/components/robots/Robot.tsx +2 -1
- package/src/components/robots/RobotAnimator.tsx +6 -13
- package/src/components/robots/SupportedRobot.tsx +35 -111
- package/src/components/robots/ghostStyle.ts +70 -0
- package/src/components/robots/robotModelLogic.ts +90 -0
- package/src/index.ts +1 -0
- package/dist/components/robots/types.d.ts +0 -19
- package/dist/components/robots/types.d.ts.map +0 -1
- package/dist/components/utils/robotTreeQuery.d.ts +0 -6
- package/dist/components/utils/robotTreeQuery.d.ts.map +0 -1
- package/src/components/robots/types.ts +0 -21
- package/src/components/utils/robotTreeQuery.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/wandelbots-js-react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -24,7 +24,12 @@
|
|
|
24
24
|
"dev:build": "nodemon -V -w src -e '*' -x \"vite build && tsc --declaration --emitDeclarationOnly\"",
|
|
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
|
-
"test": "
|
|
27
|
+
"test": "test-storybook --url http://127.0.0.1:6006 --index-json --browsers chromium",
|
|
28
|
+
"ci:test": "run-s ci:test:build ci:test:built",
|
|
29
|
+
"ci:test:build": "storybook build --quiet --test",
|
|
30
|
+
"ci:test:built": "run-p --race ci:test:server ci:test:runner",
|
|
31
|
+
"ci:test:server": "http-server storybook-static --port 9009 --silent",
|
|
32
|
+
"ci:test:runner": "wait-on tcp:127.0.0.1:9009 && test-storybook --url http://127.0.0.1:9009 --index-json --browsers chromium",
|
|
28
33
|
"build": "rimraf dist && vite build && tsc --declaration --emitDeclarationOnly",
|
|
29
34
|
"build-storybook": "storybook build"
|
|
30
35
|
},
|
|
@@ -43,55 +48,65 @@
|
|
|
43
48
|
"@emotion/react": "^11.13.3",
|
|
44
49
|
"@emotion/styled": "^11.13.0",
|
|
45
50
|
"@mui/material": "^5.16.7",
|
|
46
|
-
"@rollup/plugin-commonjs": "^
|
|
51
|
+
"@rollup/plugin-commonjs": "^28.0.1",
|
|
47
52
|
"@rollup/plugin-json": "^6.1.0",
|
|
48
|
-
"@rollup/plugin-node-resolve": "^15.
|
|
53
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
49
54
|
"@rollup/plugin-terser": "^0.4.4",
|
|
50
|
-
"@rollup/plugin-typescript": "^
|
|
51
|
-
"@storybook/addon-docs": "^8.2
|
|
52
|
-
"@storybook/addon-essentials": "^8.2
|
|
53
|
-
"@storybook/
|
|
54
|
-
"@storybook/
|
|
55
|
-
"@storybook/react
|
|
56
|
-
"@storybook/
|
|
55
|
+
"@rollup/plugin-typescript": "^12.1.1",
|
|
56
|
+
"@storybook/addon-docs": "^8.4.2",
|
|
57
|
+
"@storybook/addon-essentials": "^8.4.2",
|
|
58
|
+
"@storybook/addon-interactions": "^8.4.2",
|
|
59
|
+
"@storybook/blocks": "^8.4.2",
|
|
60
|
+
"@storybook/react": "^8.4.2",
|
|
61
|
+
"@storybook/react-vite": "^8.4.2",
|
|
62
|
+
"@storybook/test": "^8.4.2",
|
|
57
63
|
"@storybook/test-runner": "^0.19.1",
|
|
58
|
-
"@storybook/types": "^8.2
|
|
64
|
+
"@storybook/types": "^8.4.2",
|
|
59
65
|
"@svgr/rollup": "^8.1.0",
|
|
60
66
|
"@types/lodash-es": "^4.17.12",
|
|
61
|
-
"@types/react": "^18.3.
|
|
62
|
-
"@types/three": "^0.
|
|
63
|
-
"@vitejs/plugin-react": "^4.3.
|
|
64
|
-
"
|
|
65
|
-
"
|
|
67
|
+
"@types/react": "^18.3.12",
|
|
68
|
+
"@types/three": "^0.169.0",
|
|
69
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
70
|
+
"add": "^2.0.6",
|
|
71
|
+
"glob": "^11.0.0",
|
|
72
|
+
"http-server": "^14.1.1",
|
|
73
|
+
"jest-simple-dot-reporter": "^1.0.5",
|
|
74
|
+
"jest-summary-reporter": "^0.0.2",
|
|
75
|
+
"nodemon": "^3.1.7",
|
|
76
|
+
"npm-run-all": "^4.1.5",
|
|
77
|
+
"postcss": "^8.4.47",
|
|
66
78
|
"prettier-eslint": "^16.3.0",
|
|
67
79
|
"prop-types": "^15.8.1",
|
|
68
80
|
"react": "^18.3.1",
|
|
69
81
|
"react-dom": "^18.3.1",
|
|
70
82
|
"rimraf": "^6.0.1",
|
|
71
|
-
"rollup": "^4.
|
|
83
|
+
"rollup": "^4.24.4",
|
|
72
84
|
"rollup-plugin-dts": "^6.1.1",
|
|
73
85
|
"rollup-plugin-gltf": "^4.0.0",
|
|
74
86
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
75
87
|
"rollup-plugin-postcss": "^4.0.2",
|
|
76
|
-
"semantic-release": "^24.
|
|
77
|
-
"storybook": "^8.2
|
|
88
|
+
"semantic-release": "^24.2.0",
|
|
89
|
+
"storybook": "^8.4.2",
|
|
78
90
|
"storybook-dark-mode": "^4.0.2",
|
|
79
91
|
"storybook-preset-inline-svg": "^1.0.1",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
92
|
+
"ts-dedent": "^2.2.0",
|
|
93
|
+
"typescript": "^5.6.3",
|
|
94
|
+
"unplugin": "^1.15.0",
|
|
95
|
+
"vite": "^5.4.10",
|
|
96
|
+
"vite-plugin-svgr": "^4.3.0",
|
|
97
|
+
"vitest": "^2.1.4",
|
|
98
|
+
"wait-on": "^8.0.1"
|
|
84
99
|
},
|
|
85
100
|
"peerDependencies": {
|
|
86
101
|
"@emotion/react": "^11.11.1",
|
|
87
102
|
"@emotion/styled": "^11.11.0",
|
|
88
|
-
"@mui/material": "
|
|
103
|
+
"@mui/material": "^5",
|
|
89
104
|
"@react-spring/three": ">=9",
|
|
90
105
|
"@react-three/drei": ">=9",
|
|
91
106
|
"@react-three/fiber": ">=8",
|
|
92
|
-
"react": "
|
|
93
|
-
"react-dom": "
|
|
94
|
-
"three": ">=0.
|
|
107
|
+
"react": ">=18.2.0",
|
|
108
|
+
"react-dom": ">=18.2.0",
|
|
109
|
+
"three": ">=0.170",
|
|
95
110
|
"three-stdlib": ">=2"
|
|
96
111
|
},
|
|
97
112
|
"peerDependenciesMeta": {
|
|
@@ -103,14 +118,14 @@
|
|
|
103
118
|
"@monaco-editor/react": "^4.6.0",
|
|
104
119
|
"@mui/icons-material": "^5.16.7",
|
|
105
120
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
106
|
-
"@shikijs/monaco": "^1.
|
|
107
|
-
"@wandelbots/wandelbots-js": "^1.11.
|
|
121
|
+
"@shikijs/monaco": "^1.22.2",
|
|
122
|
+
"@wandelbots/wandelbots-js": "^1.11.2",
|
|
108
123
|
"i18next-browser-languagedetector": "^8.0.0",
|
|
109
124
|
"lodash-es": "^4.17.21",
|
|
110
|
-
"mobx": "^6.13.
|
|
125
|
+
"mobx": "^6.13.5",
|
|
111
126
|
"mobx-react-lite": "^4.0.7",
|
|
112
|
-
"react-error-boundary": "^4.
|
|
113
|
-
"react-i18next": "^15.0
|
|
114
|
-
"shiki": "^1.
|
|
127
|
+
"react-error-boundary": "^4.1.2",
|
|
128
|
+
"react-i18next": "^15.1.0",
|
|
129
|
+
"shiki": "^1.22.2"
|
|
115
130
|
}
|
|
116
131
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Stack } from "@mui/material"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
poseToWandelscriptString,
|
|
5
|
-
} from "@wandelbots/wandelbots-js"
|
|
2
|
+
import type { MotionStreamConnection } from "@wandelbots/wandelbots-js"
|
|
3
|
+
import { poseToWandelscriptString } from "@wandelbots/wandelbots-js"
|
|
6
4
|
import { observer } from "mobx-react-lite"
|
|
7
5
|
import { useRef } from "react"
|
|
8
6
|
import { CopyableText } from "../CopyableText"
|
|
@@ -3,10 +3,6 @@ import type { DHParameter } from "@wandelbots/wandelbots-api-client"
|
|
|
3
3
|
import type * as THREE from "three"
|
|
4
4
|
import { Matrix4, Quaternion, Vector3 } from "three"
|
|
5
5
|
import type { LineGeometry } from "three/examples/jsm/lines/LineGeometry.js"
|
|
6
|
-
import {
|
|
7
|
-
getAllJointsByName,
|
|
8
|
-
type RobotSceneJoint,
|
|
9
|
-
} from "../utils/robotTreeQuery"
|
|
10
6
|
import RobotAnimator from "./RobotAnimator"
|
|
11
7
|
import type { DHRobotProps } from "./SupportedRobot"
|
|
12
8
|
|
|
@@ -85,16 +81,11 @@ export function DHRobot({
|
|
|
85
81
|
})
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
function jointCollector(rootObject: THREE.Object3D): RobotSceneJoint[] {
|
|
89
|
-
return getAllJointsByName(rootObject, "^group_[0-9]+$")
|
|
90
|
-
}
|
|
91
|
-
|
|
92
84
|
return (
|
|
93
85
|
<>
|
|
94
86
|
<RobotAnimator
|
|
95
87
|
rapidlyChangingMotionState={rapidlyChangingMotionState}
|
|
96
88
|
dhParameters={dhParameters}
|
|
97
|
-
jointCollector={jointCollector}
|
|
98
89
|
onRotationChanged={setRotation}
|
|
99
90
|
>
|
|
100
91
|
<group {...props} name="Scene">
|
|
@@ -108,8 +99,9 @@ export function DHRobot({
|
|
|
108
99
|
rapidlyChangingMotionState.state.joint_position.joints[index] ??
|
|
109
100
|
0,
|
|
110
101
|
)
|
|
102
|
+
const jointName = `dhrobot_J0${index}`
|
|
111
103
|
return (
|
|
112
|
-
<group name={
|
|
104
|
+
<group name={jointName} key={jointName}>
|
|
113
105
|
<Line
|
|
114
106
|
name={CHILD_LINE}
|
|
115
107
|
points={[a, b]}
|
|
@@ -1,25 +1,46 @@
|
|
|
1
1
|
import { animated } from "@react-spring/three"
|
|
2
2
|
import { useGLTF } from "@react-three/drei"
|
|
3
|
-
import type {
|
|
3
|
+
import type { GroupProps } from "@react-three/fiber"
|
|
4
|
+
import React, { useCallback } from "react"
|
|
5
|
+
import type { Group, Mesh } from "three"
|
|
4
6
|
import { type Object3D } from "three"
|
|
5
|
-
import
|
|
7
|
+
import { isFlange, parseRobotModel } from "./robotModelLogic"
|
|
8
|
+
|
|
9
|
+
export type RobotModelProps = {
|
|
10
|
+
modelURL: string
|
|
11
|
+
/**
|
|
12
|
+
* Called after a robot model has been loaded and
|
|
13
|
+
* rendered into the threejs scene
|
|
14
|
+
*/
|
|
15
|
+
postModelRender?: () => void
|
|
16
|
+
flangeRef?: React.Ref<Group>
|
|
17
|
+
} & GroupProps
|
|
6
18
|
|
|
7
19
|
function isMesh(node: Object3D): node is Mesh {
|
|
8
20
|
return node.type === "Mesh"
|
|
9
21
|
}
|
|
10
22
|
|
|
11
|
-
function isFlange(node: Object3D): boolean {
|
|
12
|
-
return node.name.endsWith("_FLG")
|
|
13
|
-
}
|
|
14
|
-
|
|
15
23
|
export function GenericRobot({
|
|
16
24
|
modelURL,
|
|
17
25
|
flangeRef,
|
|
26
|
+
postModelRender,
|
|
18
27
|
...props
|
|
19
28
|
}: RobotModelProps) {
|
|
20
|
-
const gltf =
|
|
29
|
+
const { gltf } = parseRobotModel(
|
|
30
|
+
useGLTF(modelURL),
|
|
31
|
+
modelURL.split("/").pop() || modelURL,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const groupRef: React.RefCallback<Group> = useCallback(
|
|
35
|
+
(group) => {
|
|
36
|
+
if (group && postModelRender) {
|
|
37
|
+
postModelRender()
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
[modelURL],
|
|
41
|
+
)
|
|
21
42
|
|
|
22
|
-
|
|
43
|
+
function renderNode(node: Object3D): React.ReactNode {
|
|
23
44
|
if (isMesh(node)) {
|
|
24
45
|
return (
|
|
25
46
|
<mesh
|
|
@@ -47,7 +68,7 @@ export function GenericRobot({
|
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
return (
|
|
50
|
-
<group {...props} dispose={null}>
|
|
71
|
+
<group {...props} dispose={null} ref={groupRef}>
|
|
51
72
|
{renderNode(gltf.scene)}
|
|
52
73
|
</group>
|
|
53
74
|
)
|
|
@@ -2,7 +2,8 @@ import { type GroupProps } from "@react-three/fiber"
|
|
|
2
2
|
|
|
3
3
|
import type { ConnectedMotionGroup } from "@wandelbots/wandelbots-js"
|
|
4
4
|
import type { Group } from "three"
|
|
5
|
-
import {
|
|
5
|
+
import { SupportedRobot } from "./SupportedRobot"
|
|
6
|
+
import { defaultGetModel } from "./robotModelLogic"
|
|
6
7
|
|
|
7
8
|
export type RobotProps = {
|
|
8
9
|
connectedMotionGroup: ConnectedMotionGroup
|
|
@@ -5,39 +5,32 @@ import type {
|
|
|
5
5
|
MotionGroupStateResponse,
|
|
6
6
|
} from "@wandelbots/wandelbots-api-client"
|
|
7
7
|
import React, { useRef } from "react"
|
|
8
|
-
import type
|
|
8
|
+
import type { Group, Object3D } from "three"
|
|
9
9
|
import { useAutorun } from "../utils/hooks"
|
|
10
|
-
import {
|
|
11
|
-
getAllJointsByName,
|
|
12
|
-
type RobotSceneJoint,
|
|
13
|
-
} from "../utils/robotTreeQuery"
|
|
10
|
+
import { collectJoints } from "./robotModelLogic"
|
|
14
11
|
|
|
15
12
|
type RobotAnimatorProps = {
|
|
16
13
|
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
17
14
|
dhParameters: DHParameter[]
|
|
18
|
-
onRotationChanged?: (joints:
|
|
19
|
-
jointCollector?: (rootObject: THREE.Object3D) => RobotSceneJoint[]
|
|
15
|
+
onRotationChanged?: (joints: Object3D[], jointValues: number[]) => void
|
|
20
16
|
children: React.ReactNode
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
export default function RobotAnimator({
|
|
24
20
|
rapidlyChangingMotionState,
|
|
25
21
|
dhParameters,
|
|
26
|
-
jointCollector,
|
|
27
22
|
onRotationChanged,
|
|
28
23
|
children,
|
|
29
24
|
}: RobotAnimatorProps) {
|
|
30
25
|
Globals.assign({ frameLoop: "always" })
|
|
31
26
|
const jointValues = useRef<number[]>([])
|
|
32
|
-
const jointObjects = useRef<
|
|
27
|
+
const jointObjects = useRef<Object3D[]>([])
|
|
33
28
|
const { invalidate } = useThree()
|
|
34
29
|
|
|
35
|
-
function setGroupRef(group:
|
|
30
|
+
function setGroupRef(group: Group | null) {
|
|
36
31
|
if (!group) return
|
|
37
32
|
|
|
38
|
-
jointObjects.current =
|
|
39
|
-
? jointCollector(group)
|
|
40
|
-
: getAllJointsByName(group)
|
|
33
|
+
jointObjects.current = collectJoints(group)
|
|
41
34
|
|
|
42
35
|
// Set initial position
|
|
43
36
|
setRotation()
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import { Suspense, useCallback, useEffect, useRef } from "react"
|
|
2
|
-
|
|
3
|
-
import { version } from "../../../package.json"
|
|
4
|
-
|
|
5
1
|
import type { GroupProps } from "@react-three/fiber"
|
|
6
2
|
import type {
|
|
7
3
|
DHParameter,
|
|
8
4
|
MotionGroupStateResponse,
|
|
9
5
|
} from "@wandelbots/wandelbots-api-client"
|
|
6
|
+
import { Suspense, useCallback, useEffect, useState } from "react"
|
|
10
7
|
import { DHRobot } from "./DHRobot"
|
|
11
8
|
|
|
12
9
|
import { ErrorBoundary } from "react-error-boundary"
|
|
13
|
-
import * as THREE from "three"
|
|
10
|
+
import type * as THREE from "three"
|
|
14
11
|
import { externalizeComponent } from "../../externalizeComponent"
|
|
15
12
|
import ConsoleFilter from "../ConsoleFilter"
|
|
16
13
|
import { GenericRobot } from "./GenericRobot"
|
|
17
14
|
import RobotAnimator from "./RobotAnimator"
|
|
15
|
+
import { applyGhostStyle, removeGhostStyle } from "./ghostStyle"
|
|
16
|
+
import { defaultGetModel } from "./robotModelLogic"
|
|
18
17
|
|
|
19
18
|
export type DHRobotProps = {
|
|
20
19
|
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
@@ -25,19 +24,12 @@ export type SupportedRobotProps = {
|
|
|
25
24
|
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
26
25
|
modelFromController: string
|
|
27
26
|
dhParameters: DHParameter[]
|
|
28
|
-
getModel?: (modelFromController: string) => string
|
|
29
27
|
isGhost?: boolean
|
|
30
28
|
flangeRef?: React.Ref<THREE.Group>
|
|
29
|
+
getModel?: (modelFromController: string) => string
|
|
30
|
+
postModelRender?: () => void
|
|
31
31
|
} & GroupProps
|
|
32
32
|
|
|
33
|
-
export function defaultGetModel(modelFromController: string): string {
|
|
34
|
-
let useVersion = version
|
|
35
|
-
if (version.startsWith("0.")) {
|
|
36
|
-
useVersion = ""
|
|
37
|
-
}
|
|
38
|
-
return `https://cdn.jsdelivr.net/gh/wandelbotsgmbh/wandelbots-js-react-components${useVersion ? `@${useVersion}` : ""}/public/models/${modelFromController}.glb`
|
|
39
|
-
}
|
|
40
|
-
|
|
41
33
|
export const SupportedRobot = externalizeComponent(
|
|
42
34
|
({
|
|
43
35
|
rapidlyChangingMotionState,
|
|
@@ -46,115 +38,46 @@ export const SupportedRobot = externalizeComponent(
|
|
|
46
38
|
getModel = defaultGetModel,
|
|
47
39
|
isGhost = false,
|
|
48
40
|
flangeRef,
|
|
41
|
+
postModelRender,
|
|
49
42
|
...props
|
|
50
43
|
}: SupportedRobotProps) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const setRobotRef = useCallback(
|
|
54
|
-
(instance: THREE.Group | null) => {
|
|
55
|
-
if (!instance) return
|
|
56
|
-
robotRef.current = instance
|
|
57
|
-
|
|
58
|
-
if (isGhost) applyGhostStyle()
|
|
59
|
-
},
|
|
60
|
-
[isGhost],
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
const applyGhostStyle = () => {
|
|
64
|
-
if (!robotRef.current || robotRef.current.userData.isGhost) return
|
|
65
|
-
|
|
66
|
-
robotRef.current.traverse((obj) => {
|
|
67
|
-
if (obj instanceof THREE.Mesh) {
|
|
68
|
-
if (obj.material instanceof THREE.Material) {
|
|
69
|
-
obj.material.colorWrite = true
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Create a clone of the mesh
|
|
73
|
-
const depth = obj.clone()
|
|
74
|
-
const ghost = obj.clone()
|
|
75
|
-
|
|
76
|
-
depth.material = new THREE.MeshStandardMaterial({
|
|
77
|
-
depthTest: true,
|
|
78
|
-
depthWrite: true,
|
|
79
|
-
colorWrite: false,
|
|
80
|
-
polygonOffset: true,
|
|
81
|
-
polygonOffsetFactor: 1,
|
|
82
|
-
})
|
|
83
|
-
depth.userData.isGhost = true
|
|
84
|
-
|
|
85
|
-
// Set the material for the ghost mesh
|
|
86
|
-
ghost.material = new THREE.MeshStandardMaterial({
|
|
87
|
-
color: "#D91433",
|
|
88
|
-
opacity: 0.3,
|
|
89
|
-
depthTest: true,
|
|
90
|
-
depthWrite: false,
|
|
91
|
-
transparent: true,
|
|
92
|
-
polygonOffset: true,
|
|
93
|
-
polygonOffsetFactor: -1,
|
|
94
|
-
})
|
|
95
|
-
ghost.userData.isGhost = true
|
|
44
|
+
const [robotGroup, setRobotGroup] = useState<THREE.Group | null>(null)
|
|
96
45
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
robotRef.current.userData.isGhost = true
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const removeGhostStyle = () => {
|
|
108
|
-
if (!robotRef.current || !robotRef.current.userData.isGhost) return
|
|
109
|
-
|
|
110
|
-
const objectsToRemove: THREE.Object3D[] = []
|
|
111
|
-
|
|
112
|
-
robotRef.current.traverse((obj) => {
|
|
113
|
-
if (obj instanceof THREE.Mesh) {
|
|
114
|
-
if (obj.userData?.isGhost) {
|
|
115
|
-
objectsToRemove.push(obj)
|
|
116
|
-
} else if (obj.material instanceof THREE.Material) {
|
|
117
|
-
obj.material.colorWrite = true
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
objectsToRemove.forEach((obj) => {
|
|
123
|
-
if (obj.parent) {
|
|
124
|
-
obj.parent.remove(obj)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
robotRef.current.userData.isGhost = true
|
|
129
|
-
}
|
|
46
|
+
const setRobotRef = useCallback((instance: THREE.Group | null) => {
|
|
47
|
+
setRobotGroup(instance)
|
|
48
|
+
}, [])
|
|
130
49
|
|
|
131
50
|
useEffect(() => {
|
|
51
|
+
if (!robotGroup) return
|
|
52
|
+
|
|
132
53
|
if (isGhost) {
|
|
133
|
-
applyGhostStyle()
|
|
54
|
+
applyGhostStyle(robotGroup)
|
|
134
55
|
} else {
|
|
135
|
-
removeGhostStyle()
|
|
56
|
+
removeGhostStyle(robotGroup)
|
|
136
57
|
}
|
|
137
|
-
}, [isGhost])
|
|
58
|
+
}, [robotGroup, isGhost])
|
|
59
|
+
|
|
60
|
+
const dhrobot = (
|
|
61
|
+
<DHRobot
|
|
62
|
+
rapidlyChangingMotionState={rapidlyChangingMotionState}
|
|
63
|
+
dhParameters={dhParameters}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
138
67
|
|
|
139
68
|
return (
|
|
140
69
|
<ErrorBoundary
|
|
141
|
-
fallback={
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
>
|
|
149
|
-
<Suspense
|
|
150
|
-
fallback={
|
|
151
|
-
<DHRobot
|
|
152
|
-
rapidlyChangingMotionState={rapidlyChangingMotionState}
|
|
153
|
-
dhParameters={dhParameters}
|
|
154
|
-
{...props}
|
|
155
|
-
/>
|
|
70
|
+
fallback={dhrobot}
|
|
71
|
+
onError={(err) => {
|
|
72
|
+
if (err.message.includes("404: Not Found")) {
|
|
73
|
+
// Missing model; show the fallback for now
|
|
74
|
+
console.error(err)
|
|
75
|
+
} else {
|
|
76
|
+
throw err
|
|
156
77
|
}
|
|
157
|
-
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<Suspense fallback={dhrobot}>
|
|
158
81
|
<group ref={setRobotRef}>
|
|
159
82
|
<RobotAnimator
|
|
160
83
|
rapidlyChangingMotionState={rapidlyChangingMotionState}
|
|
@@ -162,6 +85,7 @@ export const SupportedRobot = externalizeComponent(
|
|
|
162
85
|
>
|
|
163
86
|
<GenericRobot
|
|
164
87
|
modelURL={getModel(modelFromController)}
|
|
88
|
+
postModelRender={postModelRender}
|
|
165
89
|
flangeRef={flangeRef}
|
|
166
90
|
{...props}
|
|
167
91
|
/>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Group, Object3D } from "three"
|
|
2
|
+
import { Material, Mesh, MeshStandardMaterial } from "three"
|
|
3
|
+
|
|
4
|
+
export const applyGhostStyle = (robot: Group) => {
|
|
5
|
+
if (robot.userData.isGhost) return
|
|
6
|
+
|
|
7
|
+
robot.traverse((obj) => {
|
|
8
|
+
if (obj instanceof Mesh) {
|
|
9
|
+
if (obj.material instanceof Material) {
|
|
10
|
+
obj.material.colorWrite = true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Create a clone of the mesh
|
|
14
|
+
const depth = obj.clone()
|
|
15
|
+
const ghost = obj.clone()
|
|
16
|
+
|
|
17
|
+
depth.material = new MeshStandardMaterial({
|
|
18
|
+
depthTest: true,
|
|
19
|
+
depthWrite: true,
|
|
20
|
+
colorWrite: false,
|
|
21
|
+
polygonOffset: true,
|
|
22
|
+
polygonOffsetFactor: 1,
|
|
23
|
+
})
|
|
24
|
+
depth.userData.isGhost = true
|
|
25
|
+
|
|
26
|
+
// Set the material for the ghost mesh
|
|
27
|
+
ghost.material = new MeshStandardMaterial({
|
|
28
|
+
color: "#D91433",
|
|
29
|
+
opacity: 0.3,
|
|
30
|
+
depthTest: true,
|
|
31
|
+
depthWrite: false,
|
|
32
|
+
transparent: true,
|
|
33
|
+
polygonOffset: true,
|
|
34
|
+
polygonOffsetFactor: -1,
|
|
35
|
+
})
|
|
36
|
+
ghost.userData.isGhost = true
|
|
37
|
+
|
|
38
|
+
if (obj.parent) {
|
|
39
|
+
obj.parent.add(depth)
|
|
40
|
+
obj.parent.add(ghost)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
robot.userData.isGhost = true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const removeGhostStyle = (robot: Group) => {
|
|
49
|
+
if (!robot.userData.isGhost) return
|
|
50
|
+
|
|
51
|
+
const objectsToRemove: Object3D[] = []
|
|
52
|
+
|
|
53
|
+
robot.traverse((obj) => {
|
|
54
|
+
if (obj instanceof Mesh) {
|
|
55
|
+
if (obj.userData?.isGhost) {
|
|
56
|
+
objectsToRemove.push(obj)
|
|
57
|
+
} else if (obj.material instanceof Material) {
|
|
58
|
+
obj.material.colorWrite = true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
objectsToRemove.forEach((obj) => {
|
|
64
|
+
if (obj.parent) {
|
|
65
|
+
obj.parent.remove(obj)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
robot.userData.isGhost = true
|
|
70
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Object3D } from "three"
|
|
2
|
+
import type { GLTF } from "three-stdlib"
|
|
3
|
+
import { version } from "../../../package.json"
|
|
4
|
+
|
|
5
|
+
export function defaultGetModel(modelFromController: string): string {
|
|
6
|
+
let useVersion = version
|
|
7
|
+
if (version.startsWith("0.")) {
|
|
8
|
+
useVersion = ""
|
|
9
|
+
}
|
|
10
|
+
return `https://cdn.jsdelivr.net/gh/wandelbotsgmbh/wandelbots-js-react-components${useVersion ? `@${useVersion}` : ""}/public/models/${modelFromController}.glb`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Finds all the joint groups in a GLTF tree, as identified
|
|
15
|
+
* by the _Jxx name ending convention.
|
|
16
|
+
*/
|
|
17
|
+
export function collectJoints(rootObject: Object3D): Object3D[] {
|
|
18
|
+
function getAllObjects(root: Object3D): Object3D[] {
|
|
19
|
+
if (root.children.length === 0) {
|
|
20
|
+
return [root]
|
|
21
|
+
}
|
|
22
|
+
return [root, ...root.children.flatMap((child) => getAllObjects(child))]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return getAllObjects(rootObject).filter((o) => isJoint(o))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a specified threejs object represents the flange of a
|
|
30
|
+
* robot, based on the _FLG name ending convention.
|
|
31
|
+
*/
|
|
32
|
+
export function isFlange(node: Object3D) {
|
|
33
|
+
return node.name.endsWith("_FLG")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if a specified threejs object represents a joint of a
|
|
38
|
+
* robot, based on the _Jxx name ending convention.
|
|
39
|
+
*/
|
|
40
|
+
export function isJoint(node: Object3D) {
|
|
41
|
+
return /_J[0-9]+$/.test(node.name)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validates that the loaded GLTF file has six joints and a flange group.
|
|
46
|
+
*/
|
|
47
|
+
export function parseRobotModel(gltf: GLTF, filename: string): { gltf: GLTF } {
|
|
48
|
+
let flange: Object3D | undefined
|
|
49
|
+
const joints: Object3D[] = []
|
|
50
|
+
|
|
51
|
+
function isSixJoints(
|
|
52
|
+
joints: Object3D[],
|
|
53
|
+
): joints is [Object3D, Object3D, Object3D, Object3D, Object3D, Object3D] {
|
|
54
|
+
return joints.length === 6
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseNode(node: Object3D) {
|
|
58
|
+
if (isFlange(node)) {
|
|
59
|
+
if (flange) {
|
|
60
|
+
throw Error(
|
|
61
|
+
`Found multiple flange groups in robot model ${filename}; first ${flange.name} then ${node.name}. Only one _FLG group is allowed.`,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
flange = node
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isJoint(node)) {
|
|
69
|
+
joints.push(node)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
node.children.map(parseNode)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
parseNode(gltf.scene)
|
|
76
|
+
|
|
77
|
+
if (!isSixJoints(joints)) {
|
|
78
|
+
throw Error(
|
|
79
|
+
`Expected to find 6 joint groups in robot model ${filename} with names _J01, _J02 etc, found ${joints.length}. Only 6-joint robot models are currently supported.`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!flange) {
|
|
84
|
+
throw Error(
|
|
85
|
+
`No flange group found in robot model ${filename}. Flange must be identified with a name ending in _FLG.`,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { gltf }
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./components/LoadingCover"
|
|
|
10
10
|
export * from "./components/modal/NoMotionGroupModal"
|
|
11
11
|
export * from "./components/robots/AxisConfig"
|
|
12
12
|
export * from "./components/robots/Robot"
|
|
13
|
+
export { defaultGetModel } from "./components/robots/robotModelLogic"
|
|
13
14
|
export * from "./components/robots/SupportedRobot"
|
|
14
15
|
export * from "./components/SelectableFab"
|
|
15
16
|
export * from "./components/utils/hooks"
|