Skip to content

Commit

Permalink
Merge pull request #13 from tscircuit/feat/dsn-convertion-to-and-fro
Browse files Browse the repository at this point in the history
feat: dsn convertion from circuit json and vice versa
  • Loading branch information
imrishabh18 authored Nov 6, 2024
2 parents 6f5b088 + 5b939e9 commit 713f8c5
Show file tree
Hide file tree
Showing 39 changed files with 1,503 additions and 669 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,8 @@ dist
.DS_Store
.aider*

.vscode
.vscode

#ignore the diff images
*.diff.png
*.diff.svg
Binary file modified bun.lockb
Binary file not shown.
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())
}
107 changes: 107 additions & 0 deletions lib/dsn-pcb/circuit-json-to-dsn-json/process-components-and-pads.ts
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)
}
}
107 changes: 107 additions & 0 deletions lib/dsn-pcb/circuit-json-to-dsn-json/process-nets.ts
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
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DsnPcb } from "./types"
import type { DsnPcb } from "../types"

export const stringifyDsnJson = (dsnJson: DsnPcb): string => {
const indent = " "
Expand Down Expand Up @@ -27,7 +27,7 @@ export const stringifyDsnJson = (dsnJson: DsnPcb): string => {
}

// Start with pcb
result += `(pcb ${dsnJson.filename}\n`
result += `(pcb ${dsnJson.filename ? dsnJson.filename : "./converted_dsn.dsn"}\n`

// Parser section
result += `${indent}(parser\n`
Expand Down Expand Up @@ -128,7 +128,7 @@ export const stringifyDsnJson = (dsnJson: DsnPcb): string => {

// Wiring section
result += `${indent}(wiring\n`
dsnJson.wiring.wires.forEach((wire) => {
;(dsnJson.wiring?.wires ?? []).forEach((wire) => {
result += `${indent}${indent}(wire ${stringifyPath(wire.path, 3)}(net ${stringifyValue(wire.net)})(type ${wire.type}))\n`
})
result += `${indent})\n`
Expand Down
Loading

0 comments on commit 713f8c5

Please sign in to comment.