@wandelbots/wandelbots-js-react-components 1.3.1 → 1.4.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/package.json +18 -8
- package/src/components/3d-viewport/CoordinateSystemTransform.tsx +44 -0
- package/src/components/3d-viewport/PresetEnvironment.tsx +78 -0
- package/src/components/3d-viewport/SafetyZonesRenderer.tsx +55 -0
- package/src/components/LoadingButton.stories.tsx +61 -0
- package/src/components/LoadingButton.tsx +19 -0
- package/src/components/LoadingCover.tsx +75 -0
- package/src/components/ThemeSelect.tsx +49 -0
- package/src/components/VelocitySlider.stories.tsx +32 -0
- package/src/components/VelocitySlider.tsx +52 -0
- package/src/components/jogging/JoggingCartesianAxisControl.stories.tsx +41 -0
- package/src/components/jogging/JoggingCartesianAxisControl.tsx +127 -0
- package/src/components/jogging/JoggingCartesianTab.tsx +265 -0
- package/src/components/jogging/JoggingCartesianValues.tsx +45 -0
- package/src/components/jogging/JoggingFreedriveTab.tsx +9 -0
- package/src/components/jogging/JoggingJointLimitDetector.tsx +51 -0
- package/src/components/jogging/JoggingJointRotationControl.stories.tsx +38 -0
- package/src/components/jogging/JoggingJointRotationControl.tsx +197 -0
- package/src/components/jogging/JoggingJointTab.tsx +93 -0
- package/src/components/jogging/JoggingJointValues.tsx +45 -0
- package/src/components/jogging/JoggingOptions.tsx +96 -0
- package/src/components/jogging/JoggingPanel.stories.tsx +26 -0
- package/src/components/jogging/JoggingPanel.tsx +148 -0
- package/src/components/jogging/JoggingStore.tsx +294 -0
- package/src/components/jogging/JoggingVelocitySlider.tsx +56 -0
- package/src/components/robots/ABB_1200_07_7.tsx +127 -0
- package/src/components/robots/AxisConfig.ts +3 -0
- package/src/components/robots/DHRobot.tsx +128 -0
- package/src/components/robots/FANUC_ARC_Mate_100iD.tsx +187 -0
- package/src/components/robots/FANUC_ARC_Mate_120iD.tsx +187 -0
- package/src/components/robots/FANUC_CRX10iA.tsx +171 -0
- package/src/components/robots/FANUC_CRX25iA.tsx +171 -0
- package/src/components/robots/FANUC_CRX25iAL.tsx +182 -0
- package/src/components/robots/KUKA_KR210_R2700.tsx +291 -0
- package/src/components/robots/KUKA_KR270_R2700.tsx +244 -0
- package/src/components/robots/Robot.tsx +42 -0
- package/src/components/robots/RobotAnimator.tsx +82 -0
- package/src/components/robots/SupportedRobot.tsx +144 -0
- package/src/components/robots/UniversalRobots_UR10.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR10e.tsx +275 -0
- package/src/components/robots/UniversalRobots_UR3.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR3e.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR5.tsx +111 -0
- package/src/components/robots/UniversalRobots_UR5e.tsx +280 -0
- package/src/components/robots/Yaskawa_AR1440.tsx +156 -0
- package/src/components/robots/Yaskawa_AR1730.tsx +169 -0
- package/src/components/robots/Yaskawa_AR2010.tsx +163 -0
- package/src/components/robots/Yaskawa_AR3120.tsx +164 -0
- package/src/components/robots/Yaskawa_AR900.tsx +125 -0
- package/src/components/utils/converters.ts +23 -0
- package/src/components/utils/errorHandling.ts +30 -0
- package/src/components/utils/hooks.tsx +54 -0
- package/src/components/utils/robotTreeQuery.ts +27 -0
- package/src/components/wandelscript-editor/WandelscriptEditor.stories.tsx +45 -0
- package/src/components/wandelscript-editor/WandelscriptEditor.tsx +114 -0
- package/src/components/wandelscript-editor/wandelscript.tmLanguage.ts +62 -0
- package/src/declarations.d.ts +10 -0
- package/src/i18n/config.ts +27 -0
- package/src/i18n/locales/de/translations.json +12 -0
- package/src/i18n/locales/en/translations.json +12 -0
- package/src/icons/arrowForwardFilled.tsx +7 -0
- package/src/icons/axis-x.svg +3 -0
- package/src/icons/axis-y.svg +3 -0
- package/src/icons/axis-z.svg +3 -0
- package/src/icons/expandFilled.tsx +11 -0
- package/src/icons/home.tsx +12 -0
- package/src/icons/index.ts +6 -0
- package/src/icons/infoOutlined.tsx +10 -0
- package/src/icons/jogging.svg +3 -0
- package/src/icons/robot.svg +3 -0
- package/src/icons/robot.tsx +14 -0
- package/src/icons/rotation.svg +4 -0
- package/src/icons/wbLogo.tsx +21 -0
- package/src/index.ts +8 -0
- package/src/themes/color.tsx +74 -0
- package/src/themes/theme.ts +150 -0
- package/src/themes/wbTheme.stories.tsx +64 -0
- package/src/themes/wbTheme.ts +186 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { observer } from "mobx-react-lite"
|
|
2
|
+
import { Stack, Typography } from "@mui/material"
|
|
3
|
+
import { useRef } from "react"
|
|
4
|
+
import { useTranslation } from "react-i18next"
|
|
5
|
+
import { JoggingStore } from "./JoggingStore"
|
|
6
|
+
import { useAnimationFrame } from "../utils/hooks"
|
|
7
|
+
|
|
8
|
+
export const JoggingJointValues = observer(({ store }: { store: JoggingStore }) => {
|
|
9
|
+
const valueHolderRef = useRef<HTMLPreElement>(null)
|
|
10
|
+
const { t } = useTranslation()
|
|
11
|
+
|
|
12
|
+
function getCurrentValueString() {
|
|
13
|
+
const { joints } =
|
|
14
|
+
store.jogger.motionStream.rapidlyChangingMotionState.state.joint_position
|
|
15
|
+
return `{${joints.map((j) => parseFloat(j.toFixed(4))).join(", ")}}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
useAnimationFrame(() => {
|
|
19
|
+
if (!valueHolderRef.current) return
|
|
20
|
+
valueHolderRef.current.textContent = getCurrentValueString()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Stack alignItems="center" marginTop="0.8rem">
|
|
25
|
+
<Typography
|
|
26
|
+
sx={{
|
|
27
|
+
fontSize: "12px",
|
|
28
|
+
marginTop: "0.8rem",
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
{t("Jogging.Joints.JointValues.lb")}
|
|
32
|
+
</Typography>
|
|
33
|
+
<Typography
|
|
34
|
+
component="pre"
|
|
35
|
+
ref={valueHolderRef}
|
|
36
|
+
sx={{
|
|
37
|
+
fontSize: "14px",
|
|
38
|
+
opacity: 0.6,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{getCurrentValueString()}
|
|
42
|
+
</Typography>
|
|
43
|
+
</Stack>
|
|
44
|
+
)
|
|
45
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Stack, MenuItem, InputLabel, Select } from "@mui/material"
|
|
2
|
+
import { observer } from "mobx-react-lite"
|
|
3
|
+
import type { IncrementOptionId, JoggingStore } from "./JoggingStore"
|
|
4
|
+
import { useTranslation } from "react-i18next"
|
|
5
|
+
import { useThemeColors } from "../../themes/wbTheme"
|
|
6
|
+
import { ThemeSelect } from "../ThemeSelect"
|
|
7
|
+
|
|
8
|
+
export const JoggingOptions = observer(({ store }: { store: JoggingStore }) => {
|
|
9
|
+
const { t } = useTranslation()
|
|
10
|
+
const colors = useThemeColors()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Stack
|
|
14
|
+
direction={"row"}
|
|
15
|
+
alignItems={"center"}
|
|
16
|
+
spacing={1}
|
|
17
|
+
sx={{
|
|
18
|
+
padding: "16px",
|
|
19
|
+
"& label": {
|
|
20
|
+
color: colors.textSubtle,
|
|
21
|
+
fontSize: "12px",
|
|
22
|
+
marginBottom: "4px",
|
|
23
|
+
},
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{/* Coordinate system */}
|
|
27
|
+
<Stack width="33%">
|
|
28
|
+
<InputLabel id="jogging-coord-select">{"Coordinate Sys."}</InputLabel>
|
|
29
|
+
<ThemeSelect
|
|
30
|
+
kind="filled"
|
|
31
|
+
labelId="jogging-coord-select"
|
|
32
|
+
value={store.selectedCoordSystemId}
|
|
33
|
+
displayEmpty={true}
|
|
34
|
+
onChange={(event) => {
|
|
35
|
+
store.setSelectedCoordSystemId(event.target.value as string)
|
|
36
|
+
}}
|
|
37
|
+
disabled={store.isLocked}
|
|
38
|
+
>
|
|
39
|
+
{store.coordSystems.map((cs) => (
|
|
40
|
+
<MenuItem key={cs.coordinate_system} value={cs.coordinate_system}>
|
|
41
|
+
{cs.name || cs.coordinate_system}
|
|
42
|
+
</MenuItem>
|
|
43
|
+
))}
|
|
44
|
+
</ThemeSelect>
|
|
45
|
+
</Stack>
|
|
46
|
+
|
|
47
|
+
{/* TCP selection */}
|
|
48
|
+
<Stack width="33%">
|
|
49
|
+
<InputLabel id="jogging-tcp-select">TCP</InputLabel>
|
|
50
|
+
<ThemeSelect
|
|
51
|
+
kind="filled"
|
|
52
|
+
labelId="jogging-tcp-select"
|
|
53
|
+
value={store.selectedTcpId}
|
|
54
|
+
onChange={(event) => {
|
|
55
|
+
store.setSelectedTcpId(event.target.value as string)
|
|
56
|
+
}}
|
|
57
|
+
disabled={store.isLocked}
|
|
58
|
+
>
|
|
59
|
+
{store.tcps.map((tcp) => (
|
|
60
|
+
<MenuItem key={tcp.id} value={tcp.id}>
|
|
61
|
+
{tcp.id}
|
|
62
|
+
</MenuItem>
|
|
63
|
+
))}
|
|
64
|
+
</ThemeSelect>
|
|
65
|
+
</Stack>
|
|
66
|
+
|
|
67
|
+
{/* Increment selection */}
|
|
68
|
+
<Stack width="33%">
|
|
69
|
+
<InputLabel id="jogging-increment-select">{"Increment"}</InputLabel>
|
|
70
|
+
<ThemeSelect
|
|
71
|
+
kind="filled"
|
|
72
|
+
labelId="jogging-increment-select"
|
|
73
|
+
value={store.selectedIncrementId}
|
|
74
|
+
onChange={(event) => {
|
|
75
|
+
store.setSelectedIncrementId(
|
|
76
|
+
event.target.value as IncrementOptionId,
|
|
77
|
+
)
|
|
78
|
+
}}
|
|
79
|
+
disabled={store.isLocked}
|
|
80
|
+
>
|
|
81
|
+
<MenuItem key="continuous" value="continuous">
|
|
82
|
+
{t("Jogging.Increment.Continuous.dd")}
|
|
83
|
+
</MenuItem>
|
|
84
|
+
|
|
85
|
+
{store.discreteIncrementOptions.map((inc) => (
|
|
86
|
+
<MenuItem key={inc.id} value={inc.id}>
|
|
87
|
+
{store.currentMotionType === "translate"
|
|
88
|
+
? `${inc.mm}mm`
|
|
89
|
+
: `${inc.degrees}°`}
|
|
90
|
+
</MenuItem>
|
|
91
|
+
))}
|
|
92
|
+
</ThemeSelect>
|
|
93
|
+
</Stack>
|
|
94
|
+
</Stack>
|
|
95
|
+
)
|
|
96
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { JoggingPanel } from "./JoggingPanel";
|
|
3
|
+
import { useArgs } from "@storybook/preview-api";
|
|
4
|
+
import { NovaClient } from "@wandelbots/wandelbots-js";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof JoggingPanel> = {
|
|
7
|
+
component: JoggingPanel,
|
|
8
|
+
|
|
9
|
+
args: {
|
|
10
|
+
motionGroupId: "0@mock-ur5e"
|
|
11
|
+
},
|
|
12
|
+
render: function Component(args) {
|
|
13
|
+
const [, setArgs] = useArgs();
|
|
14
|
+
return <JoggingPanel
|
|
15
|
+
{...args}
|
|
16
|
+
nova={new NovaClient({
|
|
17
|
+
instanceUrl: "https://mock",
|
|
18
|
+
mock: true
|
|
19
|
+
})}
|
|
20
|
+
/>;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
export const Default: StoryObj<typeof JoggingPanel> = {
|
|
26
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Paper, Stack, Tab, Tabs } from "@mui/material"
|
|
2
|
+
import { observer, useLocalObservable } from "mobx-react-lite"
|
|
3
|
+
import { useEffect } from "react"
|
|
4
|
+
import { JoggingCartesianTab } from "./JoggingCartesianTab"
|
|
5
|
+
import { JoggingJointTab } from "./JoggingJointTab"
|
|
6
|
+
import { JoggingStore } from "./JoggingStore"
|
|
7
|
+
import { LoadingCover } from "../LoadingCover"
|
|
8
|
+
import { runInAction } from "mobx"
|
|
9
|
+
import { NovaClient } from "@wandelbots/wandelbots-js"
|
|
10
|
+
|
|
11
|
+
export type JoggingPanelProps = {
|
|
12
|
+
nova: NovaClient
|
|
13
|
+
motionGroupId: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const JoggingPanel = observer((props: JoggingPanelProps) => {
|
|
17
|
+
const { nova } = props
|
|
18
|
+
|
|
19
|
+
const state = useLocalObservable(() => ({
|
|
20
|
+
joggingStore: null as JoggingStore | null,
|
|
21
|
+
loadingError: null as unknown | null,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
async function init() {
|
|
25
|
+
try {
|
|
26
|
+
const jogger = await nova.connectJogger(props.motionGroupId)
|
|
27
|
+
const joggingStore = await JoggingStore.loadFor(jogger)
|
|
28
|
+
runInAction(() => {
|
|
29
|
+
state.joggingStore = joggingStore
|
|
30
|
+
})
|
|
31
|
+
} catch (err) {
|
|
32
|
+
state.loadingError = err
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
init()
|
|
38
|
+
return () => {
|
|
39
|
+
state.joggingStore?.dispose()
|
|
40
|
+
}
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
// Set correct jogging mode on jogger based on user selections
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!state.joggingStore) return
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
currentTab,
|
|
49
|
+
selectedTcpId,
|
|
50
|
+
selectedCoordSystemId,
|
|
51
|
+
selectedDiscreteIncrement,
|
|
52
|
+
} = state.joggingStore
|
|
53
|
+
|
|
54
|
+
if (currentTab.id !== "cartesian" && currentTab.id !== "joint") return
|
|
55
|
+
|
|
56
|
+
const cartesianJoggingOpts = {
|
|
57
|
+
tcpId: selectedTcpId,
|
|
58
|
+
coordSystemId: selectedCoordSystemId,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (selectedDiscreteIncrement && currentTab.id === "cartesian") {
|
|
62
|
+
state.joggingStore.jogger.setJoggingMode("increment", cartesianJoggingOpts)
|
|
63
|
+
} else {
|
|
64
|
+
state.joggingStore.jogger.setJoggingMode(currentTab.id, cartesianJoggingOpts)
|
|
65
|
+
}
|
|
66
|
+
}, [
|
|
67
|
+
state.joggingStore?.currentTab,
|
|
68
|
+
state.joggingStore?.selectedTcpId,
|
|
69
|
+
state.joggingStore?.selectedCoordSystemId,
|
|
70
|
+
state.joggingStore?.selectedDiscreteIncrement,
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
|
|
75
|
+
// Set the robot to default control mode (JoZi says is important for physical robot jogging)
|
|
76
|
+
async function init() {
|
|
77
|
+
if (!state.joggingStore) return
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await nova.api.controller.setDefaultMode(
|
|
81
|
+
state.joggingStore.jogger.motionStream.controllerId,
|
|
82
|
+
"MODE_CONTROL",
|
|
83
|
+
)
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(err)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
init()
|
|
90
|
+
}, [state.joggingStore?.jogger.motionStream.controllerId])
|
|
91
|
+
|
|
92
|
+
if (!state.joggingStore) {
|
|
93
|
+
return (
|
|
94
|
+
<JoggingPanelOuter>
|
|
95
|
+
<LoadingCover message="Loading jogging" error={state.loadingError} />
|
|
96
|
+
</JoggingPanelOuter>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { joggingStore: store } = state
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<JoggingPanelOuter>
|
|
104
|
+
<Stack flexGrow={1}>
|
|
105
|
+
{/* Tab selection */}
|
|
106
|
+
<Tabs value={store.tabIndex} onChange={store.onTabChange}>
|
|
107
|
+
{store.tabs.map((tab) => (
|
|
108
|
+
<Tab
|
|
109
|
+
key={tab.id}
|
|
110
|
+
label={tab.label}
|
|
111
|
+
id={`jogging-tab-${tab.id}`}
|
|
112
|
+
aria-controls={`jogging-tab-${tab.id}`}
|
|
113
|
+
/>
|
|
114
|
+
))}
|
|
115
|
+
</Tabs>
|
|
116
|
+
|
|
117
|
+
{/* Current tab content */}
|
|
118
|
+
<Stack flexGrow={1}>
|
|
119
|
+
{store.currentTab.id === "cartesian" && (
|
|
120
|
+
<JoggingCartesianTab store={store} />
|
|
121
|
+
)}
|
|
122
|
+
{store.currentTab.id === "joint" && (
|
|
123
|
+
<JoggingJointTab store={store} />
|
|
124
|
+
)}
|
|
125
|
+
</Stack>
|
|
126
|
+
</Stack>
|
|
127
|
+
</JoggingPanelOuter>
|
|
128
|
+
)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
function JoggingPanelOuter({ children }: { children: React.ReactNode }) {
|
|
132
|
+
return (
|
|
133
|
+
<Stack
|
|
134
|
+
sx={{
|
|
135
|
+
maxWidth: "460px",
|
|
136
|
+
minWidth: "350px",
|
|
137
|
+
overflowY: "auto",
|
|
138
|
+
position: "relative",
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
<Paper sx={{
|
|
142
|
+
minHeight: "90vh"
|
|
143
|
+
}}>
|
|
144
|
+
{children}
|
|
145
|
+
</Paper>
|
|
146
|
+
</Stack>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { keyBy } from "lodash-es"
|
|
2
|
+
import { autorun, makeAutoObservable, type IReactionDisposer } from "mobx"
|
|
3
|
+
import type { CoordinateSystem, JoggerConnection, MotionGroupSpecification, RobotTcp } from "@wandelbots/wandelbots-js"
|
|
4
|
+
import { tryParseJson } from "@wandelbots/wandelbots-js"
|
|
5
|
+
|
|
6
|
+
const discreteIncrementOptions = [
|
|
7
|
+
{ id: "0.1", mm: 0.1, degrees: 0.05 },
|
|
8
|
+
{ id: "1", mm: 1, degrees: 0.5 },
|
|
9
|
+
{ id: "5", mm: 5, degrees: 2.5 },
|
|
10
|
+
{ id: "10", mm: 10, degrees: 5 },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const incrementOptions = [
|
|
14
|
+
{ id: "continuous" },
|
|
15
|
+
...discreteIncrementOptions,
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
export type DiscreteIncrementOption = (typeof discreteIncrementOptions)[number]
|
|
19
|
+
export type IncrementOption = (typeof incrementOptions)[number]
|
|
20
|
+
export type IncrementOptionId = IncrementOption["id"]
|
|
21
|
+
|
|
22
|
+
export class JoggingStore {
|
|
23
|
+
selectedTabId: "cartesian" | "joint" | "debug" = "cartesian"
|
|
24
|
+
|
|
25
|
+
// TODO
|
|
26
|
+
isLocked: boolean = false
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Id of selected coordinate system from among those defined on the API side
|
|
30
|
+
*/
|
|
31
|
+
selectedCoordSystemId: string = "world"
|
|
32
|
+
|
|
33
|
+
/** Id of selected tool center point from among the options available on the robot */
|
|
34
|
+
selectedTcpId: string = ""
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Id of selected increment amount for jogging. Options are defined by robot pad.
|
|
38
|
+
* When non-continuous, jogging moves the robot by a fixed number of mm or degrees
|
|
39
|
+
* each time the button is pressed, for extra precision
|
|
40
|
+
*/
|
|
41
|
+
selectedIncrementId: IncrementOptionId = "continuous"
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* When on the cartesian tab, jogging can be either translating or rotating
|
|
45
|
+
* around the TCP.
|
|
46
|
+
*/
|
|
47
|
+
selectedCartesianMotionType: "translate" | "rotate" = "translate"
|
|
48
|
+
|
|
49
|
+
/** True when the API is busy doing a planned increment jog motion */
|
|
50
|
+
incrementJoggingInProgress = false
|
|
51
|
+
|
|
52
|
+
/** How fast the robot goes when doing cartesian translate jogging in mm/s */
|
|
53
|
+
translationVelocityMmPerSec: number = 10
|
|
54
|
+
/** How fast the robot goes when doing cartesian or joint rotation jogging in °/s */
|
|
55
|
+
rotationVelocityDegPerSec: number = 1
|
|
56
|
+
|
|
57
|
+
/** Minimum translation velocity user can choose on the velocity slider in °/s */
|
|
58
|
+
minTranslationVelocityMmPerSec: number = 5
|
|
59
|
+
/** Maximum translation velocity user can choose on the velocity slider in °/s */
|
|
60
|
+
maxTranslationVelocityMmPerSec: number = 250
|
|
61
|
+
|
|
62
|
+
/** Minimum rotation velocity user can choose on the velocity slider in °/s */
|
|
63
|
+
minRotationVelocityDegPerSec: number = 1
|
|
64
|
+
/** Maximum rotation velocity user can choose on the velocity slider in °/s */
|
|
65
|
+
maxRotationVelocityDegPerSec: number = 60
|
|
66
|
+
|
|
67
|
+
disposers: IReactionDisposer[] = []
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load a jogging store with the relevant data it needs
|
|
71
|
+
* from the backend
|
|
72
|
+
*/
|
|
73
|
+
static async loadFor(jogger: JoggerConnection) {
|
|
74
|
+
const { nova } = jogger
|
|
75
|
+
|
|
76
|
+
// Find out what TCPs this motion group has (we need it for jogging)
|
|
77
|
+
const [motionGroupSpec, { coordinatesystems }, { tcps }] = await Promise.all([
|
|
78
|
+
nova.api.motionGroupInfos.getMotionGroupSpecification(jogger.motionGroupId),
|
|
79
|
+
|
|
80
|
+
// Fetch coord systems so user can select between them
|
|
81
|
+
nova.api.coordinateSystems.listCoordinateSystems("ROTATION_VECTOR"),
|
|
82
|
+
|
|
83
|
+
// Same for TCPs
|
|
84
|
+
nova.api.motionGroupInfos.listTcps(
|
|
85
|
+
jogger.motionGroupId,
|
|
86
|
+
"ROTATION_VECTOR",
|
|
87
|
+
),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
return new JoggingStore(jogger, motionGroupSpec, coordinatesystems || [], tcps || [])
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
constructor(
|
|
94
|
+
readonly jogger: JoggerConnection,
|
|
95
|
+
readonly motionGroupSpec: MotionGroupSpecification,
|
|
96
|
+
readonly coordSystems: CoordinateSystem[],
|
|
97
|
+
readonly tcps: RobotTcp[],
|
|
98
|
+
) {
|
|
99
|
+
// TODO workaround for default coord system on backend having a canonical id
|
|
100
|
+
// of empty string. Can remove when fixed on API side
|
|
101
|
+
for (const cs of coordSystems) {
|
|
102
|
+
if (cs.coordinate_system === "") {
|
|
103
|
+
cs.coordinate_system = "world"
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.selectedCoordSystemId = coordSystems[0]?.coordinate_system || "world"
|
|
108
|
+
this.selectedTcpId = tcps[0]?.id || ""
|
|
109
|
+
|
|
110
|
+
makeAutoObservable(this, {}, { autoBind: true })
|
|
111
|
+
|
|
112
|
+
// Load user settings from local storage if available
|
|
113
|
+
this.loadFromLocalStorage()
|
|
114
|
+
|
|
115
|
+
// Automatically save user settings to local storage when save changes
|
|
116
|
+
this.disposers.push(autorun(() => this.saveToLocalStorage()))
|
|
117
|
+
;(window as any).joggingStore = this
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
dispose() {
|
|
121
|
+
for (const dispose of this.disposers) {
|
|
122
|
+
dispose()
|
|
123
|
+
}
|
|
124
|
+
this.jogger.dispose()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
loadFromLocalStorage() {
|
|
128
|
+
const save = tryParseJson(localStorage.getItem("joggingToolStore"))
|
|
129
|
+
if (!save) return
|
|
130
|
+
|
|
131
|
+
if (this.tabsById[save.selectedTabId]) {
|
|
132
|
+
this.selectedTabId = save.selectedTabId
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.coordSystemsById[save.selectedCoordSystemId]) {
|
|
136
|
+
this.selectedCoordSystemId = save.selectedCoordSystemId
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.tcpsById[save.selectedTcpId]) {
|
|
140
|
+
this.selectedTcpId = save.selectedTcpId
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (this.incrementOptionsById[save.selectedIncrementId]) {
|
|
144
|
+
this.selectedIncrementId = save.selectedIncrementId
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (["translate", "rotate"].includes(save.selectedCartesianMotionType)) {
|
|
148
|
+
this.selectedCartesianMotionType = save.selectedCartesianMotionType
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
saveToLocalStorage() {
|
|
153
|
+
localStorage.setItem(
|
|
154
|
+
"joggingToolStore",
|
|
155
|
+
JSON.stringify(this.localStorageSave),
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get localStorageSave() {
|
|
160
|
+
return {
|
|
161
|
+
selectedTabId: this.selectedTabId,
|
|
162
|
+
selectedCoordSystemId: this.selectedCoordSystemId,
|
|
163
|
+
selectedTcpId: this.selectedTcpId,
|
|
164
|
+
selectedIncrementId: this.selectedIncrementId,
|
|
165
|
+
selectedCartesianMotionType: this.selectedCartesianMotionType,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get tabs() {
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
id: "cartesian",
|
|
173
|
+
label: "Cartesian",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "joint",
|
|
177
|
+
label: "Joint",
|
|
178
|
+
},
|
|
179
|
+
] as const
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get incrementOptions() {
|
|
183
|
+
return incrementOptions
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get discreteIncrementOptions() {
|
|
187
|
+
return discreteIncrementOptions
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
get incrementOptionsById() {
|
|
191
|
+
return keyBy(this.incrementOptions, (o) => o.id)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get tabsById() {
|
|
195
|
+
return keyBy(this.tabs, (t) => t.id)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get currentTab() {
|
|
199
|
+
return this.tabsById[this.selectedTabId] || this.tabs[0]!
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get tabIndex() {
|
|
203
|
+
return this.tabs.indexOf(this.currentTab)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get coordSystemsById() {
|
|
207
|
+
return keyBy(this.coordSystems, (cs) => cs.coordinate_system)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get selectedCoordSystem() {
|
|
211
|
+
return this.coordSystemsById[this.selectedCoordSystemId]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get tcpsById() {
|
|
215
|
+
return keyBy(this.tcps, (tcp) => tcp.id)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get selectedDiscreteIncrement() {
|
|
219
|
+
return discreteIncrementOptions.find(
|
|
220
|
+
(d) => d.id === this.selectedIncrementId,
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The selected rotation velocity converted to radians per second */
|
|
225
|
+
get rotationVelocityRadsPerSec() {
|
|
226
|
+
return (this.rotationVelocityDegPerSec * Math.PI) / 180
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get velocityInCurrentUnits() {
|
|
230
|
+
return this.currentMotionType === "translate"
|
|
231
|
+
? this.translationVelocityMmPerSec
|
|
232
|
+
: this.rotationVelocityDegPerSec
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get minVelocityInCurrentUnits() {
|
|
236
|
+
return this.currentMotionType === "translate"
|
|
237
|
+
? this.minTranslationVelocityMmPerSec
|
|
238
|
+
: this.minRotationVelocityDegPerSec
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get maxVelocityInCurrentUnits() {
|
|
242
|
+
return this.currentMotionType === "translate"
|
|
243
|
+
? this.maxTranslationVelocityMmPerSec
|
|
244
|
+
: this.maxRotationVelocityDegPerSec
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* For velocity unit purposes, joint and cartesian rotation
|
|
249
|
+
* are treated as the same type of motion
|
|
250
|
+
*/
|
|
251
|
+
get currentMotionType() {
|
|
252
|
+
if (
|
|
253
|
+
this.selectedTabId === "cartesian" &&
|
|
254
|
+
this.selectedCartesianMotionType === "translate"
|
|
255
|
+
) {
|
|
256
|
+
return "translate"
|
|
257
|
+
} else {
|
|
258
|
+
return "rotate"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
onTabChange(_event: React.SyntheticEvent, newValue: number) {
|
|
263
|
+
const tab = this.tabs[newValue] || this.tabs[0]!
|
|
264
|
+
this.selectedTabId = tab.id
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
setSelectedCoordSystemId(id: string) {
|
|
268
|
+
this.selectedCoordSystemId = id
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
setSelectedTcpId(id: string) {
|
|
272
|
+
this.selectedTcpId = id
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
setSelectedIncrementId(id: IncrementOptionId) {
|
|
276
|
+
this.selectedIncrementId = id
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setIncrementJoggingInProgress(inProgress: boolean) {
|
|
280
|
+
this.incrementJoggingInProgress = inProgress
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
setVelocityFromSlider(velocity: number) {
|
|
284
|
+
if (this.currentMotionType === "translate") {
|
|
285
|
+
this.translationVelocityMmPerSec = velocity
|
|
286
|
+
} else {
|
|
287
|
+
this.rotationVelocityDegPerSec = velocity
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
setSelectedCartesianMotionType(type: "translate" | "rotate") {
|
|
292
|
+
this.selectedCartesianMotionType = type
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Stack, Divider, Typography } from "@mui/material"
|
|
2
|
+
import { observer, useLocalObservable } from "mobx-react-lite"
|
|
3
|
+
import type { JoggingStore } from "./JoggingStore"
|
|
4
|
+
import { VelocitySlider } from "../VelocitySlider"
|
|
5
|
+
import { useTranslation } from "react-i18next"
|
|
6
|
+
|
|
7
|
+
export const JoggingVelocitySlider = observer(
|
|
8
|
+
({ store }: { store: JoggingStore }) => {
|
|
9
|
+
const { t } = useTranslation()
|
|
10
|
+
|
|
11
|
+
const state = useLocalObservable(() => ({
|
|
12
|
+
get valueLabelFormat() {
|
|
13
|
+
if (store.currentMotionType === "translate") {
|
|
14
|
+
return (value: number) =>
|
|
15
|
+
`v=${t("Jogging.Cartesian.Translation.velocityMmPerSec.lb", { amount: value })}`
|
|
16
|
+
} else {
|
|
17
|
+
return (value: number) =>
|
|
18
|
+
`v=${t("Jogging.Cartesian.Rotation.velocityDegPerSec.lb", { amount: value })}`
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Stack>
|
|
25
|
+
<Divider />
|
|
26
|
+
<Stack
|
|
27
|
+
sx={{
|
|
28
|
+
margin: "0px 20px",
|
|
29
|
+
marginTop: "0.8rem",
|
|
30
|
+
marginBottom: "0.8rem",
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Stack sx={{ width: "380px", maxWidth: "90%", margin: "auto" }}>
|
|
34
|
+
<Typography
|
|
35
|
+
sx={{
|
|
36
|
+
fontWeight: "bold",
|
|
37
|
+
fontSize: "15px",
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{t("Jogging.Velocity.lb")}
|
|
41
|
+
</Typography>
|
|
42
|
+
<VelocitySlider
|
|
43
|
+
velocity={store.velocityInCurrentUnits}
|
|
44
|
+
min={store.minVelocityInCurrentUnits}
|
|
45
|
+
max={store.maxVelocityInCurrentUnits}
|
|
46
|
+
onVelocityChange={store.setVelocityFromSlider}
|
|
47
|
+
disabled={store.isLocked}
|
|
48
|
+
valueLabelFormat={state.valueLabelFormat}
|
|
49
|
+
/>
|
|
50
|
+
</Stack>
|
|
51
|
+
</Stack>
|
|
52
|
+
<Divider />
|
|
53
|
+
</Stack>
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
)
|