@wandelbots/wandelbots-js-react-components 2.32.0-pr.feature-robot-precondition-list.372.297bf4f → 2.32.0-pr.feature-robot-precondition-list.372.5d8a86e
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/CycleTimer.d.ts.map +1 -1
- package/dist/components/ProgramControl.d.ts +6 -1
- package/dist/components/ProgramControl.d.ts.map +1 -1
- package/dist/components/ProgramStateIndicator.d.ts +1 -1
- package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
- package/dist/components/RobotCard.d.ts +73 -0
- package/dist/components/RobotCard.d.ts.map +1 -0
- package/dist/components/RobotSetupReadinessIndicator.d.ts.map +1 -1
- package/dist/components/robots/Robot.d.ts +3 -2
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/index.cjs +48 -48
- 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 +6840 -6442
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CycleTimer.tsx +43 -64
- package/src/components/ProgramControl.tsx +20 -10
- package/src/components/ProgramStateIndicator.tsx +20 -8
- package/src/components/RobotCard.tsx +568 -0
- package/src/components/RobotSetupReadinessIndicator.tsx +2 -0
- package/src/components/robots/Robot.tsx +5 -2
- package/src/i18n/locales/de/translations.json +3 -1
- package/src/i18n/locales/en/translations.json +3 -1
- package/src/index.ts +1 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
Divider,
|
|
6
|
+
Typography,
|
|
7
|
+
useMediaQuery,
|
|
8
|
+
useTheme,
|
|
9
|
+
} from "@mui/material"
|
|
10
|
+
import { Bounds, useBounds } from "@react-three/drei"
|
|
11
|
+
import { Canvas } from "@react-three/fiber"
|
|
12
|
+
import type {
|
|
13
|
+
ConnectedMotionGroup,
|
|
14
|
+
RobotControllerStateOperationModeEnum,
|
|
15
|
+
RobotControllerStateSafetyStateEnum,
|
|
16
|
+
} from "@wandelbots/nova-js/v1"
|
|
17
|
+
import { observer } from "mobx-react-lite"
|
|
18
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
19
|
+
import { useTranslation } from "react-i18next"
|
|
20
|
+
import type { Group } from "three"
|
|
21
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
22
|
+
import { PresetEnvironment } from "./3d-viewport/PresetEnvironment"
|
|
23
|
+
import { CycleTimer } from "./CycleTimer"
|
|
24
|
+
import type { ProgramState } from "./ProgramControl"
|
|
25
|
+
import { ProgramStateIndicator } from "./ProgramStateIndicator"
|
|
26
|
+
import { Robot } from "./robots/Robot"
|
|
27
|
+
|
|
28
|
+
// Component to refresh bounds when model renders
|
|
29
|
+
function BoundsRefresher({
|
|
30
|
+
modelRenderTrigger,
|
|
31
|
+
children,
|
|
32
|
+
isPortrait = false,
|
|
33
|
+
}: {
|
|
34
|
+
modelRenderTrigger: number
|
|
35
|
+
children: React.ReactNode
|
|
36
|
+
isPortrait?: boolean
|
|
37
|
+
}) {
|
|
38
|
+
const bounds = useBounds()
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (modelRenderTrigger > 0) {
|
|
42
|
+
// For portrait mode, use more aggressive fitting
|
|
43
|
+
if (isPortrait) {
|
|
44
|
+
bounds.refresh().clip().fit()
|
|
45
|
+
} else {
|
|
46
|
+
bounds.refresh().clip().fit()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, [modelRenderTrigger, bounds, isPortrait])
|
|
50
|
+
|
|
51
|
+
return <>{children}</>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RobotCardProps {
|
|
55
|
+
/** Name of the robot displayed at the top */
|
|
56
|
+
robotName: string
|
|
57
|
+
/** Current program state */
|
|
58
|
+
programState: ProgramState
|
|
59
|
+
/** Current safety state of the robot controller */
|
|
60
|
+
safetyState: RobotControllerStateSafetyStateEnum
|
|
61
|
+
/** Current operation mode of the robot controller */
|
|
62
|
+
operationMode: RobotControllerStateOperationModeEnum
|
|
63
|
+
/** Whether the "Drive to Home" button should be enabled */
|
|
64
|
+
driveToHomeEnabled?: boolean
|
|
65
|
+
/** Callback fired when "Drive to Home" button is pressed */
|
|
66
|
+
onDriveToHomePress?: () => void
|
|
67
|
+
/** Callback fired when "Drive to Home" button is released */
|
|
68
|
+
onDriveToHomeRelease?: () => void
|
|
69
|
+
/** Connected motion group for the robot */
|
|
70
|
+
connectedMotionGroup: ConnectedMotionGroup
|
|
71
|
+
/** Custom robot component to render (optional, defaults to Robot) */
|
|
72
|
+
robotComponent?: React.ComponentType<{
|
|
73
|
+
connectedMotionGroup: ConnectedMotionGroup
|
|
74
|
+
flangeRef?: React.Ref<Group>
|
|
75
|
+
postModelRender?: () => void
|
|
76
|
+
transparentColor?: string
|
|
77
|
+
getModel?: (modelFromController: string) => string
|
|
78
|
+
}>
|
|
79
|
+
/** Custom cycle timer component (optional, defaults to CycleTimer) */
|
|
80
|
+
cycleTimerComponent?: React.ComponentType<{
|
|
81
|
+
variant?: "default" | "small"
|
|
82
|
+
compact?: boolean
|
|
83
|
+
onCycleComplete: (controls: {
|
|
84
|
+
startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
85
|
+
pause: () => void
|
|
86
|
+
resume: () => void
|
|
87
|
+
isPaused: () => boolean
|
|
88
|
+
}) => void
|
|
89
|
+
onCycleEnd?: () => void
|
|
90
|
+
autoStart?: boolean
|
|
91
|
+
className?: string
|
|
92
|
+
}>
|
|
93
|
+
/** Additional CSS class name */
|
|
94
|
+
className?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A responsive card component that displays a 3D robot with states and controls.
|
|
99
|
+
* The card automatically adapts to its container's size and aspect ratio.
|
|
100
|
+
*
|
|
101
|
+
* Features:
|
|
102
|
+
* - Fully responsive Material-UI Card that adapts to container dimensions
|
|
103
|
+
* - Automatic layout switching based on aspect ratio:
|
|
104
|
+
* - Portrait mode: Vertical layout with robot in center
|
|
105
|
+
* - Landscape mode: Horizontal layout with robot on left, content on right (left-aligned)
|
|
106
|
+
* - Responsive 3D robot rendering:
|
|
107
|
+
* - Scales dynamically with container size
|
|
108
|
+
* - Hides at very small sizes to preserve usability
|
|
109
|
+
* - Adaptive margin based on available space
|
|
110
|
+
* - Smart spacing and padding that reduces at smaller sizes
|
|
111
|
+
* - Minimum size constraints for usability while maximizing content density
|
|
112
|
+
* - Robot name displayed in Typography h6 at top-left
|
|
113
|
+
* - Program state indicator below the name
|
|
114
|
+
* - Auto-fitting 3D robot model that scales with container size
|
|
115
|
+
* - Compact cycle time component with small variant
|
|
116
|
+
* - Transparent gray divider line
|
|
117
|
+
* - "Drive to Home" button with press-and-hold functionality
|
|
118
|
+
* - Localization support via react-i18next
|
|
119
|
+
* - Material-UI theming integration
|
|
120
|
+
*/
|
|
121
|
+
export const RobotCard = externalizeComponent(
|
|
122
|
+
observer(
|
|
123
|
+
({
|
|
124
|
+
robotName,
|
|
125
|
+
programState,
|
|
126
|
+
safetyState,
|
|
127
|
+
operationMode,
|
|
128
|
+
driveToHomeEnabled = false,
|
|
129
|
+
onDriveToHomePress,
|
|
130
|
+
onDriveToHomeRelease,
|
|
131
|
+
connectedMotionGroup,
|
|
132
|
+
robotComponent: RobotComponent = Robot,
|
|
133
|
+
cycleTimerComponent: CycleTimerComponent = CycleTimer,
|
|
134
|
+
className,
|
|
135
|
+
}: RobotCardProps) => {
|
|
136
|
+
const theme = useTheme()
|
|
137
|
+
const { t } = useTranslation()
|
|
138
|
+
const [isDriveToHomePressed, setIsDriveToHomePressed] = useState(false)
|
|
139
|
+
const driveButtonRef = useRef<HTMLButtonElement>(null)
|
|
140
|
+
const robotRef = useRef<Group>(null)
|
|
141
|
+
const cardRef = useRef<HTMLDivElement>(null)
|
|
142
|
+
const [isLandscape, setIsLandscape] = useState(false)
|
|
143
|
+
const [cardSize, setCardSize] = useState<{
|
|
144
|
+
width: number
|
|
145
|
+
height: number
|
|
146
|
+
}>({ width: 400, height: 600 })
|
|
147
|
+
const [modelRenderTrigger, setModelRenderTrigger] = useState(0)
|
|
148
|
+
|
|
149
|
+
// Responsive breakpoints
|
|
150
|
+
const isExtraSmall = useMediaQuery(theme.breakpoints.down("sm"))
|
|
151
|
+
const isSmall = useMediaQuery(theme.breakpoints.down("md"))
|
|
152
|
+
|
|
153
|
+
// Hook to detect aspect ratio and size changes
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const checkDimensions = () => {
|
|
156
|
+
if (cardRef.current) {
|
|
157
|
+
const { offsetWidth, offsetHeight } = cardRef.current
|
|
158
|
+
setIsLandscape(offsetWidth > offsetHeight)
|
|
159
|
+
setCardSize({ width: offsetWidth, height: offsetHeight })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Initial check
|
|
164
|
+
checkDimensions()
|
|
165
|
+
|
|
166
|
+
// Set up ResizeObserver to watch for size changes
|
|
167
|
+
const resizeObserver = new ResizeObserver(checkDimensions)
|
|
168
|
+
if (cardRef.current) {
|
|
169
|
+
resizeObserver.observe(cardRef.current)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return () => {
|
|
173
|
+
resizeObserver.disconnect()
|
|
174
|
+
}
|
|
175
|
+
}, [])
|
|
176
|
+
|
|
177
|
+
const handleModelRender = useCallback(() => {
|
|
178
|
+
// Trigger bounds refresh when model renders
|
|
179
|
+
setModelRenderTrigger((prev) => prev + 1)
|
|
180
|
+
}, [])
|
|
181
|
+
|
|
182
|
+
const handleDriveToHomeMouseDown = useCallback(() => {
|
|
183
|
+
if (!driveToHomeEnabled || !onDriveToHomePress) return
|
|
184
|
+
setIsDriveToHomePressed(true)
|
|
185
|
+
onDriveToHomePress()
|
|
186
|
+
}, [driveToHomeEnabled, onDriveToHomePress])
|
|
187
|
+
|
|
188
|
+
const handleDriveToHomeMouseUp = useCallback(() => {
|
|
189
|
+
if (!driveToHomeEnabled || !onDriveToHomeRelease) return
|
|
190
|
+
setIsDriveToHomePressed(false)
|
|
191
|
+
onDriveToHomeRelease()
|
|
192
|
+
}, [driveToHomeEnabled, onDriveToHomeRelease])
|
|
193
|
+
|
|
194
|
+
const handleDriveToHomeMouseLeave = useCallback(() => {
|
|
195
|
+
if (isDriveToHomePressed && onDriveToHomeRelease) {
|
|
196
|
+
setIsDriveToHomePressed(false)
|
|
197
|
+
onDriveToHomeRelease()
|
|
198
|
+
}
|
|
199
|
+
}, [isDriveToHomePressed, onDriveToHomeRelease])
|
|
200
|
+
|
|
201
|
+
// Mock cycle timer controls for now
|
|
202
|
+
const handleCycleComplete = useCallback(
|
|
203
|
+
(_controls: {
|
|
204
|
+
startNewCycle: (
|
|
205
|
+
maxTimeSeconds: number,
|
|
206
|
+
elapsedSeconds?: number,
|
|
207
|
+
) => void
|
|
208
|
+
pause: () => void
|
|
209
|
+
resume: () => void
|
|
210
|
+
isPaused: () => boolean
|
|
211
|
+
}) => {
|
|
212
|
+
// TODO: Implement cycle timer integration if needed
|
|
213
|
+
// Controls are available here for future integration
|
|
214
|
+
},
|
|
215
|
+
[],
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// Determine if robot should be hidden at small sizes to save space
|
|
219
|
+
// Less aggressive hiding for portrait mode since robot is in center
|
|
220
|
+
const shouldHideRobot = isLandscape
|
|
221
|
+
? cardSize.width < 350 || cardSize.height < 250 // Aggressive for landscape
|
|
222
|
+
: cardSize.width < 250 || cardSize.height < 180 // Less aggressive for portrait
|
|
223
|
+
|
|
224
|
+
// Calculate responsive robot scale based on card size and orientation
|
|
225
|
+
const getRobotScale = () => {
|
|
226
|
+
if (shouldHideRobot) return 0
|
|
227
|
+
|
|
228
|
+
if (isLandscape) {
|
|
229
|
+
// More aggressive scaling for landscape since robot is on side
|
|
230
|
+
if (cardSize.width < 450) return 0.8
|
|
231
|
+
if (cardSize.width < 550) return 0.9
|
|
232
|
+
return 1
|
|
233
|
+
} else {
|
|
234
|
+
// Less aggressive scaling for portrait since robot is central
|
|
235
|
+
if (cardSize.width < 300) return 0.8
|
|
236
|
+
if (cardSize.width < 400) return 0.9
|
|
237
|
+
return 1
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const robotScale = getRobotScale()
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<Card
|
|
245
|
+
ref={cardRef}
|
|
246
|
+
className={className}
|
|
247
|
+
elevation={5}
|
|
248
|
+
sx={{
|
|
249
|
+
width: "100%",
|
|
250
|
+
height: "100%",
|
|
251
|
+
display: "flex",
|
|
252
|
+
flexDirection: isLandscape ? "row" : "column",
|
|
253
|
+
position: "relative",
|
|
254
|
+
overflow: "hidden",
|
|
255
|
+
minWidth: { xs: 180, sm: 220, md: 250 },
|
|
256
|
+
minHeight: isLandscape
|
|
257
|
+
? { xs: 160, sm: 200, md: 250 }
|
|
258
|
+
: { xs: 200, sm: 280, md: 350 },
|
|
259
|
+
border:
|
|
260
|
+
"1px solid var(--secondary-_states-outlinedBorder, #FFFFFF1F)",
|
|
261
|
+
borderRadius: "18px",
|
|
262
|
+
boxShadow: "none",
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
{isLandscape ? (
|
|
266
|
+
<>
|
|
267
|
+
{/* Landscape Layout: Robot on left, content on right */}
|
|
268
|
+
<Box
|
|
269
|
+
sx={{
|
|
270
|
+
flex: "0 0 50%",
|
|
271
|
+
position: "relative",
|
|
272
|
+
height: "100%",
|
|
273
|
+
minHeight: "100%",
|
|
274
|
+
maxHeight: "100%",
|
|
275
|
+
borderRadius: 1,
|
|
276
|
+
m: { xs: 1.5, sm: 2, md: 3 },
|
|
277
|
+
mr: { xs: 0.75, sm: 1, md: 1.5 },
|
|
278
|
+
overflow: "hidden", // Prevent content from affecting container size
|
|
279
|
+
display: shouldHideRobot ? "none" : "block",
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
{!shouldHideRobot && (
|
|
283
|
+
<Canvas
|
|
284
|
+
camera={{
|
|
285
|
+
position: [4, 4, 4], // Move camera further back for orthographic view
|
|
286
|
+
fov: 15, // Low FOV for near-orthographic projection
|
|
287
|
+
}}
|
|
288
|
+
shadows
|
|
289
|
+
frameloop="demand"
|
|
290
|
+
style={{
|
|
291
|
+
borderRadius: theme.shape.borderRadius,
|
|
292
|
+
width: "100%",
|
|
293
|
+
height: "100%",
|
|
294
|
+
background: "transparent",
|
|
295
|
+
position: "absolute",
|
|
296
|
+
top: 0,
|
|
297
|
+
left: 0,
|
|
298
|
+
}}
|
|
299
|
+
dpr={[1, 2]}
|
|
300
|
+
gl={{ alpha: true, antialias: true }}
|
|
301
|
+
>
|
|
302
|
+
<PresetEnvironment />
|
|
303
|
+
<Bounds fit clip observe margin={1}>
|
|
304
|
+
<BoundsRefresher
|
|
305
|
+
modelRenderTrigger={modelRenderTrigger}
|
|
306
|
+
isPortrait={false}
|
|
307
|
+
>
|
|
308
|
+
<group ref={robotRef} scale={robotScale}>
|
|
309
|
+
<RobotComponent
|
|
310
|
+
connectedMotionGroup={connectedMotionGroup}
|
|
311
|
+
postModelRender={handleModelRender}
|
|
312
|
+
/>
|
|
313
|
+
</group>
|
|
314
|
+
</BoundsRefresher>
|
|
315
|
+
</Bounds>
|
|
316
|
+
</Canvas>
|
|
317
|
+
)}
|
|
318
|
+
</Box>
|
|
319
|
+
|
|
320
|
+
{/* Content container on right */}
|
|
321
|
+
<Box
|
|
322
|
+
sx={{
|
|
323
|
+
flex: shouldHideRobot ? "1" : "1",
|
|
324
|
+
display: "flex",
|
|
325
|
+
flexDirection: "column",
|
|
326
|
+
justifyContent: "flex-start",
|
|
327
|
+
width: shouldHideRobot ? "100%" : "50%",
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
{/* Header section with robot name and program state */}
|
|
331
|
+
<Box
|
|
332
|
+
sx={{
|
|
333
|
+
p: { xs: 1.5, sm: 2, md: 3 },
|
|
334
|
+
pb: { xs: 1, sm: 1.5, md: 2 },
|
|
335
|
+
textAlign: "left",
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
339
|
+
{robotName}
|
|
340
|
+
</Typography>
|
|
341
|
+
<ProgramStateIndicator
|
|
342
|
+
programState={programState}
|
|
343
|
+
safetyState={safetyState}
|
|
344
|
+
operationMode={operationMode}
|
|
345
|
+
/>
|
|
346
|
+
</Box>
|
|
347
|
+
|
|
348
|
+
{/* Bottom section with runtime, cycle time, and button */}
|
|
349
|
+
<Box
|
|
350
|
+
sx={{
|
|
351
|
+
p: { xs: 1.5, sm: 2, md: 3 },
|
|
352
|
+
pt: 0,
|
|
353
|
+
flex: "1",
|
|
354
|
+
display: "flex",
|
|
355
|
+
flexDirection: "column",
|
|
356
|
+
justifyContent: "space-between",
|
|
357
|
+
}}
|
|
358
|
+
>
|
|
359
|
+
<Box>
|
|
360
|
+
{/* Runtime display */}
|
|
361
|
+
<Typography
|
|
362
|
+
variant="body1"
|
|
363
|
+
sx={{
|
|
364
|
+
mb: 0,
|
|
365
|
+
color: "var(--text-secondary, #FFFFFFB2)",
|
|
366
|
+
textAlign: "left",
|
|
367
|
+
}}
|
|
368
|
+
>
|
|
369
|
+
{t("RobotCard.Runtime.lb")}
|
|
370
|
+
</Typography>
|
|
371
|
+
|
|
372
|
+
{/* Compact cycle time component directly below runtime */}
|
|
373
|
+
<Box sx={{ textAlign: "left" }}>
|
|
374
|
+
<CycleTimerComponent
|
|
375
|
+
variant="small"
|
|
376
|
+
compact
|
|
377
|
+
onCycleComplete={handleCycleComplete}
|
|
378
|
+
/>
|
|
379
|
+
</Box>
|
|
380
|
+
|
|
381
|
+
{/* Divider */}
|
|
382
|
+
<Divider
|
|
383
|
+
sx={{
|
|
384
|
+
mt: 1,
|
|
385
|
+
mb: 0,
|
|
386
|
+
borderColor: theme.palette.divider,
|
|
387
|
+
opacity: 0.5,
|
|
388
|
+
}}
|
|
389
|
+
/>
|
|
390
|
+
</Box>
|
|
391
|
+
|
|
392
|
+
<Box sx={{ mt: "auto" }}>
|
|
393
|
+
{/* Drive to Home button with some space */}
|
|
394
|
+
<Box
|
|
395
|
+
sx={{
|
|
396
|
+
display: "flex",
|
|
397
|
+
justifyContent: "flex-start",
|
|
398
|
+
mt: { xs: 1, sm: 1.5, md: 2 },
|
|
399
|
+
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
400
|
+
}}
|
|
401
|
+
>
|
|
402
|
+
<Button
|
|
403
|
+
ref={driveButtonRef}
|
|
404
|
+
variant="contained"
|
|
405
|
+
color="secondary"
|
|
406
|
+
size="small"
|
|
407
|
+
disabled={!driveToHomeEnabled}
|
|
408
|
+
onMouseDown={handleDriveToHomeMouseDown}
|
|
409
|
+
onMouseUp={handleDriveToHomeMouseUp}
|
|
410
|
+
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
411
|
+
onTouchStart={handleDriveToHomeMouseDown}
|
|
412
|
+
onTouchEnd={handleDriveToHomeMouseUp}
|
|
413
|
+
sx={{
|
|
414
|
+
textTransform: "none",
|
|
415
|
+
px: 1.5,
|
|
416
|
+
py: 0.5,
|
|
417
|
+
}}
|
|
418
|
+
>
|
|
419
|
+
{t("RobotCard.DriveToHome.bt")}
|
|
420
|
+
</Button>
|
|
421
|
+
</Box>
|
|
422
|
+
</Box>
|
|
423
|
+
</Box>
|
|
424
|
+
</Box>
|
|
425
|
+
</>
|
|
426
|
+
) : (
|
|
427
|
+
<>
|
|
428
|
+
{/* Portrait Layout: Header, Robot, Footer */}
|
|
429
|
+
<Box
|
|
430
|
+
sx={{
|
|
431
|
+
p: 3,
|
|
432
|
+
height: "100%",
|
|
433
|
+
display: "flex",
|
|
434
|
+
flexDirection: "column",
|
|
435
|
+
}}
|
|
436
|
+
>
|
|
437
|
+
{/* Header section with robot name and program state */}
|
|
438
|
+
<Box>
|
|
439
|
+
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
440
|
+
{robotName}
|
|
441
|
+
</Typography>
|
|
442
|
+
<ProgramStateIndicator
|
|
443
|
+
programState={programState}
|
|
444
|
+
safetyState={safetyState}
|
|
445
|
+
operationMode={operationMode}
|
|
446
|
+
/>
|
|
447
|
+
</Box>
|
|
448
|
+
|
|
449
|
+
{/* 3D Robot viewport in center */}
|
|
450
|
+
<Box
|
|
451
|
+
sx={{
|
|
452
|
+
flex: shouldHideRobot ? 0 : 1,
|
|
453
|
+
position: "relative",
|
|
454
|
+
minHeight: shouldHideRobot
|
|
455
|
+
? 0
|
|
456
|
+
: { xs: 120, sm: 150, md: 200 },
|
|
457
|
+
height: shouldHideRobot ? 0 : "auto",
|
|
458
|
+
borderRadius: 1,
|
|
459
|
+
overflow: "hidden",
|
|
460
|
+
display: shouldHideRobot ? "none" : "block",
|
|
461
|
+
}}
|
|
462
|
+
>
|
|
463
|
+
{!shouldHideRobot && (
|
|
464
|
+
<Canvas
|
|
465
|
+
camera={{
|
|
466
|
+
position: [3, 3, 3], // Closer camera position for portrait to make robot larger
|
|
467
|
+
fov: 20, // Slightly higher FOV for portrait to fill better
|
|
468
|
+
}}
|
|
469
|
+
shadows
|
|
470
|
+
frameloop="demand"
|
|
471
|
+
style={{
|
|
472
|
+
borderRadius: theme.shape.borderRadius,
|
|
473
|
+
width: "100%",
|
|
474
|
+
height: "100%",
|
|
475
|
+
background: "transparent",
|
|
476
|
+
position: "absolute",
|
|
477
|
+
top: 0,
|
|
478
|
+
left: 0,
|
|
479
|
+
}}
|
|
480
|
+
dpr={[1, 2]}
|
|
481
|
+
gl={{ alpha: true, antialias: true }}
|
|
482
|
+
>
|
|
483
|
+
<PresetEnvironment />
|
|
484
|
+
<Bounds fit observe margin={1}>
|
|
485
|
+
<BoundsRefresher
|
|
486
|
+
modelRenderTrigger={modelRenderTrigger}
|
|
487
|
+
isPortrait={true}
|
|
488
|
+
>
|
|
489
|
+
<group ref={robotRef} scale={robotScale}>
|
|
490
|
+
<RobotComponent
|
|
491
|
+
connectedMotionGroup={connectedMotionGroup}
|
|
492
|
+
postModelRender={handleModelRender}
|
|
493
|
+
/>
|
|
494
|
+
</group>
|
|
495
|
+
</BoundsRefresher>
|
|
496
|
+
</Bounds>
|
|
497
|
+
</Canvas>
|
|
498
|
+
)}
|
|
499
|
+
</Box>
|
|
500
|
+
|
|
501
|
+
{/* Bottom section with runtime, cycle time, and button */}
|
|
502
|
+
<Box>
|
|
503
|
+
{/* Runtime display */}
|
|
504
|
+
<Typography
|
|
505
|
+
variant="body1"
|
|
506
|
+
sx={{
|
|
507
|
+
mb: 0,
|
|
508
|
+
color: "var(--text-secondary, #FFFFFFB2)",
|
|
509
|
+
}}
|
|
510
|
+
>
|
|
511
|
+
{t("RobotCard.Runtime.lb")}
|
|
512
|
+
</Typography>
|
|
513
|
+
|
|
514
|
+
{/* Compact cycle time component directly below runtime */}
|
|
515
|
+
<CycleTimerComponent
|
|
516
|
+
variant="small"
|
|
517
|
+
compact
|
|
518
|
+
onCycleComplete={handleCycleComplete}
|
|
519
|
+
/>
|
|
520
|
+
|
|
521
|
+
{/* Divider */}
|
|
522
|
+
<Divider
|
|
523
|
+
sx={{
|
|
524
|
+
mt: 1,
|
|
525
|
+
mb: 0,
|
|
526
|
+
borderColor: theme.palette.divider,
|
|
527
|
+
opacity: 0.5,
|
|
528
|
+
}}
|
|
529
|
+
/>
|
|
530
|
+
|
|
531
|
+
{/* Drive to Home button with some space */}
|
|
532
|
+
<Box
|
|
533
|
+
sx={{
|
|
534
|
+
display: "flex",
|
|
535
|
+
justifyContent: "flex-start",
|
|
536
|
+
mt: { xs: 1, sm: 2, md: 5 },
|
|
537
|
+
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
538
|
+
}}
|
|
539
|
+
>
|
|
540
|
+
<Button
|
|
541
|
+
ref={driveButtonRef}
|
|
542
|
+
variant="contained"
|
|
543
|
+
color="secondary"
|
|
544
|
+
size="small"
|
|
545
|
+
disabled={!driveToHomeEnabled}
|
|
546
|
+
onMouseDown={handleDriveToHomeMouseDown}
|
|
547
|
+
onMouseUp={handleDriveToHomeMouseUp}
|
|
548
|
+
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
549
|
+
onTouchStart={handleDriveToHomeMouseDown}
|
|
550
|
+
onTouchEnd={handleDriveToHomeMouseUp}
|
|
551
|
+
sx={{
|
|
552
|
+
textTransform: "none",
|
|
553
|
+
px: 1.5,
|
|
554
|
+
py: 0.5,
|
|
555
|
+
}}
|
|
556
|
+
>
|
|
557
|
+
{t("RobotCard.DriveToHome.bt")}
|
|
558
|
+
</Button>
|
|
559
|
+
</Box>
|
|
560
|
+
</Box>
|
|
561
|
+
</Box>
|
|
562
|
+
</>
|
|
563
|
+
)}
|
|
564
|
+
</Card>
|
|
565
|
+
)
|
|
566
|
+
},
|
|
567
|
+
),
|
|
568
|
+
)
|
|
@@ -112,8 +112,10 @@ export const RobotSetupReadinessIndicator = externalizeComponent(
|
|
|
112
112
|
backgroundColor,
|
|
113
113
|
color: theme.palette.getContrastText(backgroundColor),
|
|
114
114
|
fontWeight: 500,
|
|
115
|
+
height: "auto",
|
|
115
116
|
"& .MuiChip-label": {
|
|
116
117
|
paddingX: 1.5,
|
|
118
|
+
paddingY: 0.5,
|
|
117
119
|
},
|
|
118
120
|
}}
|
|
119
121
|
/>
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
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"
|
|
5
|
-
import { SupportedRobot } from "./SupportedRobot"
|
|
6
5
|
import { defaultGetModel } from "./robotModelLogic"
|
|
6
|
+
import { SupportedRobot } from "./SupportedRobot"
|
|
7
7
|
|
|
8
8
|
export type RobotProps = {
|
|
9
9
|
connectedMotionGroup: ConnectedMotionGroup
|
|
10
10
|
getModel?: (modelFromController: string) => string
|
|
11
11
|
flangeRef?: React.Ref<Group>
|
|
12
12
|
transparentColor?: string
|
|
13
|
+
postModelRender?: () => void
|
|
13
14
|
} & ThreeElements["group"]
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -28,6 +29,7 @@ export function Robot({
|
|
|
28
29
|
getModel = defaultGetModel,
|
|
29
30
|
flangeRef,
|
|
30
31
|
transparentColor,
|
|
32
|
+
postModelRender,
|
|
31
33
|
...props
|
|
32
34
|
}: RobotProps) {
|
|
33
35
|
if (!connectedMotionGroup.dhParameters) {
|
|
@@ -44,6 +46,7 @@ export function Robot({
|
|
|
44
46
|
getModel={getModel}
|
|
45
47
|
flangeRef={flangeRef}
|
|
46
48
|
transparentColor={transparentColor}
|
|
49
|
+
postModelRender={postModelRender}
|
|
47
50
|
{...props}
|
|
48
51
|
/>
|
|
49
52
|
)
|
|
@@ -64,5 +64,7 @@
|
|
|
64
64
|
"ProgramStateIndicator.ManualT2.lb": "Manuell T2",
|
|
65
65
|
"RobotSetupReadinessIndicator.Ready.lb": "Bereit",
|
|
66
66
|
"RobotSetupReadinessIndicator.RobotDisconnected.lb": "Roboter getrennt",
|
|
67
|
-
"RobotSetupReadinessIndicator.PreconditionNotFulfilled.lb": "Voraussetzung nicht erfüllt"
|
|
67
|
+
"RobotSetupReadinessIndicator.PreconditionNotFulfilled.lb": "Voraussetzung nicht erfüllt",
|
|
68
|
+
"RobotCard.Runtime.lb": "Laufzeit",
|
|
69
|
+
"RobotCard.DriveToHome.bt": "Zur Home-Position fahren"
|
|
68
70
|
}
|
|
@@ -65,5 +65,7 @@
|
|
|
65
65
|
"ProgramStateIndicator.ManualT2.lb": "Manual T2",
|
|
66
66
|
"RobotSetupReadinessIndicator.Ready.lb": "Ready",
|
|
67
67
|
"RobotSetupReadinessIndicator.RobotDisconnected.lb": "Robot disconnected",
|
|
68
|
-
"RobotSetupReadinessIndicator.PreconditionNotFulfilled.lb": "Precondition not fulfilled"
|
|
68
|
+
"RobotSetupReadinessIndicator.PreconditionNotFulfilled.lb": "Precondition not fulfilled",
|
|
69
|
+
"RobotCard.Runtime.lb": "Runtime",
|
|
70
|
+
"RobotCard.DriveToHome.bt": "Drive to Home"
|
|
69
71
|
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./components/LoadingCover"
|
|
|
12
12
|
export * from "./components/modal/NoMotionGroupModal"
|
|
13
13
|
export * from "./components/ProgramControl"
|
|
14
14
|
export * from "./components/ProgramStateIndicator"
|
|
15
|
+
export * from "./components/RobotCard"
|
|
15
16
|
export * from "./components/RobotListItem"
|
|
16
17
|
export * from "./components/robots/AxisConfig"
|
|
17
18
|
export * from "./components/robots/Robot"
|