@wandelbots/wandelbots-js-react-components 1.3.1 → 1.3.2

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.
Files changed (77) hide show
  1. package/package.json +18 -7
  2. package/src/components/3d-viewport/CoordinateSystemTransform.tsx +44 -0
  3. package/src/components/3d-viewport/PresetEnvironment.tsx +78 -0
  4. package/src/components/3d-viewport/SafetyZonesRenderer.tsx +54 -0
  5. package/src/components/LoadingButton.stories.tsx +61 -0
  6. package/src/components/LoadingButton.tsx +19 -0
  7. package/src/components/LoadingCover.tsx +75 -0
  8. package/src/components/ThemeSelect.tsx +49 -0
  9. package/src/components/VelocitySlider.stories.tsx +32 -0
  10. package/src/components/VelocitySlider.tsx +52 -0
  11. package/src/components/jogging/JoggingCartesianAxisControl.stories.tsx +41 -0
  12. package/src/components/jogging/JoggingCartesianAxisControl.tsx +127 -0
  13. package/src/components/jogging/JoggingCartesianTab.tsx +265 -0
  14. package/src/components/jogging/JoggingCartesianValues.tsx +45 -0
  15. package/src/components/jogging/JoggingFreedriveTab.tsx +9 -0
  16. package/src/components/jogging/JoggingJointLimitDetector.tsx +51 -0
  17. package/src/components/jogging/JoggingJointRotationControl.stories.tsx +38 -0
  18. package/src/components/jogging/JoggingJointRotationControl.tsx +197 -0
  19. package/src/components/jogging/JoggingJointTab.tsx +93 -0
  20. package/src/components/jogging/JoggingJointValues.tsx +45 -0
  21. package/src/components/jogging/JoggingOptions.tsx +96 -0
  22. package/src/components/jogging/JoggingPanel.stories.tsx +26 -0
  23. package/src/components/jogging/JoggingPanel.tsx +148 -0
  24. package/src/components/jogging/JoggingStore.tsx +294 -0
  25. package/src/components/jogging/JoggingVelocitySlider.tsx +56 -0
  26. package/src/components/robots/ABB_1200_07_7.tsx +123 -0
  27. package/src/components/robots/AxisConfig.ts +3 -0
  28. package/src/components/robots/DHRobot.tsx +129 -0
  29. package/src/components/robots/FANUC_ARC_Mate_100iD.tsx +187 -0
  30. package/src/components/robots/FANUC_ARC_Mate_120iD.tsx +187 -0
  31. package/src/components/robots/FANUC_CRX10iA.tsx +167 -0
  32. package/src/components/robots/FANUC_CRX25iA.tsx +167 -0
  33. package/src/components/robots/FANUC_CRX25iAL.tsx +178 -0
  34. package/src/components/robots/KUKA_KR210_R2700.tsx +291 -0
  35. package/src/components/robots/KUKA_KR270_R2700.tsx +244 -0
  36. package/src/components/robots/RobotAnimator.tsx +83 -0
  37. package/src/components/robots/SupportedRobot.tsx +131 -0
  38. package/src/components/robots/UniversalRobots_UR10.tsx +112 -0
  39. package/src/components/robots/UniversalRobots_UR10e.tsx +275 -0
  40. package/src/components/robots/UniversalRobots_UR3.tsx +112 -0
  41. package/src/components/robots/UniversalRobots_UR3e.tsx +112 -0
  42. package/src/components/robots/UniversalRobots_UR5.tsx +111 -0
  43. package/src/components/robots/UniversalRobots_UR5e.tsx +280 -0
  44. package/src/components/robots/Yaskawa_AR1440.tsx +152 -0
  45. package/src/components/robots/Yaskawa_AR1730.tsx +165 -0
  46. package/src/components/robots/Yaskawa_AR2010.tsx +159 -0
  47. package/src/components/robots/Yaskawa_AR3120.tsx +160 -0
  48. package/src/components/robots/Yaskawa_AR900.tsx +121 -0
  49. package/src/components/utils/converters.ts +23 -0
  50. package/src/components/utils/errorHandling.ts +30 -0
  51. package/src/components/utils/hooks.tsx +54 -0
  52. package/src/components/utils/robotTreeQuery.ts +27 -0
  53. package/src/components/wandelscript-editor/WandelscriptEditor.stories.tsx +45 -0
  54. package/src/components/wandelscript-editor/WandelscriptEditor.tsx +114 -0
  55. package/src/components/wandelscript-editor/wandelscript.tmLanguage.ts +62 -0
  56. package/src/declarations.d.ts +10 -0
  57. package/src/i18n/config.ts +27 -0
  58. package/src/i18n/locales/de/translations.json +12 -0
  59. package/src/i18n/locales/en/translations.json +12 -0
  60. package/src/icons/arrowForwardFilled.tsx +7 -0
  61. package/src/icons/axis-x.svg +3 -0
  62. package/src/icons/axis-y.svg +3 -0
  63. package/src/icons/axis-z.svg +3 -0
  64. package/src/icons/expandFilled.tsx +11 -0
  65. package/src/icons/home.tsx +12 -0
  66. package/src/icons/index.ts +6 -0
  67. package/src/icons/infoOutlined.tsx +10 -0
  68. package/src/icons/jogging.svg +3 -0
  69. package/src/icons/robot.svg +3 -0
  70. package/src/icons/robot.tsx +14 -0
  71. package/src/icons/rotation.svg +4 -0
  72. package/src/icons/wbLogo.tsx +21 -0
  73. package/src/index.ts +7 -0
  74. package/src/themes/color.tsx +74 -0
  75. package/src/themes/theme.ts +150 -0
  76. package/src/themes/wbTheme.stories.tsx +64 -0
  77. 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
+ )