imrishabh18/rp2040-motor-controller
An RP2040-based NEMA17 stepper-motor controller with USB-C programming and PD motor-power negotiation, DRV8825 dual H-bridge drive, current/temperature telemetry, protected power filtering, status RGB LED, buzzer alarm, and optional magnetic encoder.
- Version
- 1.0.42
- License
- unset
- Stars
- 3
schematic/AutoLayoutSections.tsx
import { installSchematicStyleRefinement } from "./refineSchematicStyle";
import {
Children,
Fragment,
cloneElement,
isValidElement,
type ReactElement,
type ReactNode,
} from "react";
/** Keep PCB hierarchy/coordinates intact; give each schematic section its own layout scope. */
export function autoLayoutSections(tree: ReactNode): ReactNode {
let elementKey = 0;
const sectionTitles = new Map<string, string>();
const notes: Array<{
text: string;
ref?: string;
sectionName?: string;
fontSize: number;
color: string;
}> = [];
const textHeadings: Record<string, string> = {
"3.3 V Supply": "MCU__programming_supply",
"3.3 V Bypass": "MCU__programming_bypass",
"Programming USB-C": "MCU__usb",
"USB-C PD Negotiation": "motor_power_pd_negotiation",
"Power Filtering": "motor_power_filtering",
"Temperature Sensing & MCU Alert": "thermal_protection",
"RP2040-selectable USB-PD voltage": "pd_gpio_buffers",
};
const selectorProps = (value: any): any => {
if (typeof value === "string") {
if (value === "MCU__power") return "MCU__rp2040";
if (["MCU__status", "MCU__debug"].includes(value)) return "MCU__controls";
return value.replace(
/\.MCU > \.(U1|L_AVDD|J_USB)(?= |$)/g,
(_, ref) =>
`.MCU > .layout_MCU__${ref === "J_USB" ? "usb" : "rp2040"} > .${ref}`,
);
}
if (isValidElement(value)) return value;
if (Array.isArray(value)) return value.map(selectorProps);
if (value && typeof value === "object")
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, selectorProps(v)]),
);
return value;
};
const sectionSlots = new Map<string, Map<string, number>>();
function expand(input: ReactNode): ReactNode[] {
if (!isValidElement(input)) return input == null ? [] : [input];
const node = input as ReactElement<any>;
if (typeof node.type === "function")
return expand((node.type as any)(node.props));
if ((node.type as unknown) === Fragment)
return Children.toArray(node.props.children).flatMap(expand);
if (node.type === "schematicsection") {
if (node.props.displayName)
sectionTitles.set(
selectorProps(node.props.name),
node.props.displayName,
);
return [];
}
if (node.type === "schematictext") {
const headingSection = textHeadings[node.props.text];
if (headingSection) sectionTitles.set(headingSection, node.props.text);
else {
const ref = node.props.name?.match(/^description_(.+)_\d+$/)?.[1];
notes.push({
text: node.props.text,
ref,
sectionName: ref ? undefined : "pd_gpio_buffers",
fontSize: node.props.fontSize ?? 0.23,
color: node.props.color ?? "#31576b",
});
}
return [];
}
const children = Children.toArray(node.props.children).flatMap(expand);
return [
cloneElement(
node,
{
...selectorProps(node.props),
...(node.type === "schematicsheet"
? { sheetWidth: undefined, sheetHeight: undefined }
: {}),
key: `layout-element-${elementKey++}`,
},
children,
),
];
}
function arrange(node: any, inheritedSheet = "controller"): any {
if (!isValidElement<any>(node)) return node;
const sheet = node.props.schSheetName ?? inheritedSheet;
let children = Children.toArray(node.props.children).map((c) =>
arrange(c, sheet),
);
if (node.type !== "group" && node.type !== "board")
return cloneElement(node, {}, children);
const sections = new Map<string, any[]>();
for (const child of children) {
if (
!isValidElement<any>(child) ||
!child.props.schSectionName ||
!child.props.name
)
continue;
const key = `${child.props.schSheetName ?? sheet}:${child.props.schSectionName}`;
const members = sections.get(key) ?? [];
members.push(child);
sections.set(key, members);
}
children = children.filter(
(c) => ![...sections.values()].some((m) => m.includes(c)),
);
for (const [key, members] of sections) {
const sectionSheet =
members[0].props.schSectionName === "MCU__controls"
? "controller_controls"
: (members[0].props.schSheetName ?? sheet);
const sectionName = members[0].props.schSectionName;
const slots = sectionSlots.get(sectionSheet) ?? new Map<string, number>();
if (!slots.has(key)) slots.set(key, slots.size);
sectionSlots.set(sectionSheet, slots);
const slot = slots.get(key)!;
children.push(
<group
key={key}
name={`layout_${sectionName}`}
pcbX={0}
pcbY={0}
schSheetName={sectionSheet}
schX={(slot % 2) * 20 - 10}
schY={-Math.floor(slot / 2) * 16}
schAutoLayoutEnabled
>
<schematicsection
name={sectionName}
displayName={
sectionTitles.get(sectionName) ??
(sectionName === "MCU__controls"
? "Controls, Status & SWD"
: sectionName.replace(/^MCU__/, "").replaceAll("_", " "))
}
/>
{members.map((m) =>
cloneElement(m, {
schX: undefined,
schY: undefined,
schSheetName: sectionSheet,
}),
)}
</group>,
);
}
return cloneElement(
node,
node.type === "group"
? { schX: 0, schY: 0 }
: {
ref: (board: any) => {
if (typeof node.props.ref === "function") node.props.ref(board);
installSchematicStyleRefinement(board, notes);
},
},
children,
);
}
const result = arrange(expand(tree)[0]);
return cloneElement(
result,
{},
result.props.children,
<schematicsheet
name="controller_controls"
displayName="Controller Controls, Status & SWD"
sheetIndex={10}
/>,
);
}
