Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions src/convert-kicad-json-to-tscircuit-soup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { KicadModJson } from "./kicad-zod"
import type { KicadModJson, KicadSymJson } from "./kicad-zod"
import { convertKicadSymToTscircuitSchematic } from "./convert-kicad-sym-to-tscircuit-schematic"
import type { AnyCircuitElement } from "circuit-json"
import Debug from "debug"
import { generateArcPath, getArcLength } from "./math/arc-utils"
Expand Down Expand Up @@ -82,6 +83,7 @@ export const convertKicadLayerToTscircuitLayer = (kicadLayer: string) => {

export const convertKicadJsonToTsCircuitSoup = async (
kicadJson: KicadModJson,
kicadSymJson?: KicadSymJson,
): Promise<AnyCircuitElement[]> => {
const {
fp_lines,
Expand All @@ -102,14 +104,33 @@ export const convertKicadJsonToTsCircuitSoup = async (
supplier_part_numbers: {},
} as any)

circuitJson.push({
const schematicComponent: any = {
type: "schematic_component",
schematic_component_id: "schematic_component_0",
source_component_id: "source_component_0",
center: { x: 0, y: 0 },
rotation: 0,
size: { width: 0, height: 0 },
} as any)
}

if (kicadSymJson) {
const schematicInfo = convertKicadSymToTscircuitSchematic(kicadSymJson)
schematicComponent.port_arrangement = schematicInfo.port_arrangement
schematicComponent.port_labels = schematicInfo.port_labels

const pin_spacing = 2.54 // 0.1 inch
const left_pins =
schematicInfo.port_arrangement?.left_side?.pins.length ?? 0
const right_pins =
schematicInfo.port_arrangement?.right_side?.pins.length ?? 0

const height = Math.max(left_pins, right_pins) * pin_spacing
const width = pin_spacing * 4

schematicComponent.size = { width, height }
}

circuitJson.push(schematicComponent)

// Collect all unique port names from pads and holes
const portNames = new Set<string>()
Expand All @@ -135,12 +156,42 @@ export const convertKicadJsonToTsCircuitSoup = async (
name: portName,
port_hints: [portName],
})

const schematic_port_id = `schematic_port_${sourcePortId++}`

let center = { x: 0, y: 0 }
if (schematicComponent.port_arrangement) {
const { width, height } = schematicComponent.size
const pin_spacing = 2.54
const portNumber = parseInt(portName, 10)

const left_pins =
schematicComponent.port_arrangement.left_side?.pins ?? []
const right_pins =
schematicComponent.port_arrangement.right_side?.pins ?? []

if (left_pins.includes(portNumber)) {
const pinIndex = left_pins.indexOf(portNumber)
center = {
x: -width / 2,
y: (left_pins.length / 2 - pinIndex - 0.5) * pin_spacing,
}
}
if (right_pins.includes(portNumber)) {
const pinIndex = right_pins.indexOf(portNumber)
center = {
x: width / 2,
y: (right_pins.length / 2 - pinIndex - 0.5) * pin_spacing,
}
}
}

circuitJson.push({
type: "schematic_port",
schematic_port_id: `schematic_port_${sourcePortId++}`,
schematic_port_id,
source_port_id,
schematic_component_id: "schematic_component_0",
center: { x: 0, y: 0 },
center,
})
}

Expand Down
111 changes: 111 additions & 0 deletions src/convert-kicad-sym-to-tscircuit-schematic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { KicadSymJson, Pin } from "./kicad-zod"

interface SchematicComponent {
type: "schematic_component"
rotation: number
size: { width: number; height: number }
center: { x: number; y: number }
source_component_id: string
schematic_component_id: string
pin_spacing?: number
pin_styles?: Record<
string,
{
left_margin?: number
right_margin?: number
top_margin?: number
bottom_margin?: number
}
>
box_width?: number
symbol_name?: string
port_arrangement?:
| {
left_size: number
right_size: number
top_size?: number
bottom_size?: number
}
| {
left_side?: {
pins: number[]
direction?: "top-to-bottom" | "bottom-to-top"
}
right_side?: {
pins: number[]
direction?: "top-to-bottom" | "bottom-to-top"
}
top_side?: {
pins: number[]
direction?: "left-to-right" | "right-to-left"
}
bottom_side?: {
pins: number[]
direction?: "left-to-right" | "right-to-left"
}
}
port_labels?: Record<string, string>
}

