@tscircuit/cli 0.1.1135 → 0.1.1136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/main.js CHANGED
@@ -97682,7 +97682,7 @@ var import_perfect_cli = __toESM2(require_dist2(), 1);
97682
97682
  // lib/getVersion.ts
97683
97683
  import { createRequire as createRequire2 } from "node:module";
97684
97684
  // package.json
97685
- var version = "0.1.1134";
97685
+ var version = "0.1.1135";
97686
97686
  var package_default = {
97687
97687
  name: "@tscircuit/cli",
97688
97688
  version,
@@ -97712,7 +97712,7 @@ var package_default = {
97712
97712
  "bun-match-svg": "^0.0.12",
97713
97713
  chokidar: "4.0.1",
97714
97714
  "circuit-json": "^0.0.403",
97715
- "circuit-json-to-kicad": "^0.0.84",
97715
+ "circuit-json-to-kicad": "^0.0.85",
97716
97716
  "circuit-json-to-readable-netlist": "^0.0.15",
97717
97717
  "circuit-json-to-spice": "^0.0.10",
97718
97718
  "circuit-json-to-tscircuit": "^0.0.9",
@@ -101621,7 +101621,7 @@ import {
101621
101621
  SheetInstancesRootPage,
101622
101622
  EmbeddedFonts as EmbeddedFonts3
101623
101623
  } from "kicadts";
101624
- import { KicadPcb } from "kicadts";
101624
+ import { KicadPcb as KicadPcb2 } from "kicadts";
101625
101625
  import { cju as cju2 } from "@tscircuit/circuit-json-util";
101626
101626
  import { compose as compose4, translate as translate4, scale as scale3 } from "transformation-matrix";
101627
101627
  import {
@@ -101632,15 +101632,26 @@ import {
101632
101632
  Setup
101633
101633
  } from "kicadts";
101634
101634
  import { PcbNet } from "kicadts";
101635
- import { Footprint as Footprint2 } from "kicadts";
101636
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
101635
+ import { Footprint as Footprint3, FootprintModel as FootprintModel4 } from "kicadts";
101637
101636
  import {
101637
+ parseKicadSexpr,
101638
+ KicadPcb,
101639
+ FootprintModel,
101640
+ At,
101641
+ EmbeddedFonts as EmbeddedFonts4,
101642
+ FootprintAttr,
101638
101643
  Property,
101639
101644
  TextEffects as TextEffects6,
101640
- TextEffectsFont as TextEffectsFont6,
101641
- EmbeddedFonts as EmbeddedFonts4,
101642
- FootprintModel,
101643
- FootprintAttr
101645
+ TextEffectsFont as TextEffectsFont6
101646
+ } from "kicadts";
101647
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
101648
+ import {
101649
+ Property as Property2,
101650
+ TextEffects as TextEffects7,
101651
+ TextEffectsFont as TextEffectsFont7,
101652
+ EmbeddedFonts as EmbeddedFonts5,
101653
+ FootprintModel as FootprintModel2,
101654
+ FootprintAttr as FootprintAttr2
101644
101655
  } from "kicadts";
101645
101656
  import { FpCircle, Stroke as Stroke4 } from "kicadts";
101646
101657
  import { FpCircle as FpCircle2, Stroke as Stroke5 } from "kicadts";
@@ -101649,11 +101660,11 @@ import { FpRect as FpRect2, Stroke as Stroke7 } from "kicadts";
101649
101660
  import { FpRect as FpRect3, Stroke as Stroke8 } from "kicadts";
101650
101661
  import { FpPoly, Pts as Pts3, Xy as Xy3, Stroke as Stroke9 } from "kicadts";
101651
101662
  import"kicadts";
101652
- import { FpText, TextEffects as TextEffects7, TextEffectsFont as TextEffectsFont7 } from "kicadts";
101663
+ import { FpText, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
101653
101664
  import { applyToPoint as applyToPoint8, rotate, identity } from "transformation-matrix";
101654
- import { FpText as FpText3, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
101665
+ import { FpText as FpText3, TextEffects as TextEffects9, TextEffectsFont as TextEffectsFont9 } from "kicadts";
101655
101666
  import { applyToPoint as applyToPoint9, rotate as rotate2, identity as identity2 } from "transformation-matrix";
101656
- import { FootprintModel as FootprintModel2 } from "kicadts";
101667
+ import { FootprintModel as FootprintModel3 } from "kicadts";
101657
101668
  import {
101658
101669
  FootprintPad,
101659
101670
  PadPrimitives,
@@ -101680,36 +101691,25 @@ import { Via, ViaNet } from "kicadts";
101680
101691
  import { applyToPoint as applyToPoint15 } from "transformation-matrix";
101681
101692
  import { GrLine } from "kicadts";
101682
101693
  import {
101683
- At,
101694
+ At as At2,
101684
101695
  GrText,
101685
- TextEffects as TextEffects9,
101686
- TextEffectsFont as TextEffectsFont9,
101696
+ TextEffects as TextEffects10,
101697
+ TextEffectsFont as TextEffectsFont10,
101687
101698
  TextEffectsJustify as TextEffectsJustify3
101688
101699
  } from "kicadts";
101689
101700
  import { applyToPoint as applyToPoint16 } from "transformation-matrix";
101690
101701
  import { applyToPoint as applyToPoint18 } from "transformation-matrix";
101691
101702
  import {
101692
101703
  GrText as GrText2,
101693
- TextEffects as TextEffects10,
101694
- TextEffectsFont as TextEffectsFont10,
101704
+ TextEffects as TextEffects11,
101705
+ TextEffectsFont as TextEffectsFont11,
101695
101706
  TextEffectsJustify as TextEffectsJustify4,
101696
- At as At2
101707
+ At as At3
101697
101708
  } from "kicadts";
101698
101709
  import { applyToPoint as applyToPoint17 } from "transformation-matrix";
101699
101710
  import { cju as cju3 } from "@tscircuit/circuit-json-util";
101700
101711
  import { cju as cju4 } from "@tscircuit/circuit-json-util";
101701
- import { parseKicadSexpr, KicadSch as KicadSch2 } from "kicadts";
101702
- import {
101703
- parseKicadSexpr as parseKicadSexpr2,
101704
- KicadPcb as KicadPcb2,
101705
- FootprintModel as FootprintModel3,
101706
- At as At3,
101707
- EmbeddedFonts as EmbeddedFonts5,
101708
- FootprintAttr as FootprintAttr2,
101709
- Property as Property2,
101710
- TextEffects as TextEffects11,
101711
- TextEffectsFont as TextEffectsFont11
101712
- } from "kicadts";
101712
+ import { parseKicadSexpr as parseKicadSexpr2, KicadSch as KicadSch2 } from "kicadts";
101713
101713
  import { KicadSymbolLib } from "kicadts";
101714
101714
  import { parseKicadMod } from "kicadts";
101715
101715
  import {
@@ -103346,8 +103346,229 @@ function generateDeterministicUuid(data) {
103346
103346
  const hash = simpleHash(data);
103347
103347
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
103348
103348
  }
103349
+ var MODEL_CDN_BASE_URL = "https://modelcdn.tscircuit.com/jscad_models";
103350
+ var KICAD_FP_VERSION = 20240108;
103351
+ var KICAD_FP_GENERATOR = "pcbnew";
103352
+ var KICAD_FP_GENERATOR_VERSION = "8.0";
103353
+ function getBasename(filePath) {
103354
+ const parts = filePath.split(/[/\\]/);
103355
+ return parts[parts.length - 1] || filePath;
103356
+ }
103357
+ var ExtractFootprintsStage = class extends ConverterStage {
103358
+ classifyFootprints() {
103359
+ const customFootprintNames = /* @__PURE__ */ new Set;
103360
+ const footprinterStrings = /* @__PURE__ */ new Map;
103361
+ const cadComponents = this.ctx.db.cad_component?.list() ?? [];
103362
+ const sourceComponents = this.ctx.db.source_component;
103363
+ for (const cadComponent of cadComponents) {
103364
+ const sourceComp = cadComponent.source_component_id ? sourceComponents?.get(cadComponent.source_component_id) : null;
103365
+ if (!sourceComp)
103366
+ continue;
103367
+ const footprintName = getKicadCompatibleComponentName(sourceComp, cadComponent);
103368
+ if (cadComponent.footprinter_string) {
103369
+ footprinterStrings.set(footprintName, cadComponent.footprinter_string);
103370
+ } else {
103371
+ customFootprintNames.add(footprintName);
103372
+ const pcbComp = this.ctx.circuitJson.find((el) => el.type === "pcb_component" && el.source_component_id === cadComponent.source_component_id);
103373
+ if (pcbComp && pcbComp.type === "pcb_component" && pcbComp.metadata?.kicad_footprint?.footprintName) {
103374
+ customFootprintNames.add(pcbComp.metadata.kicad_footprint.footprintName);
103375
+ }
103376
+ }
103377
+ }
103378
+ return { customFootprintNames, footprinterStrings };
103379
+ }
103380
+ _step() {
103381
+ const kicadPcbString = this.ctx.kicadPcbString;
103382
+ const fpLibraryName = this.ctx.fpLibraryName ?? "tscircuit";
103383
+ if (!kicadPcbString) {
103384
+ throw new Error("PCB content not available. Run GenerateKicadSchAndPcbStage first.");
103385
+ }
103386
+ const { customFootprintNames, footprinterStrings } = this.classifyFootprints();
103387
+ const uniqueFootprints = /* @__PURE__ */ new Map;
103388
+ try {
103389
+ const parsed = parseKicadSexpr(kicadPcbString);
103390
+ const pcb = parsed.find((node) => node instanceof KicadPcb);
103391
+ if (!pcb) {
103392
+ this.ctx.footprintEntries = [];
103393
+ this.finished = true;
103394
+ return;
103395
+ }
103396
+ const footprints = pcb.footprints ?? [];
103397
+ for (const footprint of footprints) {
103398
+ const footprintEntry = this.sanitizeFootprint({
103399
+ footprint,
103400
+ fpLibraryName,
103401
+ customFootprintNames,
103402
+ footprinterStrings
103403
+ });
103404
+ if (!uniqueFootprints.has(footprintEntry.footprintName)) {
103405
+ uniqueFootprints.set(footprintEntry.footprintName, footprintEntry);
103406
+ }
103407
+ }
103408
+ } catch (error) {
103409
+ console.warn("Failed to parse PCB for footprint extraction:", error);
103410
+ }
103411
+ this.ctx.footprintEntries = Array.from(uniqueFootprints.values());
103412
+ this.finished = true;
103413
+ }
103414
+ sanitizeFootprint({
103415
+ footprint,
103416
+ fpLibraryName,
103417
+ customFootprintNames,
103418
+ footprinterStrings
103419
+ }) {
103420
+ const libraryLink = footprint.libraryLink ?? "footprint";
103421
+ const parts = libraryLink.split(":");
103422
+ const footprintName = (parts.length > 1 ? parts[1] : parts[0])?.replace(/[\\\/]/g, "-").trim() || "footprint";
103423
+ const isBuiltin = !customFootprintNames.has(footprintName);
103424
+ footprint.libraryLink = footprintName;
103425
+ footprint.position = At.from([0, 0, 0]);
103426
+ footprint.locked = false;
103427
+ footprint.placed = false;
103428
+ footprint.version = KICAD_FP_VERSION;
103429
+ footprint.generator = KICAD_FP_GENERATOR;
103430
+ footprint.generatorVersion = KICAD_FP_GENERATOR_VERSION;
103431
+ if (!footprint.descr) {
103432
+ footprint.descr = "";
103433
+ }
103434
+ if (!footprint.tags) {
103435
+ footprint.tags = "";
103436
+ }
103437
+ if (!footprint.embeddedFonts) {
103438
+ footprint.embeddedFonts = new EmbeddedFonts4(false);
103439
+ }
103440
+ if (!footprint.attr) {
103441
+ const attr = new FootprintAttr;
103442
+ const padTypes = (footprint.fpPads ?? []).map((pad) => pad.padType);
103443
+ if (padTypes.some((padType) => padType.includes("thru_hole"))) {
103444
+ attr.type = "through_hole";
103445
+ } else if (padTypes.some((padType) => padType.includes("smd"))) {
103446
+ attr.type = "smd";
103447
+ }
103448
+ footprint.attr = attr;
103449
+ }
103450
+ footprint.uuid = undefined;
103451
+ footprint.path = undefined;
103452
+ footprint.sheetfile = undefined;
103453
+ footprint.sheetname = undefined;
103454
+ const defaultFont = new TextEffectsFont6;
103455
+ defaultFont.size = { width: 1.27, height: 1.27 };
103456
+ defaultFont.thickness = 0.15;
103457
+ const defaultEffects = new TextEffects6({ font: defaultFont });
103458
+ const fpPads = footprint.fpPads ?? [];
103459
+ let minY = 0;
103460
+ let maxY = 0;
103461
+ for (const pad of fpPads) {
103462
+ const at = pad.at;
103463
+ const size = pad.size;
103464
+ if (at && size) {
103465
+ const padY = at.y ?? 0;
103466
+ const padHeight = size.height ?? 0;
103467
+ const padTop = padY - padHeight / 2;
103468
+ const padBottom = padY + padHeight / 2;
103469
+ minY = Math.min(minY, padTop);
103470
+ maxY = Math.max(maxY, padBottom);
103471
+ }
103472
+ }
103473
+ const refY = minY - 0.5;
103474
+ const valY = maxY + 0.5;
103475
+ footprint.properties = [
103476
+ new Property({
103477
+ key: "Reference",
103478
+ value: "REF**",
103479
+ position: [0, refY, 0],
103480
+ layer: "F.SilkS",
103481
+ uuid: generateDeterministicUuid(`${footprintName}-property-Reference`),
103482
+ effects: defaultEffects
103483
+ }),
103484
+ new Property({
103485
+ key: "Value",
103486
+ value: "Val**",
103487
+ position: [0, valY, 0],
103488
+ layer: "F.Fab",
103489
+ uuid: generateDeterministicUuid(`${footprintName}-property-Value`),
103490
+ effects: defaultEffects
103491
+ }),
103492
+ new Property({
103493
+ key: "Datasheet",
103494
+ value: "",
103495
+ position: [0, 0, 0],
103496
+ layer: "F.Fab",
103497
+ hidden: true,
103498
+ uuid: generateDeterministicUuid(`${footprintName}-property-Datasheet`),
103499
+ effects: defaultEffects
103500
+ }),
103501
+ new Property({
103502
+ key: "Description",
103503
+ value: "",
103504
+ position: [0, 0, 0],
103505
+ layer: "F.Fab",
103506
+ hidden: true,
103507
+ uuid: generateDeterministicUuid(`${footprintName}-property-Description`),
103508
+ effects: defaultEffects
103509
+ })
103510
+ ];
103511
+ const texts = footprint.fpTexts ?? [];
103512
+ for (const text of texts) {
103513
+ text.uuid = undefined;
103514
+ if (text.type === "reference") {
103515
+ text.text = "REF**";
103516
+ } else if (text.type === "value" && text.text.trim().length === 0) {
103517
+ text.text = footprintName;
103518
+ }
103519
+ }
103520
+ footprint.fpTexts = texts;
103521
+ const pads = footprint.fpPads ?? [];
103522
+ for (let i = 0;i < pads.length; i++) {
103523
+ const pad = pads[i];
103524
+ if (pad) {
103525
+ pad.uuid = generateDeterministicUuid(`${footprintName}-pad-${pad.number ?? i}`);
103526
+ pad.net = undefined;
103527
+ }
103528
+ }
103529
+ footprint.fpPads = pads;
103530
+ const models = footprint.models ?? [];
103531
+ const updatedModels = [];
103532
+ const modelFiles = [];
103533
+ for (const model of models) {
103534
+ if (model.path) {
103535
+ const modelFilename = getBasename(model.path);
103536
+ const newPath = `../../3dmodels/${fpLibraryName}.3dshapes/${modelFilename}`;
103537
+ const newModel = new FootprintModel(newPath);
103538
+ if (model.offset)
103539
+ newModel.offset = model.offset;
103540
+ if (model.scale)
103541
+ newModel.scale = model.scale;
103542
+ if (model.rotate)
103543
+ newModel.rotate = model.rotate;
103544
+ updatedModels.push(newModel);
103545
+ modelFiles.push(model.path);
103546
+ }
103547
+ }
103548
+ if (updatedModels.length === 0) {
103549
+ const footprinterString = footprinterStrings.get(footprintName);
103550
+ if (footprinterString) {
103551
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinterString}.step`;
103552
+ const cdnModelFilename = getBasename(cdnUrl);
103553
+ const newPath = `../../3dmodels/tscircuit_builtin.3dshapes/${cdnModelFilename}`;
103554
+ updatedModels.push(new FootprintModel(newPath));
103555
+ modelFiles.push(cdnUrl);
103556
+ }
103557
+ }
103558
+ footprint.models = updatedModels;
103559
+ return {
103560
+ footprintName,
103561
+ kicadModString: footprint.getString(),
103562
+ model3dSourcePaths: modelFiles,
103563
+ isBuiltin
103564
+ };
103565
+ }
103566
+ getOutput() {
103567
+ return this.ctx.libraryOutput;
103568
+ }
103569
+ };
103349
103570
  function createTextEffects2(metadataEffects) {
103350
- const font = new TextEffectsFont6;
103571
+ const font = new TextEffectsFont7;
103351
103572
  if (metadataEffects?.font?.size) {
103352
103573
  font.size = {
103353
103574
  width: Number(metadataEffects.font.size.x),
@@ -103361,13 +103582,13 @@ function createTextEffects2(metadataEffects) {
103361
103582
  } else {
103362
103583
  font.thickness = 0.15;
103363
103584
  }
103364
- return new TextEffects6({ font });
103585
+ return new TextEffects7({ font });
103365
103586
  }
103366
103587
  function applyMetadataToFootprint(footprint, metadata, componentName) {
103367
103588
  if (metadata.properties) {
103368
103589
  const newProperties = [];
103369
103590
  const refMeta = metadata.properties.Reference;
103370
- newProperties.push(new Property({
103591
+ newProperties.push(new Property2({
103371
103592
  key: "Reference",
103372
103593
  value: refMeta?.value ?? componentName,
103373
103594
  position: refMeta?.at ? [
@@ -103382,7 +103603,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103382
103603
  }));
103383
103604
  const valMeta = metadata.properties.Value;
103384
103605
  const valueText = valMeta?.value ?? metadata.footprintName ?? "";
103385
- newProperties.push(new Property({
103606
+ newProperties.push(new Property2({
103386
103607
  key: "Value",
103387
103608
  value: valueText,
103388
103609
  position: valMeta?.at ? [
@@ -103396,7 +103617,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103396
103617
  hidden: valMeta?.hide
103397
103618
  }));
103398
103619
  const dsMeta = metadata.properties.Datasheet;
103399
- newProperties.push(new Property({
103620
+ newProperties.push(new Property2({
103400
103621
  key: "Datasheet",
103401
103622
  value: dsMeta?.value ?? "",
103402
103623
  position: dsMeta?.at ? [
@@ -103410,7 +103631,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103410
103631
  hidden: dsMeta?.hide ?? true
103411
103632
  }));
103412
103633
  const descMeta = metadata.properties.Description;
103413
- newProperties.push(new Property({
103634
+ newProperties.push(new Property2({
103414
103635
  key: "Description",
103415
103636
  value: descMeta?.value ?? "",
103416
103637
  position: descMeta?.at ? [
@@ -103427,7 +103648,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103427
103648
  }
103428
103649
  if (metadata.attributes) {
103429
103650
  if (!footprint.attr) {
103430
- footprint.attr = new FootprintAttr;
103651
+ footprint.attr = new FootprintAttr2;
103431
103652
  }
103432
103653
  if (metadata.attributes.through_hole) {
103433
103654
  footprint.attr.type = "through_hole";
@@ -103448,10 +103669,10 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103448
103669
  footprint.layer = metadata.layer;
103449
103670
  }
103450
103671
  if (metadata.embeddedFonts !== undefined) {
103451
- footprint.embeddedFonts = new EmbeddedFonts4(metadata.embeddedFonts);
103672
+ footprint.embeddedFonts = new EmbeddedFonts5(metadata.embeddedFonts);
103452
103673
  }
103453
103674
  if (metadata.model) {
103454
- const model = new FootprintModel(metadata.model.path);
103675
+ const model = new FootprintModel2(metadata.model.path);
103455
103676
  if (metadata.model.offset) {
103456
103677
  model.offset = {
103457
103678
  x: Number(metadata.model.offset.x),
@@ -103656,9 +103877,9 @@ function createFpTextFromCircuitJson({
103656
103877
  };
103657
103878
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
103658
103879
  const fontSize = (textElement.font_size || 1) / 1.5;
103659
- const font = new TextEffectsFont7;
103880
+ const font = new TextEffectsFont8;
103660
103881
  font.size = { width: fontSize, height: fontSize };
103661
- const textEffects = new TextEffects7({
103882
+ const textEffects = new TextEffects8({
103662
103883
  font
103663
103884
  });
103664
103885
  const rotation = textElement.ccw_rotation || 0;
@@ -103699,9 +103920,9 @@ function convertNoteTexts(noteTexts, componentCenter, componentRotation) {
103699
103920
  const rotationMatrix = componentRotation !== 0 ? rotate2(componentRotation * Math.PI / 180) : identity2();
103700
103921
  const rotatedPos = applyToPoint9(rotationMatrix, { x: relX, y: relY });
103701
103922
  const fontSize = textElement.font_size || 1;
103702
- const font = new TextEffectsFont8;
103923
+ const font = new TextEffectsFont9;
103703
103924
  font.size = { width: fontSize, height: fontSize };
103704
- const textEffects = new TextEffects8({ font });
103925
+ const textEffects = new TextEffects9({ font });
103705
103926
  const fpText = new FpText3({
103706
103927
  type: "user",
103707
103928
  text: textElement.text,
@@ -103718,7 +103939,7 @@ function create3DModelsFromCadComponent(cadComponent, componentCenter) {
103718
103939
  const modelUrl = cadComponent.model_step_url || cadComponent.model_wrl_url;
103719
103940
  if (!modelUrl)
103720
103941
  return models;
103721
- const model = new FootprintModel2(modelUrl);
103942
+ const model = new FootprintModel3(modelUrl);
103722
103943
  if (cadComponent.position) {
103723
103944
  model.offset = {
103724
103945
  x: (cadComponent.position.x || 0) - componentCenter.x,
@@ -104050,6 +104271,7 @@ function convertNpthHoles(pcbHoles, componentCenter, componentRotation) {
104050
104271
  var AddFootprintsStage = class extends ConverterStage {
104051
104272
  componentsProcessed = 0;
104052
104273
  pcbComponents = [];
104274
+ includeBuiltin3dModels;
104053
104275
  getNetInfoForPcbPort(pcbPortId) {
104054
104276
  if (!pcbPortId)
104055
104277
  return;
@@ -104071,9 +104293,10 @@ var AddFootprintsStage = class extends ConverterStage {
104071
104293
  const cadComponents = this.ctx.db.cad_component?.list() || [];
104072
104294
  return cadComponents.find((cad) => cad.pcb_component_id === pcbComponentId);
104073
104295
  }
104074
- constructor(input, ctx) {
104296
+ constructor(input, ctx, options) {
104075
104297
  super(input, ctx);
104076
104298
  this.pcbComponents = this.ctx.db.pcb_component.list();
104299
+ this.includeBuiltin3dModels = options?.includeBuiltin3dModels ?? false;
104077
104300
  }
104078
104301
  _step() {
104079
104302
  const { kicadPcb, c2kMatPcb } = this.ctx;
@@ -104096,7 +104319,7 @@ var AddFootprintsStage = class extends ConverterStage {
104096
104319
  y: component.center.y
104097
104320
  });
104098
104321
  const footprintData = `footprint:${component.pcb_component_id}:${transformedPos.x},${transformedPos.y}`;
104099
- const footprint = new Footprint2({
104322
+ const footprint = new Footprint3({
104100
104323
  libraryLink: `tscircuit:${footprintName}`,
104101
104324
  layer: "F.Cu",
104102
104325
  at: [transformedPos.x, transformedPos.y, component.rotation || 0],
@@ -104141,8 +104364,37 @@ var AddFootprintsStage = class extends ConverterStage {
104141
104364
  }
104142
104365
  if (cadComponent) {
104143
104366
  const models = create3DModelsFromCadComponent(cadComponent, component.center);
104367
+ const KICAD_3D_BASE = "${KIPRJMOD}/3dmodels";
104144
104368
  if (models.length > 0) {
104145
- footprint.models = models;
104369
+ if (this.includeBuiltin3dModels) {
104370
+ footprint.models = models.map((model) => {
104371
+ if (!model.path)
104372
+ return model;
104373
+ const filename = getBasename(model.path);
104374
+ const folderName = this.ctx.projectName ?? filename.replace(/\.[^.]+$/, "");
104375
+ const newModel = new FootprintModel4(`${KICAD_3D_BASE}/${folderName}.3dshapes/${filename}`);
104376
+ if (model.offset)
104377
+ newModel.offset = model.offset;
104378
+ if (model.scale)
104379
+ newModel.scale = model.scale;
104380
+ if (model.rotate)
104381
+ newModel.rotate = model.rotate;
104382
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(model.path)) {
104383
+ this.ctx.pcbModel3dSourcePaths?.push(model.path);
104384
+ }
104385
+ return newModel;
104386
+ });
104387
+ } else {
104388
+ footprint.models = models;
104389
+ }
104390
+ } else if (cadComponent.footprinter_string && this.includeBuiltin3dModels) {
104391
+ const { footprinter_string } = cadComponent;
104392
+ const modelPath = `${KICAD_3D_BASE}/tscircuit_builtin.3dshapes/${footprinter_string}.step`;
104393
+ footprint.models = [new FootprintModel4(modelPath)];
104394
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinter_string}.step`;
104395
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(cdnUrl)) {
104396
+ this.ctx.pcbModel3dSourcePaths?.push(cdnUrl);
104397
+ }
104146
104398
  }
104147
104399
  }
104148
104400
  const footprintMetadata = component.metadata?.kicad_footprint;
@@ -104371,7 +104623,7 @@ function createFabricationNoteTextFromCircuitJson({
104371
104623
  };
104372
104624
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.Fab";
104373
104625
  const fontSize = (textElement.font_size || 1) / 1.5;
104374
- const font = new TextEffectsFont9;
104626
+ const font = new TextEffectsFont10;
104375
104627
  font.size = { width: fontSize, height: fontSize };
104376
104628
  const justify = new TextEffectsJustify3;
104377
104629
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -104395,13 +104647,13 @@ function createFabricationNoteTextFromCircuitJson({
104395
104647
  case "center":
104396
104648
  break;
104397
104649
  }
104398
- const textEffects = new TextEffects9({
104650
+ const textEffects = new TextEffects10({
104399
104651
  font
104400
104652
  });
104401
104653
  if (anchorAlignment !== "center") {
104402
104654
  textEffects.justify = justify;
104403
104655
  }
104404
- const position = new At([transformedPos.x, transformedPos.y, 0]);
104656
+ const position = new At2([transformedPos.x, transformedPos.y, 0]);
104405
104657
  const grText = new GrText({
104406
104658
  text: textElement.text,
104407
104659
  layer: kicadLayer,
@@ -104427,7 +104679,7 @@ function createGrTextFromCircuitJson({
104427
104679
  };
104428
104680
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
104429
104681
  const fontSize = (textElement.font_size || 1) / 1.5;
104430
- const font = new TextEffectsFont10;
104682
+ const font = new TextEffectsFont11;
104431
104683
  font.size = { width: fontSize, height: fontSize };
104432
104684
  const justify = new TextEffectsJustify4;
104433
104685
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -104451,14 +104703,14 @@ function createGrTextFromCircuitJson({
104451
104703
  case "center":
104452
104704
  break;
104453
104705
  }
104454
- const textEffects = new TextEffects10({
104706
+ const textEffects = new TextEffects11({
104455
104707
  font
104456
104708
  });
104457
104709
  if (anchorAlignment !== "center") {
104458
104710
  textEffects.justify = justify;
104459
104711
  }
104460
104712
  const rotation = textElement.ccw_rotation || 0;
104461
- const position = new At2([transformedPos.x, transformedPos.y, rotation]);
104713
+ const position = new At3([transformedPos.x, transformedPos.y, rotation]);
104462
104714
  const grText = new GrText2({
104463
104715
  text: textElement.text,
104464
104716
  layer: kicadLayer,
@@ -104584,23 +104836,27 @@ var CircuitJsonToKicadPcbConverter = class {
104584
104836
  get currentStage() {
104585
104837
  return this.pipeline[this.currentStageIndex];
104586
104838
  }
104587
- constructor(circuitJson) {
104839
+ constructor(circuitJson, options) {
104588
104840
  const CIRCUIT_JSON_TO_MM_SCALE = 1;
104589
104841
  const KICAD_PCB_CENTER_X = 100;
104590
104842
  const KICAD_PCB_CENTER_Y = 100;
104591
104843
  this.ctx = {
104592
104844
  db: cju2(circuitJson),
104593
104845
  circuitJson,
104594
- kicadPcb: new KicadPcb({
104846
+ kicadPcb: new KicadPcb2({
104595
104847
  generator: "circuit-json-to-kicad",
104596
104848
  generatorVersion: "0.0.1"
104597
104849
  }),
104598
- c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE))
104850
+ c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE)),
104851
+ projectName: options?.projectName,
104852
+ pcbModel3dSourcePaths: []
104599
104853
  };
104600
104854
  this.pipeline = [
104601
104855
  new InitializePcbStage(circuitJson, this.ctx),
104602
104856
  new AddNetsStage(circuitJson, this.ctx),
104603
- new AddFootprintsStage(circuitJson, this.ctx),
104857
+ new AddFootprintsStage(circuitJson, this.ctx, {
104858
+ includeBuiltin3dModels: options?.includeBuiltin3dModels
104859
+ }),
104604
104860
  new AddTracesStage(circuitJson, this.ctx),
104605
104861
  new AddViasStage(circuitJson, this.ctx),
104606
104862
  new AddGraphicsStage(circuitJson, this.ctx)
@@ -104627,6 +104883,9 @@ var CircuitJsonToKicadPcbConverter = class {
104627
104883
  getOutputString() {
104628
104884
  return this.ctx.kicadPcb.getString();
104629
104885
  }
104886
+ getModel3dSourcePaths() {
104887
+ return this.ctx.pcbModel3dSourcePaths ?? [];
104888
+ }
104630
104889
  };
104631
104890
  var CircuitJsonToKicadProConverter = class {
104632
104891
  ctx;
@@ -104731,7 +104990,7 @@ var ExtractSymbolsStage = class extends ConverterStage {
104731
104990
  }
104732
104991
  const uniqueSymbols = /* @__PURE__ */ new Map;
104733
104992
  try {
104734
- const parsed = parseKicadSexpr(schContent);
104993
+ const parsed = parseKicadSexpr2(schContent);
104735
104994
  const sch = parsed.find((node) => node instanceof KicadSch2);
104736
104995
  if (!sch) {
104737
104996
  this.ctx.symbolEntries = [];
@@ -104812,227 +105071,6 @@ var ExtractSymbolsStage = class extends ConverterStage {
104812
105071
  return this.ctx.libraryOutput;
104813
105072
  }
104814
105073
  };
104815
- var MODEL_CDN_BASE_URL = "https://modelcdn.tscircuit.com/jscad_models";
104816
- var KICAD_FP_VERSION = 20240108;
104817
- var KICAD_FP_GENERATOR = "pcbnew";
104818
- var KICAD_FP_GENERATOR_VERSION = "8.0";
104819
- function getBasename(filePath) {
104820
- const parts = filePath.split(/[/\\]/);
104821
- return parts[parts.length - 1] || filePath;
104822
- }
104823
- var ExtractFootprintsStage = class extends ConverterStage {
104824
- classifyFootprints() {
104825
- const customFootprintNames = /* @__PURE__ */ new Set;
104826
- const footprinterStrings = /* @__PURE__ */ new Map;
104827
- const cadComponents = this.ctx.db.cad_component?.list() ?? [];
104828
- const sourceComponents = this.ctx.db.source_component;
104829
- for (const cadComponent of cadComponents) {
104830
- const sourceComp = cadComponent.source_component_id ? sourceComponents?.get(cadComponent.source_component_id) : null;
104831
- if (!sourceComp)
104832
- continue;
104833
- const footprintName = getKicadCompatibleComponentName(sourceComp, cadComponent);
104834
- if (cadComponent.footprinter_string) {
104835
- footprinterStrings.set(footprintName, cadComponent.footprinter_string);
104836
- } else {
104837
- customFootprintNames.add(footprintName);
104838
- const pcbComp = this.ctx.circuitJson.find((el) => el.type === "pcb_component" && el.source_component_id === cadComponent.source_component_id);
104839
- if (pcbComp && pcbComp.type === "pcb_component" && pcbComp.metadata?.kicad_footprint?.footprintName) {
104840
- customFootprintNames.add(pcbComp.metadata.kicad_footprint.footprintName);
104841
- }
104842
- }
104843
- }
104844
- return { customFootprintNames, footprinterStrings };
104845
- }
104846
- _step() {
104847
- const kicadPcbString = this.ctx.kicadPcbString;
104848
- const fpLibraryName = this.ctx.fpLibraryName ?? "tscircuit";
104849
- if (!kicadPcbString) {
104850
- throw new Error("PCB content not available. Run GenerateKicadSchAndPcbStage first.");
104851
- }
104852
- const { customFootprintNames, footprinterStrings } = this.classifyFootprints();
104853
- const uniqueFootprints = /* @__PURE__ */ new Map;
104854
- try {
104855
- const parsed = parseKicadSexpr2(kicadPcbString);
104856
- const pcb = parsed.find((node) => node instanceof KicadPcb2);
104857
- if (!pcb) {
104858
- this.ctx.footprintEntries = [];
104859
- this.finished = true;
104860
- return;
104861
- }
104862
- const footprints = pcb.footprints ?? [];
104863
- for (const footprint of footprints) {
104864
- const footprintEntry = this.sanitizeFootprint({
104865
- footprint,
104866
- fpLibraryName,
104867
- customFootprintNames,
104868
- footprinterStrings
104869
- });
104870
- if (!uniqueFootprints.has(footprintEntry.footprintName)) {
104871
- uniqueFootprints.set(footprintEntry.footprintName, footprintEntry);
104872
- }
104873
- }
104874
- } catch (error) {
104875
- console.warn("Failed to parse PCB for footprint extraction:", error);
104876
- }
104877
- this.ctx.footprintEntries = Array.from(uniqueFootprints.values());
104878
- this.finished = true;
104879
- }
104880
- sanitizeFootprint({
104881
- footprint,
104882
- fpLibraryName,
104883
- customFootprintNames,
104884
- footprinterStrings
104885
- }) {
104886
- const libraryLink = footprint.libraryLink ?? "footprint";
104887
- const parts = libraryLink.split(":");
104888
- const footprintName = (parts.length > 1 ? parts[1] : parts[0])?.replace(/[\\\/]/g, "-").trim() || "footprint";
104889
- const isBuiltin = !customFootprintNames.has(footprintName);
104890
- footprint.libraryLink = footprintName;
104891
- footprint.position = At3.from([0, 0, 0]);
104892
- footprint.locked = false;
104893
- footprint.placed = false;
104894
- footprint.version = KICAD_FP_VERSION;
104895
- footprint.generator = KICAD_FP_GENERATOR;
104896
- footprint.generatorVersion = KICAD_FP_GENERATOR_VERSION;
104897
- if (!footprint.descr) {
104898
- footprint.descr = "";
104899
- }
104900
- if (!footprint.tags) {
104901
- footprint.tags = "";
104902
- }
104903
- if (!footprint.embeddedFonts) {
104904
- footprint.embeddedFonts = new EmbeddedFonts5(false);
104905
- }
104906
- if (!footprint.attr) {
104907
- const attr = new FootprintAttr2;
104908
- const padTypes = (footprint.fpPads ?? []).map((pad) => pad.padType);
104909
- if (padTypes.some((padType) => padType.includes("thru_hole"))) {
104910
- attr.type = "through_hole";
104911
- } else if (padTypes.some((padType) => padType.includes("smd"))) {
104912
- attr.type = "smd";
104913
- }
104914
- footprint.attr = attr;
104915
- }
104916
- footprint.uuid = undefined;
104917
- footprint.path = undefined;
104918
- footprint.sheetfile = undefined;
104919
- footprint.sheetname = undefined;
104920
- const defaultFont = new TextEffectsFont11;
104921
- defaultFont.size = { width: 1.27, height: 1.27 };
104922
- defaultFont.thickness = 0.15;
104923
- const defaultEffects = new TextEffects11({ font: defaultFont });
104924
- const fpPads = footprint.fpPads ?? [];
104925
- let minY = 0;
104926
- let maxY = 0;
104927
- for (const pad of fpPads) {
104928
- const at = pad.at;
104929
- const size = pad.size;
104930
- if (at && size) {
104931
- const padY = at.y ?? 0;
104932
- const padHeight = size.height ?? 0;
104933
- const padTop = padY - padHeight / 2;
104934
- const padBottom = padY + padHeight / 2;
104935
- minY = Math.min(minY, padTop);
104936
- maxY = Math.max(maxY, padBottom);
104937
- }
104938
- }
104939
- const refY = minY - 0.5;
104940
- const valY = maxY + 0.5;
104941
- footprint.properties = [
104942
- new Property2({
104943
- key: "Reference",
104944
- value: "REF**",
104945
- position: [0, refY, 0],
104946
- layer: "F.SilkS",
104947
- uuid: generateDeterministicUuid(`${footprintName}-property-Reference`),
104948
- effects: defaultEffects
104949
- }),
104950
- new Property2({
104951
- key: "Value",
104952
- value: "Val**",
104953
- position: [0, valY, 0],
104954
- layer: "F.Fab",
104955
- uuid: generateDeterministicUuid(`${footprintName}-property-Value`),
104956
- effects: defaultEffects
104957
- }),
104958
- new Property2({
104959
- key: "Datasheet",
104960
- value: "",
104961
- position: [0, 0, 0],
104962
- layer: "F.Fab",
104963
- hidden: true,
104964
- uuid: generateDeterministicUuid(`${footprintName}-property-Datasheet`),
104965
- effects: defaultEffects
104966
- }),
104967
- new Property2({
104968
- key: "Description",
104969
- value: "",
104970
- position: [0, 0, 0],
104971
- layer: "F.Fab",
104972
- hidden: true,
104973
- uuid: generateDeterministicUuid(`${footprintName}-property-Description`),
104974
- effects: defaultEffects
104975
- })
104976
- ];
104977
- const texts = footprint.fpTexts ?? [];
104978
- for (const text of texts) {
104979
- text.uuid = undefined;
104980
- if (text.type === "reference") {
104981
- text.text = "REF**";
104982
- } else if (text.type === "value" && text.text.trim().length === 0) {
104983
- text.text = footprintName;
104984
- }
104985
- }
104986
- footprint.fpTexts = texts;
104987
- const pads = footprint.fpPads ?? [];
104988
- for (let i = 0;i < pads.length; i++) {
104989
- const pad = pads[i];
104990
- if (pad) {
104991
- pad.uuid = generateDeterministicUuid(`${footprintName}-pad-${pad.number ?? i}`);
104992
- pad.net = undefined;
104993
- }
104994
- }
104995
- footprint.fpPads = pads;
104996
- const models = footprint.models ?? [];
104997
- const updatedModels = [];
104998
- const modelFiles = [];
104999
- for (const model of models) {
105000
- if (model.path) {
105001
- const modelFilename = getBasename(model.path);
105002
- const newPath = `../../3dmodels/${fpLibraryName}.3dshapes/${modelFilename}`;
105003
- const newModel = new FootprintModel3(newPath);
105004
- if (model.offset)
105005
- newModel.offset = model.offset;
105006
- if (model.scale)
105007
- newModel.scale = model.scale;
105008
- if (model.rotate)
105009
- newModel.rotate = model.rotate;
105010
- updatedModels.push(newModel);
105011
- modelFiles.push(model.path);
105012
- }
105013
- }
105014
- if (updatedModels.length === 0) {
105015
- const footprinterString = footprinterStrings.get(footprintName);
105016
- if (footprinterString) {
105017
- const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinterString}.step`;
105018
- const cdnModelFilename = getBasename(cdnUrl);
105019
- const newPath = `../../3dmodels/tscircuit_builtin.3dshapes/${cdnModelFilename}`;
105020
- updatedModels.push(new FootprintModel3(newPath));
105021
- modelFiles.push(cdnUrl);
105022
- }
105023
- }
105024
- footprint.models = updatedModels;
105025
- return {
105026
- footprintName,
105027
- kicadModString: footprint.getString(),
105028
- model3dSourcePaths: modelFiles,
105029
- isBuiltin
105030
- };
105031
- }
105032
- getOutput() {
105033
- return this.ctx.libraryOutput;
105034
- }
105035
- };
105036
105074
  var KICAD_SYM_LIB_VERSION = 20211014;
105037
105075
  var GENERATOR = "circuit-json-to-kicad";
105038
105076
  var GenerateSymbolLibraryStage = class extends ConverterStage {
@@ -116291,7 +116329,10 @@ var exportSnippet = async ({
116291
116329
  case "kicad_zip": {
116292
116330
  const schConverter = new CircuitJsonToKicadSchConverter(circuitJson);
116293
116331
  schConverter.runUntilFinished();
116294
- const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson);
116332
+ const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson, {
116333
+ includeBuiltin3dModels: true,
116334
+ projectName: outputBaseName
116335
+ });
116295
116336
  pcbConverter.runUntilFinished();
116296
116337
  const proConverter = new CircuitJsonToKicadProConverter(circuitJson, {
116297
116338
  projectName: outputBaseName,
@@ -116303,6 +116344,29 @@ var exportSnippet = async ({
116303
116344
  zip.file(`${outputBaseName}.kicad_sch`, schConverter.getOutputString());
116304
116345
  zip.file(`${outputBaseName}.kicad_pcb`, pcbConverter.getOutputString());
116305
116346
  zip.file(`${outputBaseName}.kicad_pro`, proConverter.getOutputString());
116347
+ const platformFetch = platformConfig?.platformFetch ?? globalThis.fetch;
116348
+ for (const modelPath of pcbConverter.getModel3dSourcePaths()) {
116349
+ const fileName = path56.basename(modelPath);
116350
+ const isRemote = modelPath.startsWith("http://") || modelPath.startsWith("https://");
116351
+ const shapesDir = isRemote ? "tscircuit_builtin.3dshapes" : `${outputBaseName}.3dshapes`;
116352
+ const zipPath = `3dmodels/${shapesDir}/${fileName}`;
116353
+ if (isRemote) {
116354
+ try {
116355
+ const response = await platformFetch(modelPath);
116356
+ if (response.ok) {
116357
+ const buffer = Buffer.from(await response.arrayBuffer());
116358
+ zip.file(zipPath, buffer);
116359
+ }
116360
+ } catch (error) {
116361
+ console.log(`Failed to fetch 3D model from ${modelPath}`);
116362
+ }
116363
+ } else {
116364
+ const resolvedPath = path56.resolve(projectDir, modelPath);
116365
+ if (fs52.existsSync(resolvedPath)) {
116366
+ zip.file(zipPath, await fs52.promises.readFile(resolvedPath));
116367
+ }
116368
+ }
116369
+ }
116306
116370
  outputContent = await zip.generateAsync({ type: "nodebuffer" });
116307
116371
  break;
116308
116372
  }