@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/lib/index.js CHANGED
@@ -60445,7 +60445,7 @@ var getNodeHandler = (winterSpec, { port, middleware = [] }) => {
60445
60445
  }));
60446
60446
  };
60447
60447
  // package.json
60448
- var version = "0.1.1134";
60448
+ var version = "0.1.1135";
60449
60449
  var package_default = {
60450
60450
  name: "@tscircuit/cli",
60451
60451
  version,
@@ -60475,7 +60475,7 @@ var package_default = {
60475
60475
  "bun-match-svg": "^0.0.12",
60476
60476
  chokidar: "4.0.1",
60477
60477
  "circuit-json": "^0.0.403",
60478
- "circuit-json-to-kicad": "^0.0.84",
60478
+ "circuit-json-to-kicad": "^0.0.85",
60479
60479
  "circuit-json-to-readable-netlist": "^0.0.15",
60480
60480
  "circuit-json-to-spice": "^0.0.10",
60481
60481
  "circuit-json-to-tscircuit": "^0.0.9",
@@ -63107,7 +63107,7 @@ import {
63107
63107
  SheetInstancesRootPage,
63108
63108
  EmbeddedFonts as EmbeddedFonts3
63109
63109
  } from "kicadts";
63110
- import { KicadPcb } from "kicadts";
63110
+ import { KicadPcb as KicadPcb2 } from "kicadts";
63111
63111
  import { cju as cju2 } from "@tscircuit/circuit-json-util";
63112
63112
  import { compose as compose4, translate as translate4, scale as scale3 } from "transformation-matrix";
63113
63113
  import {
@@ -63118,15 +63118,26 @@ import {
63118
63118
  Setup
63119
63119
  } from "kicadts";
63120
63120
  import { PcbNet } from "kicadts";
63121
- import { Footprint as Footprint2 } from "kicadts";
63122
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
63121
+ import { Footprint as Footprint3, FootprintModel as FootprintModel4 } from "kicadts";
63123
63122
  import {
63123
+ parseKicadSexpr,
63124
+ KicadPcb,
63125
+ FootprintModel,
63126
+ At,
63127
+ EmbeddedFonts as EmbeddedFonts4,
63128
+ FootprintAttr,
63124
63129
  Property,
63125
63130
  TextEffects as TextEffects6,
63126
- TextEffectsFont as TextEffectsFont6,
63127
- EmbeddedFonts as EmbeddedFonts4,
63128
- FootprintModel,
63129
- FootprintAttr
63131
+ TextEffectsFont as TextEffectsFont6
63132
+ } from "kicadts";
63133
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
63134
+ import {
63135
+ Property as Property2,
63136
+ TextEffects as TextEffects7,
63137
+ TextEffectsFont as TextEffectsFont7,
63138
+ EmbeddedFonts as EmbeddedFonts5,
63139
+ FootprintModel as FootprintModel2,
63140
+ FootprintAttr as FootprintAttr2
63130
63141
  } from "kicadts";
63131
63142
  import { FpCircle, Stroke as Stroke4 } from "kicadts";
63132
63143
  import { FpCircle as FpCircle2, Stroke as Stroke5 } from "kicadts";
@@ -63135,11 +63146,11 @@ import { FpRect as FpRect2, Stroke as Stroke7 } from "kicadts";
63135
63146
  import { FpRect as FpRect3, Stroke as Stroke8 } from "kicadts";
63136
63147
  import { FpPoly, Pts as Pts3, Xy as Xy3, Stroke as Stroke9 } from "kicadts";
63137
63148
  import"kicadts";
63138
- import { FpText, TextEffects as TextEffects7, TextEffectsFont as TextEffectsFont7 } from "kicadts";
63149
+ import { FpText, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
63139
63150
  import { applyToPoint as applyToPoint8, rotate, identity } from "transformation-matrix";
63140
- import { FpText as FpText3, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
63151
+ import { FpText as FpText3, TextEffects as TextEffects9, TextEffectsFont as TextEffectsFont9 } from "kicadts";
63141
63152
  import { applyToPoint as applyToPoint9, rotate as rotate2, identity as identity2 } from "transformation-matrix";
63142
- import { FootprintModel as FootprintModel2 } from "kicadts";
63153
+ import { FootprintModel as FootprintModel3 } from "kicadts";
63143
63154
  import {
63144
63155
  FootprintPad,
63145
63156
  PadPrimitives,
@@ -63166,36 +63177,25 @@ import { Via, ViaNet } from "kicadts";
63166
63177
  import { applyToPoint as applyToPoint15 } from "transformation-matrix";
63167
63178
  import { GrLine } from "kicadts";
63168
63179
  import {
63169
- At,
63180
+ At as At2,
63170
63181
  GrText,
63171
- TextEffects as TextEffects9,
63172
- TextEffectsFont as TextEffectsFont9,
63182
+ TextEffects as TextEffects10,
63183
+ TextEffectsFont as TextEffectsFont10,
63173
63184
  TextEffectsJustify as TextEffectsJustify3
63174
63185
  } from "kicadts";
63175
63186
  import { applyToPoint as applyToPoint16 } from "transformation-matrix";
63176
63187
  import { applyToPoint as applyToPoint18 } from "transformation-matrix";
63177
63188
  import {
63178
63189
  GrText as GrText2,
63179
- TextEffects as TextEffects10,
63180
- TextEffectsFont as TextEffectsFont10,
63190
+ TextEffects as TextEffects11,
63191
+ TextEffectsFont as TextEffectsFont11,
63181
63192
  TextEffectsJustify as TextEffectsJustify4,
63182
- At as At2
63193
+ At as At3
63183
63194
  } from "kicadts";
63184
63195
  import { applyToPoint as applyToPoint17 } from "transformation-matrix";
63185
63196
  import { cju as cju3 } from "@tscircuit/circuit-json-util";
63186
63197
  import { cju as cju4 } from "@tscircuit/circuit-json-util";
63187
- import { parseKicadSexpr, KicadSch as KicadSch2 } from "kicadts";
63188
- import {
63189
- parseKicadSexpr as parseKicadSexpr2,
63190
- KicadPcb as KicadPcb2,
63191
- FootprintModel as FootprintModel3,
63192
- At as At3,
63193
- EmbeddedFonts as EmbeddedFonts5,
63194
- FootprintAttr as FootprintAttr2,
63195
- Property as Property2,
63196
- TextEffects as TextEffects11,
63197
- TextEffectsFont as TextEffectsFont11
63198
- } from "kicadts";
63198
+ import { parseKicadSexpr as parseKicadSexpr2, KicadSch as KicadSch2 } from "kicadts";
63199
63199
  import { KicadSymbolLib } from "kicadts";
63200
63200
  import { parseKicadMod } from "kicadts";
63201
63201
  import {
@@ -64832,8 +64832,229 @@ function generateDeterministicUuid(data) {
64832
64832
  const hash = simpleHash(data);
64833
64833
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
64834
64834
  }
64835
+ var MODEL_CDN_BASE_URL = "https://modelcdn.tscircuit.com/jscad_models";
64836
+ var KICAD_FP_VERSION = 20240108;
64837
+ var KICAD_FP_GENERATOR = "pcbnew";
64838
+ var KICAD_FP_GENERATOR_VERSION = "8.0";
64839
+ function getBasename(filePath) {
64840
+ const parts = filePath.split(/[/\\]/);
64841
+ return parts[parts.length - 1] || filePath;
64842
+ }
64843
+ var ExtractFootprintsStage = class extends ConverterStage {
64844
+ classifyFootprints() {
64845
+ const customFootprintNames = /* @__PURE__ */ new Set;
64846
+ const footprinterStrings = /* @__PURE__ */ new Map;
64847
+ const cadComponents = this.ctx.db.cad_component?.list() ?? [];
64848
+ const sourceComponents = this.ctx.db.source_component;
64849
+ for (const cadComponent of cadComponents) {
64850
+ const sourceComp = cadComponent.source_component_id ? sourceComponents?.get(cadComponent.source_component_id) : null;
64851
+ if (!sourceComp)
64852
+ continue;
64853
+ const footprintName = getKicadCompatibleComponentName(sourceComp, cadComponent);
64854
+ if (cadComponent.footprinter_string) {
64855
+ footprinterStrings.set(footprintName, cadComponent.footprinter_string);
64856
+ } else {
64857
+ customFootprintNames.add(footprintName);
64858
+ const pcbComp = this.ctx.circuitJson.find((el) => el.type === "pcb_component" && el.source_component_id === cadComponent.source_component_id);
64859
+ if (pcbComp && pcbComp.type === "pcb_component" && pcbComp.metadata?.kicad_footprint?.footprintName) {
64860
+ customFootprintNames.add(pcbComp.metadata.kicad_footprint.footprintName);
64861
+ }
64862
+ }
64863
+ }
64864
+ return { customFootprintNames, footprinterStrings };
64865
+ }
64866
+ _step() {
64867
+ const kicadPcbString = this.ctx.kicadPcbString;
64868
+ const fpLibraryName = this.ctx.fpLibraryName ?? "tscircuit";
64869
+ if (!kicadPcbString) {
64870
+ throw new Error("PCB content not available. Run GenerateKicadSchAndPcbStage first.");
64871
+ }
64872
+ const { customFootprintNames, footprinterStrings } = this.classifyFootprints();
64873
+ const uniqueFootprints = /* @__PURE__ */ new Map;
64874
+ try {
64875
+ const parsed = parseKicadSexpr(kicadPcbString);
64876
+ const pcb = parsed.find((node) => node instanceof KicadPcb);
64877
+ if (!pcb) {
64878
+ this.ctx.footprintEntries = [];
64879
+ this.finished = true;
64880
+ return;
64881
+ }
64882
+ const footprints = pcb.footprints ?? [];
64883
+ for (const footprint of footprints) {
64884
+ const footprintEntry = this.sanitizeFootprint({
64885
+ footprint,
64886
+ fpLibraryName,
64887
+ customFootprintNames,
64888
+ footprinterStrings
64889
+ });
64890
+ if (!uniqueFootprints.has(footprintEntry.footprintName)) {
64891
+ uniqueFootprints.set(footprintEntry.footprintName, footprintEntry);
64892
+ }
64893
+ }
64894
+ } catch (error) {
64895
+ console.warn("Failed to parse PCB for footprint extraction:", error);
64896
+ }
64897
+ this.ctx.footprintEntries = Array.from(uniqueFootprints.values());
64898
+ this.finished = true;
64899
+ }
64900
+ sanitizeFootprint({
64901
+ footprint,
64902
+ fpLibraryName,
64903
+ customFootprintNames,
64904
+ footprinterStrings
64905
+ }) {
64906
+ const libraryLink = footprint.libraryLink ?? "footprint";
64907
+ const parts = libraryLink.split(":");
64908
+ const footprintName = (parts.length > 1 ? parts[1] : parts[0])?.replace(/[\\\/]/g, "-").trim() || "footprint";
64909
+ const isBuiltin = !customFootprintNames.has(footprintName);
64910
+ footprint.libraryLink = footprintName;
64911
+ footprint.position = At.from([0, 0, 0]);
64912
+ footprint.locked = false;
64913
+ footprint.placed = false;
64914
+ footprint.version = KICAD_FP_VERSION;
64915
+ footprint.generator = KICAD_FP_GENERATOR;
64916
+ footprint.generatorVersion = KICAD_FP_GENERATOR_VERSION;
64917
+ if (!footprint.descr) {
64918
+ footprint.descr = "";
64919
+ }
64920
+ if (!footprint.tags) {
64921
+ footprint.tags = "";
64922
+ }
64923
+ if (!footprint.embeddedFonts) {
64924
+ footprint.embeddedFonts = new EmbeddedFonts4(false);
64925
+ }
64926
+ if (!footprint.attr) {
64927
+ const attr = new FootprintAttr;
64928
+ const padTypes = (footprint.fpPads ?? []).map((pad) => pad.padType);
64929
+ if (padTypes.some((padType) => padType.includes("thru_hole"))) {
64930
+ attr.type = "through_hole";
64931
+ } else if (padTypes.some((padType) => padType.includes("smd"))) {
64932
+ attr.type = "smd";
64933
+ }
64934
+ footprint.attr = attr;
64935
+ }
64936
+ footprint.uuid = undefined;
64937
+ footprint.path = undefined;
64938
+ footprint.sheetfile = undefined;
64939
+ footprint.sheetname = undefined;
64940
+ const defaultFont = new TextEffectsFont6;
64941
+ defaultFont.size = { width: 1.27, height: 1.27 };
64942
+ defaultFont.thickness = 0.15;
64943
+ const defaultEffects = new TextEffects6({ font: defaultFont });
64944
+ const fpPads = footprint.fpPads ?? [];
64945
+ let minY = 0;
64946
+ let maxY = 0;
64947
+ for (const pad of fpPads) {
64948
+ const at = pad.at;
64949
+ const size = pad.size;
64950
+ if (at && size) {
64951
+ const padY = at.y ?? 0;
64952
+ const padHeight = size.height ?? 0;
64953
+ const padTop = padY - padHeight / 2;
64954
+ const padBottom = padY + padHeight / 2;
64955
+ minY = Math.min(minY, padTop);
64956
+ maxY = Math.max(maxY, padBottom);
64957
+ }
64958
+ }
64959
+ const refY = minY - 0.5;
64960
+ const valY = maxY + 0.5;
64961
+ footprint.properties = [
64962
+ new Property({
64963
+ key: "Reference",
64964
+ value: "REF**",
64965
+ position: [0, refY, 0],
64966
+ layer: "F.SilkS",
64967
+ uuid: generateDeterministicUuid(`${footprintName}-property-Reference`),
64968
+ effects: defaultEffects
64969
+ }),
64970
+ new Property({
64971
+ key: "Value",
64972
+ value: "Val**",
64973
+ position: [0, valY, 0],
64974
+ layer: "F.Fab",
64975
+ uuid: generateDeterministicUuid(`${footprintName}-property-Value`),
64976
+ effects: defaultEffects
64977
+ }),
64978
+ new Property({
64979
+ key: "Datasheet",
64980
+ value: "",
64981
+ position: [0, 0, 0],
64982
+ layer: "F.Fab",
64983
+ hidden: true,
64984
+ uuid: generateDeterministicUuid(`${footprintName}-property-Datasheet`),
64985
+ effects: defaultEffects
64986
+ }),
64987
+ new Property({
64988
+ key: "Description",
64989
+ value: "",
64990
+ position: [0, 0, 0],
64991
+ layer: "F.Fab",
64992
+ hidden: true,
64993
+ uuid: generateDeterministicUuid(`${footprintName}-property-Description`),
64994
+ effects: defaultEffects
64995
+ })
64996
+ ];
64997
+ const texts = footprint.fpTexts ?? [];
64998
+ for (const text of texts) {
64999
+ text.uuid = undefined;
65000
+ if (text.type === "reference") {
65001
+ text.text = "REF**";
65002
+ } else if (text.type === "value" && text.text.trim().length === 0) {
65003
+ text.text = footprintName;
65004
+ }
65005
+ }
65006
+ footprint.fpTexts = texts;
65007
+ const pads = footprint.fpPads ?? [];
65008
+ for (let i = 0;i < pads.length; i++) {
65009
+ const pad = pads[i];
65010
+ if (pad) {
65011
+ pad.uuid = generateDeterministicUuid(`${footprintName}-pad-${pad.number ?? i}`);
65012
+ pad.net = undefined;
65013
+ }
65014
+ }
65015
+ footprint.fpPads = pads;
65016
+ const models = footprint.models ?? [];
65017
+ const updatedModels = [];
65018
+ const modelFiles = [];
65019
+ for (const model of models) {
65020
+ if (model.path) {
65021
+ const modelFilename = getBasename(model.path);
65022
+ const newPath = `../../3dmodels/${fpLibraryName}.3dshapes/${modelFilename}`;
65023
+ const newModel = new FootprintModel(newPath);
65024
+ if (model.offset)
65025
+ newModel.offset = model.offset;
65026
+ if (model.scale)
65027
+ newModel.scale = model.scale;
65028
+ if (model.rotate)
65029
+ newModel.rotate = model.rotate;
65030
+ updatedModels.push(newModel);
65031
+ modelFiles.push(model.path);
65032
+ }
65033
+ }
65034
+ if (updatedModels.length === 0) {
65035
+ const footprinterString = footprinterStrings.get(footprintName);
65036
+ if (footprinterString) {
65037
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinterString}.step`;
65038
+ const cdnModelFilename = getBasename(cdnUrl);
65039
+ const newPath = `../../3dmodels/tscircuit_builtin.3dshapes/${cdnModelFilename}`;
65040
+ updatedModels.push(new FootprintModel(newPath));
65041
+ modelFiles.push(cdnUrl);
65042
+ }
65043
+ }
65044
+ footprint.models = updatedModels;
65045
+ return {
65046
+ footprintName,
65047
+ kicadModString: footprint.getString(),
65048
+ model3dSourcePaths: modelFiles,
65049
+ isBuiltin
65050
+ };
65051
+ }
65052
+ getOutput() {
65053
+ return this.ctx.libraryOutput;
65054
+ }
65055
+ };
64835
65056
  function createTextEffects2(metadataEffects) {
64836
- const font = new TextEffectsFont6;
65057
+ const font = new TextEffectsFont7;
64837
65058
  if (metadataEffects?.font?.size) {
64838
65059
  font.size = {
64839
65060
  width: Number(metadataEffects.font.size.x),
@@ -64847,13 +65068,13 @@ function createTextEffects2(metadataEffects) {
64847
65068
  } else {
64848
65069
  font.thickness = 0.15;
64849
65070
  }
64850
- return new TextEffects6({ font });
65071
+ return new TextEffects7({ font });
64851
65072
  }
64852
65073
  function applyMetadataToFootprint(footprint, metadata, componentName) {
64853
65074
  if (metadata.properties) {
64854
65075
  const newProperties = [];
64855
65076
  const refMeta = metadata.properties.Reference;
64856
- newProperties.push(new Property({
65077
+ newProperties.push(new Property2({
64857
65078
  key: "Reference",
64858
65079
  value: refMeta?.value ?? componentName,
64859
65080
  position: refMeta?.at ? [
@@ -64868,7 +65089,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
64868
65089
  }));
64869
65090
  const valMeta = metadata.properties.Value;
64870
65091
  const valueText = valMeta?.value ?? metadata.footprintName ?? "";
64871
- newProperties.push(new Property({
65092
+ newProperties.push(new Property2({
64872
65093
  key: "Value",
64873
65094
  value: valueText,
64874
65095
  position: valMeta?.at ? [
@@ -64882,7 +65103,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
64882
65103
  hidden: valMeta?.hide
64883
65104
  }));
64884
65105
  const dsMeta = metadata.properties.Datasheet;
64885
- newProperties.push(new Property({
65106
+ newProperties.push(new Property2({
64886
65107
  key: "Datasheet",
64887
65108
  value: dsMeta?.value ?? "",
64888
65109
  position: dsMeta?.at ? [
@@ -64896,7 +65117,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
64896
65117
  hidden: dsMeta?.hide ?? true
64897
65118
  }));
64898
65119
  const descMeta = metadata.properties.Description;
64899
- newProperties.push(new Property({
65120
+ newProperties.push(new Property2({
64900
65121
  key: "Description",
64901
65122
  value: descMeta?.value ?? "",
64902
65123
  position: descMeta?.at ? [
@@ -64913,7 +65134,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
64913
65134
  }
64914
65135
  if (metadata.attributes) {
64915
65136
  if (!footprint.attr) {
64916
- footprint.attr = new FootprintAttr;
65137
+ footprint.attr = new FootprintAttr2;
64917
65138
  }
64918
65139
  if (metadata.attributes.through_hole) {
64919
65140
  footprint.attr.type = "through_hole";
@@ -64934,10 +65155,10 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
64934
65155
  footprint.layer = metadata.layer;
64935
65156
  }
64936
65157
  if (metadata.embeddedFonts !== undefined) {
64937
- footprint.embeddedFonts = new EmbeddedFonts4(metadata.embeddedFonts);
65158
+ footprint.embeddedFonts = new EmbeddedFonts5(metadata.embeddedFonts);
64938
65159
  }
64939
65160
  if (metadata.model) {
64940
- const model = new FootprintModel(metadata.model.path);
65161
+ const model = new FootprintModel2(metadata.model.path);
64941
65162
  if (metadata.model.offset) {
64942
65163
  model.offset = {
64943
65164
  x: Number(metadata.model.offset.x),
@@ -65142,9 +65363,9 @@ function createFpTextFromCircuitJson({
65142
65363
  };
65143
65364
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
65144
65365
  const fontSize = (textElement.font_size || 1) / 1.5;
65145
- const font = new TextEffectsFont7;
65366
+ const font = new TextEffectsFont8;
65146
65367
  font.size = { width: fontSize, height: fontSize };
65147
- const textEffects = new TextEffects7({
65368
+ const textEffects = new TextEffects8({
65148
65369
  font
65149
65370
  });
65150
65371
  const rotation = textElement.ccw_rotation || 0;
@@ -65185,9 +65406,9 @@ function convertNoteTexts(noteTexts, componentCenter, componentRotation) {
65185
65406
  const rotationMatrix = componentRotation !== 0 ? rotate2(componentRotation * Math.PI / 180) : identity2();
65186
65407
  const rotatedPos = applyToPoint9(rotationMatrix, { x: relX, y: relY });
65187
65408
  const fontSize = textElement.font_size || 1;
65188
- const font = new TextEffectsFont8;
65409
+ const font = new TextEffectsFont9;
65189
65410
  font.size = { width: fontSize, height: fontSize };
65190
- const textEffects = new TextEffects8({ font });
65411
+ const textEffects = new TextEffects9({ font });
65191
65412
  const fpText = new FpText3({
65192
65413
  type: "user",
65193
65414
  text: textElement.text,
@@ -65204,7 +65425,7 @@ function create3DModelsFromCadComponent(cadComponent, componentCenter) {
65204
65425
  const modelUrl = cadComponent.model_step_url || cadComponent.model_wrl_url;
65205
65426
  if (!modelUrl)
65206
65427
  return models;
65207
- const model = new FootprintModel2(modelUrl);
65428
+ const model = new FootprintModel3(modelUrl);
65208
65429
  if (cadComponent.position) {
65209
65430
  model.offset = {
65210
65431
  x: (cadComponent.position.x || 0) - componentCenter.x,
@@ -65536,6 +65757,7 @@ function convertNpthHoles(pcbHoles, componentCenter, componentRotation) {
65536
65757
  var AddFootprintsStage = class extends ConverterStage {
65537
65758
  componentsProcessed = 0;
65538
65759
  pcbComponents = [];
65760
+ includeBuiltin3dModels;
65539
65761
  getNetInfoForPcbPort(pcbPortId) {
65540
65762
  if (!pcbPortId)
65541
65763
  return;
@@ -65557,9 +65779,10 @@ var AddFootprintsStage = class extends ConverterStage {
65557
65779
  const cadComponents = this.ctx.db.cad_component?.list() || [];
65558
65780
  return cadComponents.find((cad) => cad.pcb_component_id === pcbComponentId);
65559
65781
  }
65560
- constructor(input, ctx) {
65782
+ constructor(input, ctx, options) {
65561
65783
  super(input, ctx);
65562
65784
  this.pcbComponents = this.ctx.db.pcb_component.list();
65785
+ this.includeBuiltin3dModels = options?.includeBuiltin3dModels ?? false;
65563
65786
  }
65564
65787
  _step() {
65565
65788
  const { kicadPcb, c2kMatPcb } = this.ctx;
@@ -65582,7 +65805,7 @@ var AddFootprintsStage = class extends ConverterStage {
65582
65805
  y: component.center.y
65583
65806
  });
65584
65807
  const footprintData = `footprint:${component.pcb_component_id}:${transformedPos.x},${transformedPos.y}`;
65585
- const footprint = new Footprint2({
65808
+ const footprint = new Footprint3({
65586
65809
  libraryLink: `tscircuit:${footprintName}`,
65587
65810
  layer: "F.Cu",
65588
65811
  at: [transformedPos.x, transformedPos.y, component.rotation || 0],
@@ -65627,8 +65850,37 @@ var AddFootprintsStage = class extends ConverterStage {
65627
65850
  }
65628
65851
  if (cadComponent) {
65629
65852
  const models = create3DModelsFromCadComponent(cadComponent, component.center);
65853
+ const KICAD_3D_BASE = "${KIPRJMOD}/3dmodels";
65630
65854
  if (models.length > 0) {
65631
- footprint.models = models;
65855
+ if (this.includeBuiltin3dModels) {
65856
+ footprint.models = models.map((model) => {
65857
+ if (!model.path)
65858
+ return model;
65859
+ const filename = getBasename(model.path);
65860
+ const folderName = this.ctx.projectName ?? filename.replace(/\.[^.]+$/, "");
65861
+ const newModel = new FootprintModel4(`${KICAD_3D_BASE}/${folderName}.3dshapes/${filename}`);
65862
+ if (model.offset)
65863
+ newModel.offset = model.offset;
65864
+ if (model.scale)
65865
+ newModel.scale = model.scale;
65866
+ if (model.rotate)
65867
+ newModel.rotate = model.rotate;
65868
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(model.path)) {
65869
+ this.ctx.pcbModel3dSourcePaths?.push(model.path);
65870
+ }
65871
+ return newModel;
65872
+ });
65873
+ } else {
65874
+ footprint.models = models;
65875
+ }
65876
+ } else if (cadComponent.footprinter_string && this.includeBuiltin3dModels) {
65877
+ const { footprinter_string } = cadComponent;
65878
+ const modelPath = `${KICAD_3D_BASE}/tscircuit_builtin.3dshapes/${footprinter_string}.step`;
65879
+ footprint.models = [new FootprintModel4(modelPath)];
65880
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinter_string}.step`;
65881
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(cdnUrl)) {
65882
+ this.ctx.pcbModel3dSourcePaths?.push(cdnUrl);
65883
+ }
65632
65884
  }
65633
65885
  }
65634
65886
  const footprintMetadata = component.metadata?.kicad_footprint;
@@ -65857,7 +66109,7 @@ function createFabricationNoteTextFromCircuitJson({
65857
66109
  };
65858
66110
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.Fab";
65859
66111
  const fontSize = (textElement.font_size || 1) / 1.5;
65860
- const font = new TextEffectsFont9;
66112
+ const font = new TextEffectsFont10;
65861
66113
  font.size = { width: fontSize, height: fontSize };
65862
66114
  const justify = new TextEffectsJustify3;
65863
66115
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -65881,13 +66133,13 @@ function createFabricationNoteTextFromCircuitJson({
65881
66133
  case "center":
65882
66134
  break;
65883
66135
  }
65884
- const textEffects = new TextEffects9({
66136
+ const textEffects = new TextEffects10({
65885
66137
  font
65886
66138
  });
65887
66139
  if (anchorAlignment !== "center") {
65888
66140
  textEffects.justify = justify;
65889
66141
  }
65890
- const position = new At([transformedPos.x, transformedPos.y, 0]);
66142
+ const position = new At2([transformedPos.x, transformedPos.y, 0]);
65891
66143
  const grText = new GrText({
65892
66144
  text: textElement.text,
65893
66145
  layer: kicadLayer,
@@ -65913,7 +66165,7 @@ function createGrTextFromCircuitJson({
65913
66165
  };
65914
66166
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
65915
66167
  const fontSize = (textElement.font_size || 1) / 1.5;
65916
- const font = new TextEffectsFont10;
66168
+ const font = new TextEffectsFont11;
65917
66169
  font.size = { width: fontSize, height: fontSize };
65918
66170
  const justify = new TextEffectsJustify4;
65919
66171
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -65937,14 +66189,14 @@ function createGrTextFromCircuitJson({
65937
66189
  case "center":
65938
66190
  break;
65939
66191
  }
65940
- const textEffects = new TextEffects10({
66192
+ const textEffects = new TextEffects11({
65941
66193
  font
65942
66194
  });
65943
66195
  if (anchorAlignment !== "center") {
65944
66196
  textEffects.justify = justify;
65945
66197
  }
65946
66198
  const rotation = textElement.ccw_rotation || 0;
65947
- const position = new At2([transformedPos.x, transformedPos.y, rotation]);
66199
+ const position = new At3([transformedPos.x, transformedPos.y, rotation]);
65948
66200
  const grText = new GrText2({
65949
66201
  text: textElement.text,
65950
66202
  layer: kicadLayer,
@@ -66070,23 +66322,27 @@ var CircuitJsonToKicadPcbConverter = class {
66070
66322
  get currentStage() {
66071
66323
  return this.pipeline[this.currentStageIndex];
66072
66324
  }
66073
- constructor(circuitJson) {
66325
+ constructor(circuitJson, options) {
66074
66326
  const CIRCUIT_JSON_TO_MM_SCALE = 1;
66075
66327
  const KICAD_PCB_CENTER_X = 100;
66076
66328
  const KICAD_PCB_CENTER_Y = 100;
66077
66329
  this.ctx = {
66078
66330
  db: cju2(circuitJson),
66079
66331
  circuitJson,
66080
- kicadPcb: new KicadPcb({
66332
+ kicadPcb: new KicadPcb2({
66081
66333
  generator: "circuit-json-to-kicad",
66082
66334
  generatorVersion: "0.0.1"
66083
66335
  }),
66084
- c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE))
66336
+ c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE)),
66337
+ projectName: options?.projectName,
66338
+ pcbModel3dSourcePaths: []
66085
66339
  };
66086
66340
  this.pipeline = [
66087
66341
  new InitializePcbStage(circuitJson, this.ctx),
66088
66342
  new AddNetsStage(circuitJson, this.ctx),
66089
- new AddFootprintsStage(circuitJson, this.ctx),
66343
+ new AddFootprintsStage(circuitJson, this.ctx, {
66344
+ includeBuiltin3dModels: options?.includeBuiltin3dModels
66345
+ }),
66090
66346
  new AddTracesStage(circuitJson, this.ctx),
66091
66347
  new AddViasStage(circuitJson, this.ctx),
66092
66348
  new AddGraphicsStage(circuitJson, this.ctx)
@@ -66113,6 +66369,9 @@ var CircuitJsonToKicadPcbConverter = class {
66113
66369
  getOutputString() {
66114
66370
  return this.ctx.kicadPcb.getString();
66115
66371
  }
66372
+ getModel3dSourcePaths() {
66373
+ return this.ctx.pcbModel3dSourcePaths ?? [];
66374
+ }
66116
66375
  };
66117
66376
  var CircuitJsonToKicadProConverter = class {
66118
66377
  ctx;
@@ -66217,7 +66476,7 @@ var ExtractSymbolsStage = class extends ConverterStage {
66217
66476
  }
66218
66477
  const uniqueSymbols = /* @__PURE__ */ new Map;
66219
66478
  try {
66220
- const parsed = parseKicadSexpr(schContent);
66479
+ const parsed = parseKicadSexpr2(schContent);
66221
66480
  const sch = parsed.find((node) => node instanceof KicadSch2);
66222
66481
  if (!sch) {
66223
66482
  this.ctx.symbolEntries = [];
@@ -66298,227 +66557,6 @@ var ExtractSymbolsStage = class extends ConverterStage {
66298
66557
  return this.ctx.libraryOutput;
66299
66558
  }
66300
66559
  };
66301
- var MODEL_CDN_BASE_URL = "https://modelcdn.tscircuit.com/jscad_models";
66302
- var KICAD_FP_VERSION = 20240108;
66303
- var KICAD_FP_GENERATOR = "pcbnew";
66304
- var KICAD_FP_GENERATOR_VERSION = "8.0";
66305
- function getBasename(filePath) {
66306
- const parts = filePath.split(/[/\\]/);
66307
- return parts[parts.length - 1] || filePath;
66308
- }
66309
- var ExtractFootprintsStage = class extends ConverterStage {
66310
- classifyFootprints() {
66311
- const customFootprintNames = /* @__PURE__ */ new Set;
66312
- const footprinterStrings = /* @__PURE__ */ new Map;
66313
- const cadComponents = this.ctx.db.cad_component?.list() ?? [];
66314
- const sourceComponents = this.ctx.db.source_component;
66315
- for (const cadComponent of cadComponents) {
66316
- const sourceComp = cadComponent.source_component_id ? sourceComponents?.get(cadComponent.source_component_id) : null;
66317
- if (!sourceComp)
66318
- continue;
66319
- const footprintName = getKicadCompatibleComponentName(sourceComp, cadComponent);
66320
- if (cadComponent.footprinter_string) {
66321
- footprinterStrings.set(footprintName, cadComponent.footprinter_string);
66322
- } else {
66323
- customFootprintNames.add(footprintName);
66324
- const pcbComp = this.ctx.circuitJson.find((el) => el.type === "pcb_component" && el.source_component_id === cadComponent.source_component_id);
66325
- if (pcbComp && pcbComp.type === "pcb_component" && pcbComp.metadata?.kicad_footprint?.footprintName) {
66326
- customFootprintNames.add(pcbComp.metadata.kicad_footprint.footprintName);
66327
- }
66328
- }
66329
- }
66330
- return { customFootprintNames, footprinterStrings };
66331
- }
66332
- _step() {
66333
- const kicadPcbString = this.ctx.kicadPcbString;
66334
- const fpLibraryName = this.ctx.fpLibraryName ?? "tscircuit";
66335
- if (!kicadPcbString) {
66336
- throw new Error("PCB content not available. Run GenerateKicadSchAndPcbStage first.");
66337
- }
66338
- const { customFootprintNames, footprinterStrings } = this.classifyFootprints();
66339
- const uniqueFootprints = /* @__PURE__ */ new Map;
66340
- try {
66341
- const parsed = parseKicadSexpr2(kicadPcbString);
66342
- const pcb = parsed.find((node) => node instanceof KicadPcb2);
66343
- if (!pcb) {
66344
- this.ctx.footprintEntries = [];
66345
- this.finished = true;
66346
- return;
66347
- }
66348
- const footprints = pcb.footprints ?? [];
66349
- for (const footprint of footprints) {
66350
- const footprintEntry = this.sanitizeFootprint({
66351
- footprint,
66352
- fpLibraryName,
66353
- customFootprintNames,
66354
- footprinterStrings
66355
- });
66356
- if (!uniqueFootprints.has(footprintEntry.footprintName)) {
66357
- uniqueFootprints.set(footprintEntry.footprintName, footprintEntry);
66358
- }
66359
- }
66360
- } catch (error) {
66361
- console.warn("Failed to parse PCB for footprint extraction:", error);
66362
- }
66363
- this.ctx.footprintEntries = Array.from(uniqueFootprints.values());
66364
- this.finished = true;
66365
- }
66366
- sanitizeFootprint({
66367
- footprint,
66368
- fpLibraryName,
66369
- customFootprintNames,
66370
- footprinterStrings
66371
- }) {
66372
- const libraryLink = footprint.libraryLink ?? "footprint";
66373
- const parts = libraryLink.split(":");
66374
- const footprintName = (parts.length > 1 ? parts[1] : parts[0])?.replace(/[\\\/]/g, "-").trim() || "footprint";
66375
- const isBuiltin = !customFootprintNames.has(footprintName);
66376
- footprint.libraryLink = footprintName;
66377
- footprint.position = At3.from([0, 0, 0]);
66378
- footprint.locked = false;
66379
- footprint.placed = false;
66380
- footprint.version = KICAD_FP_VERSION;
66381
- footprint.generator = KICAD_FP_GENERATOR;
66382
- footprint.generatorVersion = KICAD_FP_GENERATOR_VERSION;
66383
- if (!footprint.descr) {
66384
- footprint.descr = "";
66385
- }
66386
- if (!footprint.tags) {
66387
- footprint.tags = "";
66388
- }
66389
- if (!footprint.embeddedFonts) {
66390
- footprint.embeddedFonts = new EmbeddedFonts5(false);
66391
- }
66392
- if (!footprint.attr) {
66393
- const attr = new FootprintAttr2;
66394
- const padTypes = (footprint.fpPads ?? []).map((pad) => pad.padType);
66395
- if (padTypes.some((padType) => padType.includes("thru_hole"))) {
66396
- attr.type = "through_hole";
66397
- } else if (padTypes.some((padType) => padType.includes("smd"))) {
66398
- attr.type = "smd";
66399
- }
66400
- footprint.attr = attr;
66401
- }
66402
- footprint.uuid = undefined;
66403
- footprint.path = undefined;
66404
- footprint.sheetfile = undefined;
66405
- footprint.sheetname = undefined;
66406
- const defaultFont = new TextEffectsFont11;
66407
- defaultFont.size = { width: 1.27, height: 1.27 };
66408
- defaultFont.thickness = 0.15;
66409
- const defaultEffects = new TextEffects11({ font: defaultFont });
66410
- const fpPads = footprint.fpPads ?? [];
66411
- let minY = 0;
66412
- let maxY = 0;
66413
- for (const pad of fpPads) {
66414
- const at = pad.at;
66415
- const size = pad.size;
66416
- if (at && size) {
66417
- const padY = at.y ?? 0;
66418
- const padHeight = size.height ?? 0;
66419
- const padTop = padY - padHeight / 2;
66420
- const padBottom = padY + padHeight / 2;
66421
- minY = Math.min(minY, padTop);
66422
- maxY = Math.max(maxY, padBottom);
66423
- }
66424
- }
66425
- const refY = minY - 0.5;
66426
- const valY = maxY + 0.5;
66427
- footprint.properties = [
66428
- new Property2({
66429
- key: "Reference",
66430
- value: "REF**",
66431
- position: [0, refY, 0],
66432
- layer: "F.SilkS",
66433
- uuid: generateDeterministicUuid(`${footprintName}-property-Reference`),
66434
- effects: defaultEffects
66435
- }),
66436
- new Property2({
66437
- key: "Value",
66438
- value: "Val**",
66439
- position: [0, valY, 0],
66440
- layer: "F.Fab",
66441
- uuid: generateDeterministicUuid(`${footprintName}-property-Value`),
66442
- effects: defaultEffects
66443
- }),
66444
- new Property2({
66445
- key: "Datasheet",
66446
- value: "",
66447
- position: [0, 0, 0],
66448
- layer: "F.Fab",
66449
- hidden: true,
66450
- uuid: generateDeterministicUuid(`${footprintName}-property-Datasheet`),
66451
- effects: defaultEffects
66452
- }),
66453
- new Property2({
66454
- key: "Description",
66455
- value: "",
66456
- position: [0, 0, 0],
66457
- layer: "F.Fab",
66458
- hidden: true,
66459
- uuid: generateDeterministicUuid(`${footprintName}-property-Description`),
66460
- effects: defaultEffects
66461
- })
66462
- ];
66463
- const texts = footprint.fpTexts ?? [];
66464
- for (const text of texts) {
66465
- text.uuid = undefined;
66466
- if (text.type === "reference") {
66467
- text.text = "REF**";
66468
- } else if (text.type === "value" && text.text.trim().length === 0) {
66469
- text.text = footprintName;
66470
- }
66471
- }
66472
- footprint.fpTexts = texts;
66473
- const pads = footprint.fpPads ?? [];
66474
- for (let i = 0;i < pads.length; i++) {
66475
- const pad = pads[i];
66476
- if (pad) {
66477
- pad.uuid = generateDeterministicUuid(`${footprintName}-pad-${pad.number ?? i}`);
66478
- pad.net = undefined;
66479
- }
66480
- }
66481
- footprint.fpPads = pads;
66482
- const models = footprint.models ?? [];
66483
- const updatedModels = [];
66484
- const modelFiles = [];
66485
- for (const model of models) {
66486
- if (model.path) {
66487
- const modelFilename = getBasename(model.path);
66488
- const newPath = `../../3dmodels/${fpLibraryName}.3dshapes/${modelFilename}`;
66489
- const newModel = new FootprintModel3(newPath);
66490
- if (model.offset)
66491
- newModel.offset = model.offset;
66492
- if (model.scale)
66493
- newModel.scale = model.scale;
66494
- if (model.rotate)
66495
- newModel.rotate = model.rotate;
66496
- updatedModels.push(newModel);
66497
- modelFiles.push(model.path);
66498
- }
66499
- }
66500
- if (updatedModels.length === 0) {
66501
- const footprinterString = footprinterStrings.get(footprintName);
66502
- if (footprinterString) {
66503
- const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinterString}.step`;
66504
- const cdnModelFilename = getBasename(cdnUrl);
66505
- const newPath = `../../3dmodels/tscircuit_builtin.3dshapes/${cdnModelFilename}`;
66506
- updatedModels.push(new FootprintModel3(newPath));
66507
- modelFiles.push(cdnUrl);
66508
- }
66509
- }
66510
- footprint.models = updatedModels;
66511
- return {
66512
- footprintName,
66513
- kicadModString: footprint.getString(),
66514
- model3dSourcePaths: modelFiles,
66515
- isBuiltin
66516
- };
66517
- }
66518
- getOutput() {
66519
- return this.ctx.libraryOutput;
66520
- }
66521
- };
66522
66560
  var KICAD_SYM_LIB_VERSION = 20211014;
66523
66561
  var GENERATOR = "circuit-json-to-kicad";
66524
66562
  var GenerateSymbolLibraryStage = class extends ConverterStage {