diff --git a/fission/src/ui/components/simulation/FlowControls.tsx b/fission/src/ui/components/simulation/FlowControls.tsx index 30da1a7dc9..ae8b4e68aa 100644 --- a/fission/src/ui/components/simulation/FlowControls.tsx +++ b/fission/src/ui/components/simulation/FlowControls.tsx @@ -9,16 +9,16 @@ function FlowControls({ onCreateJunction }: FlowControlsProps) { return ( - - - - diff --git a/fission/src/ui/panels/simulation/WiringNode.tsx b/fission/src/ui/panels/simulation/WiringNode.tsx index d7066c9435..7c8e3d76ec 100644 --- a/fission/src/ui/panels/simulation/WiringNode.tsx +++ b/fission/src/ui/panels/simulation/WiringNode.tsx @@ -1,4 +1,4 @@ -import { Stack } from "@mui/material" +import { Box } from "@mui/material" import { type Connection, type Edge, Handle, type NodeProps, Position } from "@xyflow/react" import { useCallback, useMemo } from "react" import { @@ -8,18 +8,17 @@ import { SimConfig, type SimConfigData, } from "@/systems/simulation/SimConfigShared" -import Label from "@/ui/components/Label" import { CustomTooltip, DeleteButton, EditButton, RefreshButton } from "@/ui/components/StyledComponents" const WiringNode = ({ data, isConnectable }: NodeProps) => { - const robotInput = data.input as HandleInfo[] | undefined - const robotOutput = data.output as HandleInfo[] | undefined - const onEdit = data.onEdit as (() => void) | undefined - const onRefresh = data.onRefresh as (() => void) | undefined - const onDelete = data.onDelete as (() => void) | undefined - const simConfig = data.simConfig as SimConfigData - const title = data.title as string - const tooltip = data.tooltip as string | undefined + const robotInput = useMemo(() => data.input as HandleInfo[] | undefined, [data]) + const robotOutput = useMemo(() => data.output as HandleInfo[] | undefined, [data]) + const onEdit = useMemo(() => data.onEdit as (() => void) | undefined, [data]) + const onRefresh = useMemo(() => data.onRefresh as (() => void) | undefined, [data]) + const onDelete = useMemo(() => data.onDelete as (() => void) | undefined, [data]) + const simConfig = useMemo(() => data.simConfig as SimConfigData, [data]) + const title = useMemo(() => data.title as string, [data]) + const tooltip = useMemo(() => data.tooltip as string | undefined, [data]) const validateConnection = useCallback( (edge: Edge | Connection) => { @@ -30,66 +29,97 @@ const WiringNode = ({ data, isConnectable }: NodeProps) => { const inputHandles = useMemo( () => - robotInput && ( - - {robotInput.sort(handleInfoDisplayCompare).map((x, i) => ( -
- - -
- ))} -
+ robotInput ? ( + + {robotInput.sort(handleInfoDisplayCompare).map((x, i) => { + return ( +
+
{x.displayName}
+ +
+ ) + })} +
+ ) : ( + <> ), [isConnectable, robotInput] ) const outputHandles = useMemo( () => - robotOutput && ( - - {robotOutput.sort(handleInfoDisplayCompare).map((x, i) => ( -
- - -
- ))} -
+ robotOutput ? ( + + {robotOutput.sort(handleInfoDisplayCompare).map((x, i) => { + return ( +
+
{x.displayName}
+ +
+ ) + })} +
+ ) : ( + <> ), [isConnectable, robotOutput, validateConnection] ) return ( - -
+ - {tooltip && CustomTooltip(tooltip)} + {tooltip ? CustomTooltip(tooltip) : <>} {title} -
-
+ { columnGap: "0.5rem", } : robotInput - ? { paddingRight: "2rem" } - : { paddingLeft: "2rem" } + ? { + paddingRight: "2rem", + } + : { + paddingLeft: "2rem", + } } > {inputHandles} {outputHandles} -
- {(onEdit || onDelete) && ( -
- {onEdit && EditButton(onEdit)} - {onRefresh && RefreshButton(onRefresh)} - {onDelete && DeleteButton(onDelete)} -
+ + {onEdit || onDelete ? ( + + {onEdit ? EditButton(onEdit) : <>} + {onRefresh ? RefreshButton(onRefresh) : <>} + {onDelete ? DeleteButton(onDelete) : <>} + + ) : ( + <> )} -
+ ) } diff --git a/fission/src/ui/panels/simulation/WiringPanel.tsx b/fission/src/ui/panels/simulation/WiringPanel.tsx index baf460658d..109a63e944 100644 --- a/fission/src/ui/panels/simulation/WiringPanel.tsx +++ b/fission/src/ui/panels/simulation/WiringPanel.tsx @@ -1,4 +1,5 @@ -import { Grid, Stack } from "@mui/material" +import "@xyflow/react/dist/style.css" +import { Box, Stack, useTheme } from "@mui/material" import { type Connection, type FinalConnectionState, @@ -38,6 +39,11 @@ import FlowInfo from "@/ui/components/simulation/FlowInfo" import { useUIContext } from "../../helpers/UIProviderHelpers" import WiringNode from "./WiringNode" +/** + * WARNING: Please test *thoroughly* when making changes. React Flow is very tempermental with how nodes + * and object references are maintained. + */ + type ConfigComponentProps = { setConfigState: (state: ConfigState) => void selectedAssembly: MirabufSceneObject @@ -55,7 +61,7 @@ type NodeType = ComponentType< const nodeTypes: Record = [WiringNode].reduce<{ [k: string]: NodeType }>((prev, next) => { - prev[next.name] = next as NodeType + prev[next.name] = next return prev }, {}) @@ -67,7 +73,7 @@ function generateGraph( const nodes: Map = new Map() const edges: FlowEdge[] = [] - for (const [_k, v] of Object.entries(simConfig.nodes)) { + Object.entries(simConfig.nodes).forEach(([_k, v]) => { let onEdit: (() => void) | undefined let onRefresh: (() => void) | undefined let onDelete: (() => void) | undefined @@ -98,32 +104,34 @@ function generateGraph( } nodes.set(v.id, { - ...v, + id: v.id, + type: v.type, + position: v.position, data: { - title, - onEdit, - onRefresh, - onDelete, - simConfig, + title: title, + onEdit: onEdit, + onRefresh: onRefresh, + onDelete: onDelete, + simConfig: simConfig, input: [], output: [], tooltip: v.tooltip, }, }) - } + }) - for (const [_k, v] of Object.entries(simConfig.handles)) { - if (!v.enabled) break + Object.entries(simConfig.handles).forEach(([_k, v]) => { + if (!v.enabled) return const node = nodes.get(v.nodeId) if (!node) { console.warn("Orphaned handle found") - break + return } const list = (v.isSource ? node.data.output : node.data.input) as unknown[] - list.push(v) - } + list.push({ ...v }) + }) - for (const [k, v] of Object.entries(simConfig.edges)) { + Object.entries(simConfig.edges).forEach(([k, v]) => { const sourceHandle = simConfig.handles[v.sourceId] const targetHandle = simConfig.handles[v.targetId] @@ -136,71 +144,96 @@ function generateGraph( targetHandle: targetHandle.id, }) } - } + }) return [[...nodes.values()], edges] } const SimIoComponent: React.FC = ({ setConfigState, simConfig }) => { - const simOut: HandleInfo[] = [] - const simIn: HandleInfo[] = [] - for (const [_k, v] of Object.entries(simConfig.handles)) { - if (v.nodeId === NODE_ID_SIM_OUT || v.nodeId === NODE_ID_SIM_IN) { - const list = v.isSource ? simOut : simIn - list.push(v) + const theme = useTheme() + + const [simOut, setSimOut] = useState>({}) + const [simIn, setSimIn] = useState>({}) + + useEffect(() => { + const simOut: Record = {} + const simIn: Record = {} + for (const [_k, v] of Object.entries(simConfig.handles)) { + if (v.nodeId === NODE_ID_SIM_OUT || v.nodeId === NODE_ID_SIM_IN) { + v.isSource ? (simOut[v.id] = v) : (simIn[v.id] = v) + } } - } + setSimOut(simOut) + setSimIn(simIn) + }, [simConfig]) return ( - - - + + + - + - {simOut.sort(handleInfoDisplayCompare).map(handle => ( + {Object.values(simOut).sort(handleInfoDisplayCompare).map(handle => ( { handle.enabled = checked + setSimOut({ ...simOut, [handle.id]: handle }) }} /> ))} + - + - {simIn.sort(handleInfoDisplayCompare).map(handle => ( + {Object.values(simIn).sort(handleInfoDisplayCompare).map(handle => ( { handle.enabled = checked + setSimIn({ ...simIn, [handle.id]: handle }) }} /> ))} - - + + ) } const RobotIoComponent: React.FC = ({ setConfigState, simConfig }) => { + const theme = useTheme() + + const [refreshHook, refreshCheckboxes] = useReducer(x => !x, false) + const [canEncoders, canMotors, pwmDevices, accelerometers] = useMemo(() => { const canEncoders: JSX.Element[] = [] const canMotors: JSX.Element[] = [] const pwmDevices: JSX.Element[] = [] const accelerometers: JSX.Element[] = [] - for (const [_k, v] of Object.entries(simConfig.handles)) { + Object.entries(simConfig.handles).forEach(([_k, v]) => { if (v.nodeId !== NODE_ID_ROBOT_IO) return [] + console.debug(v) + const checkbox = ( = ({ setConfigState, simC checked={v.enabled} onClick={enabled => { v.enabled = enabled + refreshCheckboxes() }} /> ) @@ -220,23 +254,29 @@ const RobotIoComponent: React.FC = ({ setConfigState, simC pwmDevices.push(checkbox) break case SimType.CAN_ENCODER: - pwmDevices.push(checkbox) + canEncoders.push(checkbox) break case SimType.ACCELEROMETER: - pwmDevices.push(checkbox) + accelerometers.push(checkbox) break } - } + }) return [canEncoders, canMotors, pwmDevices, accelerometers] - }, [simConfig]) + }, [simConfig, refreshHook]) return ( - - + + - + {canEncoders} @@ -244,8 +284,9 @@ const RobotIoComponent: React.FC = ({ setConfigState, simC {accelerometers} + - + {canMotors} @@ -253,8 +294,10 @@ const RobotIoComponent: React.FC = ({ setConfigState, simC {pwmDevices} - - + + ) } @@ -263,14 +306,14 @@ const WiringComponent: React.FC = ({ setConfigState, simCo const { screenToFlowPosition } = useReactFlow() const [nodes, setNodes, onNodesChange] = useNodesState([] as FlowNode[]) const [edges, setEdges, onEdgesChange] = useEdgesState([] as FlowEdge[]) - const [_refreshHook, refreshGraph] = useReducer(x => !x, false) // Whenever I use reducers, it's always sketch. -Hunter + const [refreshHook, refreshGraph] = useReducer(x => !x, false) // Whenever I use reducers, it's always sketch. -Hunter // Essentially a callback, but it can use itself useEffect(() => { const [nodes, edges] = generateGraph(simConfig, refreshGraph, setConfigState) setNodes(nodes) setEdges(edges) - }, [setConfigState, setEdges, setNodes, simConfig]) + }, [setConfigState, setEdges, setNodes, simConfig, refreshHook]) const onEdgeDoubleClick = useCallback( (_: React.MouseEvent, edge: FlowEdge) => { @@ -337,7 +380,7 @@ const WiringComponent: React.FC = ({ setConfigState, simCo const onCreateJunction = useCallback(() => { SimConfig.AddJunctionNode(simConfig) refreshGraph() - }, [simConfig]) + }, [refreshGraph, simConfig]) return ( > = ({ panel }) => { const existingConfig = selectedAssembly.simConfigData if (existingConfig) { + console.debug('Existing SimConfig found') setSimConfig(JSON.parse(JSON.stringify(existingConfig))) // Create copy to not force a save } else { + console.debug('No SimConfig found, creating default...') setSimConfig(SimConfig.Default(selectedAssembly)) } }, [selectedAssembly]) @@ -395,6 +440,8 @@ const WiringPanel: React.FC> = ({ panel }) => { console.debug(`${flows.length} Flows Successfully Compiled!`) selectedAssembly.updateSimConfig(simConfig) + } else { + console.warn('Failed to save SimConfig', simConfig, selectedAssembly) } }, [selectedAssembly, simConfig]) @@ -406,12 +453,18 @@ const WiringPanel: React.FC> = ({ panel }) => { useEffect(() => { configureScreen(panel!, { title: "Wiring Panel" }, { onBeforeAccept: save }) - }, []) + }, [save]) return ( <> {selectedAssembly && simConfig ? ( -
+ {configState === "wiring" && ( > = ({ panel }) => { setConfigState={setConfigState} /> )} -
+ ) : ( "ERRR" )} diff --git a/simulation/samples/JavaAutoSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaAutoSample/src/main/java/frc/robot/Robot.java index 9452a56343..e1d21d63c4 100644 --- a/simulation/samples/JavaAutoSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaAutoSample/src/main/java/frc/robot/Robot.java @@ -39,7 +39,9 @@ public class Robot extends TimedRobot { private String m_autoSelected; private final SendableChooser m_chooser = new SendableChooser<>(); - private ADXL362 m_Accelerometer = new ADXL362(SPI.Port.kMXP, ADXL362.Range.k8G); + private static final int kAutoTurn = 90; + + private ADXL362 m_accelerometer = new ADXL362(SPI.Port.kMXP, ADXL362.Range.k8G); private AHRS m_Gyro = new AHRS(); private CANSparkMax m_sparkLeft = new CANSparkMax(1, MotorType.kBrushless); @@ -47,6 +49,8 @@ public class Robot extends TimedRobot { private CANSparkMax m_sparkArm = new CANSparkMax(3, MotorType.kBrushless); private RelativeEncoder m_encoder; + private double m_initAngle = 0; + /** * This function is run when the robot is first started up and should be used * for any @@ -117,13 +121,19 @@ public void autonomousPeriodic() { m_sparkLeft.set(0.5); m_sparkRight.set(0.5); if (m_encoder.getPosition() > 36.0) { + m_initAngle = m_accelerometer.getY(); m_autoState = AutoState.Stage2; System.out.println("--- Transitioning to Stage 2 ---"); } break; case Stage2: - m_sparkLeft.set(0.5); - m_sparkRight.set(-0.5); + double current = (m_accelerometer.getY() - m_initAngle); + double delta = kAutoTurn - current; + double speed = delta * (1.0 / 15.0); + speed = Math.max(Math.min(speed, 1.0), -1.0); + + m_sparkLeft.set(speed); + m_sparkRight.set(-speed); break; default: break;