EasyEDA Schematic Component Placement & Net Fanout
Overview
This skill defines the proven workflow for placing components on an EasyEDA Pro schematic and fanning out all pins to net flags/ports. The result is a clean, non-overlapping layout where every pin has a visible net label connected by a short wire — ready for the user to route connections manually.
Workflow Steps
Step 1: Batch-fetch device info by LCSC IDs
Use lib_Device.getByLcscIds() with allowMultiMatch=true to get all device
UUIDs and libraryUuids in one call:
const lcscIds = ["C9900163599", "C9900012665", "C6186", ...];
const results = await eda.lib_Device.getByLcscIds(lcscIds, undefined, true);
// Each result has: uuid, libraryUuid, name, footprintName, etc.
Important: otherProperty keys are in English (e.g. "Supplier Part",
"Manufacturer"). Do NOT use Chinese keys — they will throw "is not defined".
Step 2: Place components with spacing
Schematic coordinate unit = 0.01 inch (10mil). Grid step of 10 = 100mil.
Module-Box Layout (recommended for multi-block designs)
When the design has distinct functional blocks (e.g. USB/Power, MCU, Display, Storage, Buttons), draw colored module rectangles FIRST, then place components inside their respective boxes. This produces a readable, professional schematic.
Module rectangle API:
// create(topLeftX, topLeftY, width, height, cornerRadius, rotation, color, fillColor, lineWidth, lineType, fillStyle)
// - color: border color string e.g. "#0066CC"
// - fillColor: "none" for transparent, null for default, or hex color
// - lineWidth: 1-10
await eda.sch_PrimitiveRectangle.create(x, topY, w, h, 10, 0, "#0066CC", "none", 2, null, null);
Module text label API:
// create(x, y, content, rotation, textColor, fontName, fontSize, bold, italic, underLine, alignMode)
// alignMode: use NUMERIC value, NOT enum name (see pitfall below)
// 1=LEFT_TOP, 2=LEFT_MIDDLE, 4=CENTER_TOP, 5=CENTER, 6=CENTER_BOTTOM, 8=RIGHT_MIDDLE
await eda.sch_PrimitiveText.create(centerX, topY - 15, "USB & 电源", 0, "#0066CC", null, 16, true, false, false, 4);
Grid layout example (5 modules in a 3x2 grid):
Row 1 (top, topY~1450): USB&Power(x=50,w=620) | ESP32(x=750,w=700) | LCD(x=1550,w=520)
Row 2 (bottom, topY~700): Button&UART(x=50,w=620) | (empty) | TF Card(x=1550,w=520)
- Give each module enough interior space for all its components plus net flag fanout room (~100 units margin inside edges).
- ESP32 module needs the largest box (700x900+) — it has 41 pins fanning out on both sides.
- Place module label text 15 units below the top edge (inside the box, Y decreasing).
Component placement inside modules
const placements = [
// {designator, device_key, x, y, rotation}
// Place each component at a coordinate INSIDE its module box
];
for (const p of placements) {
const comp = {libraryUuid: p.libUuid, uuid: p.uuid};
const created = await eda.sch_PrimitiveComponent.create(comp, p.x, p.y, "", p.rot, false, true, true);
const asyncComp = created.toAsync();
asyncComp.setState_Designator(p.des);
await asyncComp.done();
}
Spacing guidelines (in schematic units = 10mil each):
- Small ICs (SOT-23, SOT-23-6): 200+ X gap, 250+ Y gap between rows
- Medium ICs (SOT-223, SMA): 250+ X gap
- Large components (ESP32 module, LCD): 350-400+ X gap, 350+ Y gap
- Buttons: 200+ X gap
- Always place large components (ESP32, LCD) on their own row or in their own module box
- Passive components (R/C, 2-pin): 100+ X gap is sufficient; they are small
- When using module boxes, ensure components + their fanout flags stay inside the box — add ~100 units margin from box edges
Step 3: Get all pin positions
const pins = await eda.sch_PrimitiveComponent.getAllPinsByPrimitiveId(compPrimitiveId);
// Each pin has: getState_PinNumber(), getState_PinName(), getState_X(), getState_Y(), getState_Rotation()
Pin rotation indicates direction:
- 0 = pin points right (endpoint is to the right of component body)
- 180 = pin points left
- 270 = pin points up
- 90 = pin points down
Step 4: Define net assignments
Create a mapping {designator}:{pinNumber} -> netName based on the circuit design.
Classify each net:
- Power nets (3V3, 5V, VBUS, etc.) → use
createNetFlag("Power", ...) - Ground net (GND) → use
createNetFlag("Ground", ...) - Signal nets (everything else) → use
createNetPort("BI", ...) - NC (no connect) → skip
Step 5: Create net flags/ports + connecting wires
For each pin, place a net flag/port 10 units away from the pin endpoint, in the direction the pin points. Then connect with a short wire.
for (const e of entries) {
let flagPrim;
if (e.flagType === "power") {
flagPrim = await eda.sch_PrimitiveComponent.createNetFlag("Power", e.net, e.fx, e.fy, e.flagRot, false);
} else if (e.flagType === "ground") {
flagPrim = await eda.sch_PrimitiveComponent.createNetFlag("Ground", e.net, e.fx, e.fy, e.flagRot, false);
} else {
flagPrim = await eda.sch_PrimitiveComponent.createNetPort("BI", e.net, e.fx, e.fy, e.flagRot, false);
}
// Wire: [pinX, pinY, flagX, flagY]
const wire = await eda.sch_PrimitiveWire.create([e.pinX, e.pinY, e.fx, e.fy], e.net);
}
Flag placement offset calculation:
rot=0 → flag at (pinX+10, pinY), flagRot=0, wire=[pinX,pinY, pinX+10,pinY]
rot=180 → flag at (pinX-10, pinY), flagRot=180, wire=[pinX,pinY, pinX-10,pinY]
rot=270 → flag at (pinX, pinY-10), flagRot=270, wire=[pinX,pinY, pinX,pinY-10]
rot=90 → flag at (pinX, pinY+10), flagRot=90, wire=[pinX,pinY, pinX,pinY+10]
Step 6: Execute via bridge — avoid shell escaping
When sending large payloads (99+ operations) via curl, write the JSON payload
to a temp file and use curl -d @file to avoid bash quoting issues:
import json, subprocess
payload = json.dumps({"code": js_code})
with open("payload.json", "w") as f:
f.write(payload)
result = subprocess.run(
["curl", "-s", "--max-time", "120", "-X", "POST",
"http://localhost:49620/execute",
"-H", "Content-Type: application/json",
"-d", "@payload.json"],
capture_output=True, text=True, timeout=180
)
Coordinate System (CRITICAL)
EasyEDA schematic Y-axis increases UPWARD (first quadrant orientation).
sch_PrimitiveRectangle.create(topLeftX, topLeftY, width, height):topLeftYis the TOP boundary (larger Y value),heightextends DOWNWARD (Y decreases). To draw a box with visual top-left at (30, 30) and size 540x360, usetopLeftX=30, topLeftY=390, width=540, height=360.sch_PrimitiveComponent.create(x, y, ...): largery= higher on screen.sch_PrimitiveText.create(x, y, ...): same, largery= higher.
Getting this wrong causes module boxes to appear in the wrong quadrant (mirrored vertically). When placing module rectangles around components, compute topLeftY = visualTopY + height.
Key Pitfalls
- Y-axis goes UP - rectangle topLeftY is the top edge (large Y), height extends downward. Getting this wrong puts boxes in the wrong quadrant.
- Never inline JSON in bash -d '...' for large payloads - Chinese characters
or nested quotes break bash parsing. Always use
@file. - Always use
awaiton all EDA API methods - they return Promises. - Set designator via async pattern:
created.toAsync()->setState_Designator()->await done(). otherPropertykeys are English strings - never use Chinese property names.- EP (exposed pad) / mechanical pins on USB-C etc. - skip if not in net assignment.
- Pin rotation determines flag direction - placing flag in wrong direction causes visual overlap with component body.
- Batch all flag+wire creations in one execute call - much faster than individual curl calls. 127 flags + 127 wires (254 operations) complete in ~38 seconds.
- Component BBOX overlap - always check component sizes before placing. Large components (ESP32, LCD, TF card) need 350+ X gap. Resistors/capacitors near large components must be offset enough to avoid BBOX collision.
ESCH_PrimitiveTextAlignModeenum is NOT available as a global in the bridge execution context. PassingESCH_PrimitiveTextAlignMode.CENTER_TOPthrows "is not defined". Use the numeric value directly:1=LEFT_TOP,2=LEFT_MIDDLE,4=CENTER_TOP,5=CENTER,6=CENTER_BOTTOM,8=RIGHT_MIDDLE. NOTE: this contradicts theeasyeda-apiskill's blanket rule "always use enum members" - for SCH text alignment, the enum is simply not injected into the execution context. Use numeric values.- Schematic
getAll()returns net flags and net ports as components withdesignator: null- there is noisNetFlag/isNetPortboolean property. To count real components, filtergetState_Designator() != null. A 30-component design with 127-pin fanout yields 157 total component objects (30 real + 127 flags/ports). - Module boxes must accommodate fanout - pins fan out 10+ units beyond each component edge. Size module rectangles with ~100 units interior margin so net flags/ports and wires stay inside the box. ESP32 (41 pins) needs the largest box (700x900+ schematic units).
fillColor: "none"makes rectangle backgrounds transparent (no fill). Use"none"explicitly -nullgives the default fill which may obscure components placed behind the rectangle.- Wire objects expose
.lineproperty, NOTgetState_Coordinates()-w.linereturns[x1, y1, x2, y2]directly.getState_Coordinates()throws an error on wire objects. Usew.lineandw.netto read wire geometry and net name. - Deleting a component does NOT auto-delete its net flags or wires -
you must manually locate and delete the associated net flags/ports (via
sch_PrimitiveComponent.delete) and wires (viasch_PrimitiveWire.delete). Orphaned net labels remain on the canvas otherwise. - Tactile button same-pole pins must share a net - on TS-1187A and similar 4-pin buttons, pin1=pin2 (A=B) are the same pole, pin3=pin4 (C=D) are the same pole. NEVER assign different nets to same-pole pins. Typical wiring: pin1+pin2 -> signal (BOOT/EN), pin3+pin4 -> GND. Assigning pin1=BOOT and pin2=GND is a hard error - they are internally shorted.
execute_codeon Windows writes temp files totempfile.gettempdir(), NOT/tmp/. The Linux-style/tmp/path does not exist on Windows. Always useos.path.join(tempfile.gettempdir(), "filename")for temp payloads.
Modifying an Existing Schematic
When the user asks to modify an already-placed schematic (change net assignments, add/remove components, fix wiring), you must locate and delete the old net flags/ports and wires BEFORE creating new ones.
Reading wire positions
Wire objects returned by sch_PrimitiveWire.getAll() expose their coordinates
via the .line property (NOT getState_Coordinates(), which does not exist):
const wires = await eda.sch_PrimitiveWire.getAll();
// Each wire: { primitiveId, line: [x1, y1, x2, y2], net: "GND", ... }
Reading component positions
Component objects expose position via getState_X(), getState_Y(),
getState_Rotation(), getState_Designator(), getState_Net():
const comps = await eda.sch_PrimitiveComponent.getAll();
// Net flags/ports have designator === null (no isNetFlag boolean)
// Use position + net to identify which flag belongs to which pin
Locate-and-delete workflow
- Get all components and wires with their positions
- Filter by coordinate proximity to the target component's pin locations (net flags are 10 units away from pin endpoints, wires connect the two)
- Delete the old net flags/ports via
sch_PrimitiveComponent.delete(pids) - Delete the old wires via
sch_PrimitiveWire.delete(pids) - Delete the component itself if removing it:
sch_PrimitiveComponent.delete(pid) - Create new components, net flags, and wires as needed
// Example: find all net flags/ports near a specific area
const toDelete = comps.filter(c => {
const x = c.getState_X(), y = c.getState_Y();
const des = c.getState_Designator();
return !des && x >= minX && x <= maxX && y >= minY && y <= maxY;
});
// Then delete by primitiveId
Pitfall: When deleting components, their associated net flags and wires are NOT automatically deleted - you must delete them separately. Always clean up both the component AND its fanout (flags + wires) to avoid orphaned net labels.
Verification
After fanout, verify with:
const comps = await eda.sch_PrimitiveComponent.getAll();
const wires = await eda.sch_PrimitiveWire.getAll();
const rects = await eda.sch_PrimitiveRectangle.getAll();
const texts = await eda.sch_PrimitiveText.getAll();
// comps includes real components + net flags + net ports
// Net flags/ports have designator === null (no isNetFlag boolean)
// Wire objects have .line property: [x1, y1, x2, y2]
const realComps = comps.filter(c => {
try { return c.getState_Designator() != null; } catch(e) { return false; }
});
return {
realComponents: realComps.length,
totalComponentObjects: comps.length, // real + flags + ports
wires: wires.length,
rectangles: rects.length,
texts: texts.length,
designators: realComps.map(c => {
try { return c.getState_Designator(); } catch(e) { return "?"; }
}).sort()
};
Expected breakdown (example: 30-component ESP32-S3 design):
- Real components: 30 (match placement count)
- Total component objects: 157 (30 real + 127 net flags/ports)
- Wires: 127 (one per non-NC pin)
- Rectangles: 5 module boxes + any border decorations
- Texts: 5 module labels
Reference Example
See references/esp32-s3-debugger-example.md for a complete worked example:
30 components, 5 module boxes, 127-pin net fanout, including the full net
assignment table for an ESP32-S3 wireless debugger design.