export const convertKicadSymToTscircuitSchematic = (
kicadSymJson: KicadSymJson,
): Pick<SchematicComponent, "port_arrangement" | "port_labels"> => {
const { pins } = kicadSymJson

const port_labels: Record<string, string> = {}
for (const pin of pins) {
port_labels[pin.num] = pin.name
}

const left_pins: Pin[] = []
const right_pins: Pin[] = []
const top_pins: Pin[] = []
const bottom_pins: Pin[] = []

for (const pin of pins) {
const angle = pin.at[2]
if (angle === 0) {
right_pins.push(pin)
} else if (angle === 90) {
top_pins.push(pin)
} else if (angle === 180) {
left_pins.push(pin)
} else if (angle === 270) {
bottom_pins.push(pin)
}
}

// Sort pins by position
left_pins.sort((a, b) => a.at[1] - b.at[1]) // sort by y
right_pins.sort((a, b) => a.at[1] - b.at[1]) // sort by y
top_pins.sort((a, b) => a.at[0] - b.at[0]) // sort by x
bottom_pins.sort((a, b) => a.at[0] - b.at[0]) // sort by x

const port_arrangement: SchematicComponent["port_arrangement"] = {}

if (left_pins.length > 0) {
port_arrangement.left_side = {
pins: left_pins.map((p) => parseInt(p.num, 10)),
}
}
if (right_pins.length > 0) {
port_arrangement.right_side = {
pins: right_pins.map((p) => parseInt(p.num, 10)),
}
}
if (top_pins.length > 0) {
port_arrangement.top_side = {
pins: top_pins.map((p) => parseInt(p.num, 10)),
}
}
if (bottom_pins.length > 0) {
port_arrangement.bottom_side = {
pins: bottom_pins.map((p) => parseInt(p.num, 10)),
}
}

return {
port_arrangement,
port_labels,
}
}
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { parseKicadModToKicadJson } from "./parse-kicad-mod-to-kicad-json"
export { parseKicadModToCircuitJson } from "./parse-kicad-mod-to-circuit-json"
export { convertKicadJsonToTsCircuitSoup } from "./convert-kicad-json-to-tscircuit-soup"
export { convertKicadSymToTscircuitSchematic } from "./convert-kicad-sym-to-tscircuit-schematic"
export { parseKicadModToCircuitJson } from "./parse-kicad-mod-to-circuit-json"
export { parseKicadModToKicadJson } from "./parse-kicad-mod-to-kicad-json"
export { parseKicadSymToKicadJson } from "./parse-kicad-sym-to-kicad-json"
18 changes: 18 additions & 0 deletions src/kicad-zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,21 @@ export type FpArc = z.infer<typeof fp_arc_def>
export type FpCircle = z.infer<typeof fp_circle_def>
export type FpPoly = z.infer<typeof fp_poly_def>
export type KicadModJson = z.infer<typeof kicad_mod_json_def>

export const pin_def = z.object({
num: z.string(),
name: z.string(),
type: z.string(),
shape: z.string().optional(),
at: point3,
length: z.number(),
})

export const kicad_sym_json_def = z.object({
symbol_name: z.string(),
properties: z.array(property_def),
pins: z.array(pin_def),
})

