-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from tscircuit/feat/dsn-convertion-to-and-fro
feat: dsn convertion from circuit json and vice versa
- Loading branch information
Showing
39 changed files
with
1,503 additions
and
669 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -175,4 +175,8 @@ dist | |
.DS_Store | ||
.aider* | ||
|
||
.vscode | ||
.vscode | ||
|
||
#ignore the diff images | ||
*.diff.png | ||
*.diff.svg |
146 changes: 146 additions & 0 deletions
146
lib/dsn-pcb/circuit-json-to-dsn-json/convert-circuit-json-to-dsn-json.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import type { AnyCircuitElement } from "circuit-json" | ||
import type { DsnPcb, Padstack, ComponentGroup } from "../types" | ||
import { processComponentsAndPads } from "./process-components-and-pads" | ||
import { processNets } from "./process-nets" | ||
|
||
export function convertCircuitJsonToDsnJson( | ||
circuitElements: AnyCircuitElement[], | ||
): DsnPcb { | ||
const pcb: DsnPcb = { | ||
filename: "", | ||
parser: { | ||
string_quote: "", | ||
host_version: "", | ||
space_in_quoted_tokens: "", | ||
host_cad: "", | ||
}, | ||
resolution: { | ||
unit: "um", | ||
value: 10, | ||
}, | ||
unit: "um", | ||
structure: { | ||
layers: [ | ||
{ | ||
name: "F.Cu", | ||
type: "signal", | ||
property: { | ||
index: 0, | ||
}, | ||
}, | ||
{ | ||
name: "B.Cu", | ||
type: "signal", | ||
property: { | ||
index: 1, | ||
}, | ||
}, | ||
], | ||
boundary: { | ||
path: { | ||
layer: "pcb", | ||
width: 0, | ||
coordinates: [ | ||
158000, -108000, 147500, -108000, 147500, -102000, 158000, -102000, | ||
158000, -108000, | ||
], | ||
}, | ||
}, | ||
via: "Via[0-1]_600:300_um", | ||
rule: { | ||
clearances: [ | ||
{ | ||
value: 200, | ||
}, | ||
{ | ||
value: 200, | ||
type: "default_smd", | ||
}, | ||
{ | ||
value: 50, | ||
type: "smd_smd", | ||
}, | ||
], | ||
width: 200, | ||
}, | ||
}, | ||
placement: { | ||
components: [], | ||
}, | ||
library: { | ||
images: [], | ||
padstacks: [ | ||
{ | ||
name: "Via[0-1]_600:300_um", | ||
shapes: [ | ||
{ | ||
shapeType: "circle", | ||
layer: "F.Cu", | ||
diameter: 600, | ||
}, | ||
{ | ||
shapeType: "circle", | ||
layer: "B.Cu", | ||
diameter: 600, | ||
}, | ||
], | ||
attach: "off", | ||
}, | ||
], | ||
}, | ||
network: { | ||
nets: [], | ||
classes: [ | ||
{ | ||
name: "kicad_default", | ||
description: "", | ||
net_names: [], | ||
circuit: { | ||
use_via: "Via[0-1]_600:300_um", | ||
}, | ||
rule: { | ||
clearances: [ | ||
{ | ||
value: 200, | ||
type: "", | ||
}, | ||
], | ||
width: 200, | ||
}, | ||
}, | ||
], | ||
}, | ||
wiring: { | ||
wires: [], | ||
}, | ||
} | ||
|
||
const componentGroups = groupComponents(circuitElements) | ||
processComponentsAndPads(componentGroups, circuitElements, pcb) | ||
processNets(circuitElements, pcb) | ||
|
||
return pcb | ||
} | ||
|
||
function groupComponents( | ||
circuitElements: AnyCircuitElement[], | ||
): ComponentGroup[] { | ||
const componentMap = new Map<string, ComponentGroup>() | ||
|
||
for (const element of circuitElements) { | ||
if (element.type === "pcb_smtpad") { | ||
const pcbPad = element | ||
const componentId = pcbPad.pcb_component_id ?? "" | ||
|
||
if (!componentMap.has(componentId)) { | ||
componentMap.set(componentId, { | ||
pcb_component_id: componentId, | ||
pcb_smtpads: [], | ||
}) | ||
} | ||
componentMap.get(componentId)?.pcb_smtpads.push(pcbPad) | ||
} | ||
} | ||
|
||
return Array.from(componentMap.values()) | ||
} |
File renamed without changes.
107 changes: 107 additions & 0 deletions
107
lib/dsn-pcb/circuit-json-to-dsn-json/process-components-and-pads.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import type { | ||
AnyCircuitElement, | ||
PcbComponent, | ||
SourceComponentBase, | ||
} from "circuit-json" | ||
import { getComponentValue } from "lib/utils/get-component-value" | ||
import { getFootprintName } from "lib/utils/get-footprint-name" | ||
import { getPadstackName } from "lib/utils/get-padstack-name" | ||
import { applyToPoint, scale } from "transformation-matrix" | ||
import type { ComponentGroup, DsnPcb, Padstack } from "../types" | ||
|
||
const transformMmToUm = scale(1000) | ||
|
||
function getComponentPins(): Array<{ x: number; y: number }> { | ||
return [ | ||
{ x: -500, y: 0 }, | ||
{ x: 500, y: 0 }, | ||
] | ||
} | ||
|
||
function createExactPadstack(padstackName: string): Padstack { | ||
return { | ||
name: padstackName, | ||
shapes: [ | ||
{ | ||
shapeType: "polygon", | ||
layer: "F.Cu", | ||
width: 0, | ||
coordinates: [ | ||
-300.0, 300.0, 300.0, 300.0, 300.0, -300.0, -300.0, -300.0, -300.0, | ||
300.0, | ||
], | ||
}, | ||
], | ||
attach: "off", | ||
} | ||
} | ||
|
||
export function processComponentsAndPads( | ||
componentGroups: ComponentGroup[], | ||
circuitElements: AnyCircuitElement[], | ||
pcb: DsnPcb, | ||
) { | ||
const processedPadstacks = new Set<string>() | ||
|
||
for (const group of componentGroups) { | ||
const { pcb_component_id, pcb_smtpads } = group | ||
if (pcb_smtpads.length === 0) continue | ||
|
||
const sourceComponent = circuitElements.find( | ||
(e) => | ||
e.type === "pcb_component" && e.pcb_component_id === pcb_component_id, | ||
) as PcbComponent | ||
const srcComp = | ||
sourceComponent && | ||
(circuitElements.find( | ||
(e) => | ||
e.type === "source_component" && | ||
e.source_component_id === sourceComponent.source_component_id, | ||
) as SourceComponentBase) | ||
|
||
const footprintName = getFootprintName(srcComp?.ftype) | ||
const componentName = srcComp?.name || "Unknown" | ||
|
||
// Transform component coordinates | ||
const circuitSpaceCoordinates = applyToPoint( | ||
transformMmToUm, | ||
sourceComponent.center, | ||
) | ||
|
||
// Fixed placement coordinates | ||
const componentPlacement = { | ||
name: footprintName, | ||
place: { | ||
refdes: componentName, | ||
x: circuitSpaceCoordinates.x, | ||
y: circuitSpaceCoordinates.y, | ||
side: "front" as const, | ||
rotation: 0, | ||
PN: getComponentValue(srcComp), | ||
}, | ||
} | ||
|
||
// Handle padstacks | ||
const padstackName = `${getPadstackName(srcComp?.ftype)}_${srcComp?.source_component_id}` | ||
if (!processedPadstacks.has(padstackName)) { | ||
const padstack = createExactPadstack(padstackName) | ||
pcb.library.padstacks.push(padstack) | ||
processedPadstacks.add(padstackName) | ||
} | ||
|
||
// Create image with exact pin positions | ||
const image = { | ||
name: footprintName, | ||
outlines: [], | ||
pins: getComponentPins().map((pos, index) => ({ | ||
padstack_name: padstackName, | ||
pin_number: index + 1, | ||
x: pos.x, | ||
y: pos.y, | ||
})), | ||
} | ||
|
||
pcb.library.images.push(image) | ||
pcb.placement.components.push(componentPlacement) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import type { DsnPcb } from "../types" | ||
import type { AnyCircuitElement, SourcePort } from "circuit-json" | ||
|
||
export function processNets(circuitElements: AnyCircuitElement[], pcb: DsnPcb) { | ||
const componentNameMap = new Map<string, string>() | ||
|
||
for (const element of circuitElements) { | ||
if (element.type === "source_component") { | ||
componentNameMap.set(element.source_component_id, element.name) | ||
} | ||
} | ||
|
||
const padsBySourcePortId = new Map() | ||
|
||
for (const element of circuitElements) { | ||
if (element.type === "pcb_smtpad" && element.pcb_port_id) { | ||
const pcbPort = circuitElements.find( | ||
(e) => e.type === "pcb_port" && e.pcb_port_id === element.pcb_port_id, | ||
) | ||
|
||
if (pcbPort && "source_port_id" in pcbPort) { | ||
const sourcePort = circuitElements.find( | ||
(e) => | ||
e.type === "source_port" && | ||
e.source_port_id === pcbPort.source_port_id, | ||
) as SourcePort | ||
|
||
if (sourcePort && "source_component_id" in sourcePort) { | ||
const componentName = | ||
componentNameMap.get(sourcePort.source_component_id) || "" | ||
const pinNumber = element.port_hints?.[0] || "" | ||
|
||
padsBySourcePortId.set(sourcePort.source_port_id, { | ||
componentName, | ||
pinNumber, | ||
sourcePortId: sourcePort.source_port_id, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
const netMap = new Map() | ||
|
||
for (const element of circuitElements) { | ||
if (element.type === "source_trace" && element.connected_source_port_ids) { | ||
const connectedPorts = element.connected_source_port_ids | ||
|
||
if (connectedPorts.length >= 2) { | ||
const firstPad = padsBySourcePortId.get(connectedPorts[0]) | ||
|
||
if (firstPad) { | ||
const netName = `Net-(${firstPad.componentName}-Pad${firstPad.pinNumber})` | ||
|
||
if (!netMap.has(netName)) { | ||
netMap.set(netName, new Set()) | ||
} | ||
|
||
for (const portId of connectedPorts) { | ||
const padInfo = padsBySourcePortId.get(portId) | ||
if (padInfo) { | ||
netMap | ||
.get(netName) | ||
?.add(`${padInfo.componentName}-${padInfo.pinNumber}`) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
for (const [sourcePortId, padInfo] of padsBySourcePortId) { | ||
let isConnected = false | ||
for (const connectedPads of netMap.values()) { | ||
if (connectedPads.has(`${padInfo.componentName}-${padInfo.pinNumber}`)) { | ||
isConnected = true | ||
break | ||
} | ||
} | ||
|
||
if (!isConnected) { | ||
const unconnectedNetName = `unconnected-(${padInfo.componentName}-Pad${padInfo.pinNumber})` | ||
netMap.set( | ||
unconnectedNetName, | ||
new Set([`${padInfo.componentName}-${padInfo.pinNumber}`]), | ||
) | ||
} | ||
} | ||
|
||
// Sort nets with connected nets first | ||
const allNets = Array.from(netMap.keys()).sort((a, b) => { | ||
if (a.startsWith("Net-") && !b.startsWith("Net-")) return -1 | ||
if (!a.startsWith("Net-") && b.startsWith("Net-")) return 1 | ||
return a.localeCompare(b) | ||
}) | ||
|
||
// Add nets in sorted order | ||
for (const netName of allNets) { | ||
pcb.network.nets.push({ | ||
name: netName, | ||
pins: Array.from(netMap.get(netName) || []), | ||
}) | ||
} | ||
|
||
// Update class net names | ||
pcb.network.classes[0].net_names = allNets | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.