export type Pin = z.infer<typeof pin_def>
export type KicadSymJson = z.infer<typeof kicad_sym_json_def>
8 changes: 7 additions & 1 deletion src/parse-kicad-mod-to-circuit-json.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { AnyCircuitElement } from "circuit-json"
import { parseKicadModToKicadJson } from "./parse-kicad-mod-to-kicad-json"
import { parseKicadSymToKicadJson } from "./parse-kicad-sym-to-kicad-json"
import { convertKicadJsonToTsCircuitSoup as convertKicadJsonToCircuitJson } from "./convert-kicad-json-to-tscircuit-soup"

export const parseKicadModToCircuitJson = async (
kicadMod: string,
kicadSym?: string,
): Promise<AnyCircuitElement[]> => {
const kicadJson = parseKicadModToKicadJson(kicadMod)
const kicadSymJson = kicadSym ? parseKicadSymToKicadJson(kicadSym) : undefined

const circuitJson = await convertKicadJsonToCircuitJson(kicadJson)
const circuitJson = await convertKicadJsonToCircuitJson(
kicadJson,
kicadSymJson,
)
return circuitJson as any
}
90 changes: 90 additions & 0 deletions src/parse-kicad-sym-to-kicad-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import parseSExpression from "s-expression"
import { kicad_sym_json_def, property_def, pin_def } from "./kicad-zod"
import type { KicadSymJson, Property, Pin } from "./kicad-zod"
import { formatAttr, getAttr } from "./get-attr"
import Debug from "debug"

const debug = Debug("kicad-sym-converter")

export const parseKicadSymToKicadJson = (fileContent: string): KicadSymJson => {
const kicadSExpr = parseSExpression(fileContent)

if (kicadSExpr[0].valueOf() !== "kicad_symbol_lib") {
throw new Error("Not a kicad_symbol_lib file")
}

const symbol = kicadSExpr.find(
(item: any) => Array.isArray(item) && item[0] === "symbol",
)
if (!symbol) {
throw new Error("No symbol found in kicad_sym file")
}

const symbolName = symbol[1].valueOf()

const properties = symbol
.slice(2)
.filter((row: any[]) => Array.isArray(row) && row[0] === "property")
.map((row: any) => {
const key = row[1].valueOf()
const val = row[2].valueOf()
const attributes = row.slice(3).reduce((acc: any, attrAr: any[]) => {
const attrKey = attrAr[0].valueOf()
acc[attrKey] = formatAttr(attrAr.slice(1), attrKey)
return acc
}, {} as any)

return {
key,
val,
attributes,
} as Property
})

const symbolGraphics = symbol.find(
(item: any) => Array.isArray(item) && item[0] === "symbol",
)

const pins: Array<Pin> = []
if (symbolGraphics) {
const pinRows = symbolGraphics
.slice(1)
.filter((row: any[]) => Array.isArray(row) && row[0] === "pin")

for (const row of pinRows) {
const at = getAttr(row, "at")
const length = getAttr(row, "length")
const num = getAttr(row, "num")
const name = getAttr(row, "name")
const type = getAttr(row, "type")
const shape = getAttr(row, "shape")

if (
num === undefined ||
name === undefined ||
type === undefined ||
at === undefined ||
length === undefined
) {
debug(`Skipping invalid pin: ${JSON.stringify(row)}`)
continue
}

const pinRaw = {
num: String(num),
name: String(name),
type: String(type),
shape: shape ? String(shape) : undefined,
at,
length,
}
pins.push(pin_def.parse(pinRaw))
}
}

return kicad_sym_json_def.parse({
symbol_name: symbolName,
properties,
pins,
})
}
5 changes: 4 additions & 1 deletion src/site/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export const App = () => {
setError(null)
let circuitJson: any
try {
circuitJson = await parseKicadModToCircuitJson(filesAdded.kicad_mod)
circuitJson = await parseKicadModToCircuitJson(
filesAdded.kicad_mod,
filesAdded.kicad_sym,
)
updateCircuitJson(circuitJson as any)
} catch (err: any) {
setError(`Error parsing KiCad Mod file: ${err.toString()}`)
Expand Down
25 changes: 25 additions & 0 deletions tests/__snapshots__/schematic-from-sym.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading