@tscircuit/cli 0.1.1135 → 0.1.1137

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",
@@ -101451,6 +101451,11 @@ import * as path15 from "node:path";
101451
101451
 
101452
101452
  // lib/project-config/project-config-schema.ts
101453
101453
  import { z } from "zod";
101454
+ var pcbSnapshotSettingsSchema = z.object({
101455
+ showCourtyards: z.boolean().optional(),
101456
+ showPcbNotes: z.boolean().optional(),
101457
+ showFabricationNotes: z.boolean().optional()
101458
+ });
101454
101459
  var projectConfigSchema = z.object({
101455
101460
  mainEntrypoint: z.string().optional(),
101456
101461
  libraryEntrypoint: z.string().optional(),
@@ -101459,6 +101464,7 @@ var projectConfigSchema = z.object({
101459
101464
  ignoredFiles: z.array(z.string()).optional(),
101460
101465
  includeBoardFiles: z.array(z.string()).optional(),
101461
101466
  snapshotsDir: z.string().optional(),
101467
+ pcbSnapshotSettings: pcbSnapshotSettingsSchema.optional(),
101462
101468
  prebuildCommand: z.string().optional(),
101463
101469
  buildCommand: z.string().optional(),
101464
101470
  kicadProjectEntrypointPath: z.string().optional(),
@@ -101621,7 +101627,7 @@ import {
101621
101627
  SheetInstancesRootPage,
101622
101628
  EmbeddedFonts as EmbeddedFonts3
101623
101629
  } from "kicadts";
101624
- import { KicadPcb } from "kicadts";
101630
+ import { KicadPcb as KicadPcb2 } from "kicadts";
101625
101631
  import { cju as cju2 } from "@tscircuit/circuit-json-util";
101626
101632
  import { compose as compose4, translate as translate4, scale as scale3 } from "transformation-matrix";
101627
101633
  import {
@@ -101632,15 +101638,26 @@ import {
101632
101638
  Setup
101633
101639
  } from "kicadts";
101634
101640
  import { PcbNet } from "kicadts";
101635
- import { Footprint as Footprint2 } from "kicadts";
101636
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
101641
+ import { Footprint as Footprint3, FootprintModel as FootprintModel4 } from "kicadts";
101637
101642
  import {
101643
+ parseKicadSexpr,
101644
+ KicadPcb,
101645
+ FootprintModel,
101646
+ At,
101647
+ EmbeddedFonts as EmbeddedFonts4,
101648
+ FootprintAttr,
101638
101649
  Property,
101639
101650
  TextEffects as TextEffects6,
101640
- TextEffectsFont as TextEffectsFont6,
101641
- EmbeddedFonts as EmbeddedFonts4,
101642
- FootprintModel,
101643
- FootprintAttr
101651
+ TextEffectsFont as TextEffectsFont6
101652
+ } from "kicadts";
101653
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
101654
+ import {
101655
+ Property as Property2,
101656
+ TextEffects as TextEffects7,
101657
+ TextEffectsFont as TextEffectsFont7,
101658
+ EmbeddedFonts as EmbeddedFonts5,
101659
+ FootprintModel as FootprintModel2,
101660
+ FootprintAttr as FootprintAttr2
101644
101661
  } from "kicadts";
101645
101662
  import { FpCircle, Stroke as Stroke4 } from "kicadts";
101646
101663
  import { FpCircle as FpCircle2, Stroke as Stroke5 } from "kicadts";
@@ -101649,11 +101666,11 @@ import { FpRect as FpRect2, Stroke as Stroke7 } from "kicadts";
101649
101666
  import { FpRect as FpRect3, Stroke as Stroke8 } from "kicadts";
101650
101667
  import { FpPoly, Pts as Pts3, Xy as Xy3, Stroke as Stroke9 } from "kicadts";
101651
101668
  import"kicadts";
101652
- import { FpText, TextEffects as TextEffects7, TextEffectsFont as TextEffectsFont7 } from "kicadts";
101669
+ import { FpText, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
101653
101670
  import { applyToPoint as applyToPoint8, rotate, identity } from "transformation-matrix";
101654
- import { FpText as FpText3, TextEffects as TextEffects8, TextEffectsFont as TextEffectsFont8 } from "kicadts";
101671
+ import { FpText as FpText3, TextEffects as TextEffects9, TextEffectsFont as TextEffectsFont9 } from "kicadts";
101655
101672
  import { applyToPoint as applyToPoint9, rotate as rotate2, identity as identity2 } from "transformation-matrix";
101656
- import { FootprintModel as FootprintModel2 } from "kicadts";
101673
+ import { FootprintModel as FootprintModel3 } from "kicadts";
101657
101674
  import {
101658
101675
  FootprintPad,
101659
101676
  PadPrimitives,
@@ -101680,36 +101697,25 @@ import { Via, ViaNet } from "kicadts";
101680
101697
  import { applyToPoint as applyToPoint15 } from "transformation-matrix";
101681
101698
  import { GrLine } from "kicadts";
101682
101699
  import {
101683
- At,
101700
+ At as At2,
101684
101701
  GrText,
101685
- TextEffects as TextEffects9,
101686
- TextEffectsFont as TextEffectsFont9,
101702
+ TextEffects as TextEffects10,
101703
+ TextEffectsFont as TextEffectsFont10,
101687
101704
  TextEffectsJustify as TextEffectsJustify3
101688
101705
  } from "kicadts";
101689
101706
  import { applyToPoint as applyToPoint16 } from "transformation-matrix";
101690
101707
  import { applyToPoint as applyToPoint18 } from "transformation-matrix";
101691
101708
  import {
101692
101709
  GrText as GrText2,
101693
- TextEffects as TextEffects10,
101694
- TextEffectsFont as TextEffectsFont10,
101710
+ TextEffects as TextEffects11,
101711
+ TextEffectsFont as TextEffectsFont11,
101695
101712
  TextEffectsJustify as TextEffectsJustify4,
101696
- At as At2
101713
+ At as At3
101697
101714
  } from "kicadts";
101698
101715
  import { applyToPoint as applyToPoint17 } from "transformation-matrix";
101699
101716
  import { cju as cju3 } from "@tscircuit/circuit-json-util";
101700
101717
  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";
101718
+ import { parseKicadSexpr as parseKicadSexpr2, KicadSch as KicadSch2 } from "kicadts";
101713
101719
  import { KicadSymbolLib } from "kicadts";
101714
101720
  import { parseKicadMod } from "kicadts";
101715
101721
  import {
@@ -103346,8 +103352,229 @@ function generateDeterministicUuid(data) {
103346
103352
  const hash = simpleHash(data);
103347
103353
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
103348
103354
  }
103355
+ var MODEL_CDN_BASE_URL = "https://modelcdn.tscircuit.com/jscad_models";
103356
+ var KICAD_FP_VERSION = 20240108;
103357
+ var KICAD_FP_GENERATOR = "pcbnew";
103358
+ var KICAD_FP_GENERATOR_VERSION = "8.0";
103359
+ function getBasename(filePath) {
103360
+ const parts = filePath.split(/[/\\]/);
103361
+ return parts[parts.length - 1] || filePath;
103362
+ }
103363
+ var ExtractFootprintsStage = class extends ConverterStage {
103364
+ classifyFootprints() {
103365
+ const customFootprintNames = /* @__PURE__ */ new Set;
103366
+ const footprinterStrings = /* @__PURE__ */ new Map;
103367
+ const cadComponents = this.ctx.db.cad_component?.list() ?? [];
103368
+ const sourceComponents = this.ctx.db.source_component;
103369
+ for (const cadComponent of cadComponents) {
103370
+ const sourceComp = cadComponent.source_component_id ? sourceComponents?.get(cadComponent.source_component_id) : null;
103371
+ if (!sourceComp)
103372
+ continue;
103373
+ const footprintName = getKicadCompatibleComponentName(sourceComp, cadComponent);
103374
+ if (cadComponent.footprinter_string) {
103375
+ footprinterStrings.set(footprintName, cadComponent.footprinter_string);
103376
+ } else {
103377
+ customFootprintNames.add(footprintName);
103378
+ const pcbComp = this.ctx.circuitJson.find((el) => el.type === "pcb_component" && el.source_component_id === cadComponent.source_component_id);
103379
+ if (pcbComp && pcbComp.type === "pcb_component" && pcbComp.metadata?.kicad_footprint?.footprintName) {
103380
+ customFootprintNames.add(pcbComp.metadata.kicad_footprint.footprintName);
103381
+ }
103382
+ }
103383
+ }
103384
+ return { customFootprintNames, footprinterStrings };
103385
+ }
103386
+ _step() {
103387
+ const kicadPcbString = this.ctx.kicadPcbString;
103388
+ const fpLibraryName = this.ctx.fpLibraryName ?? "tscircuit";
103389
+ if (!kicadPcbString) {
103390
+ throw new Error("PCB content not available. Run GenerateKicadSchAndPcbStage first.");
103391
+ }
103392
+ const { customFootprintNames, footprinterStrings } = this.classifyFootprints();
103393
+ const uniqueFootprints = /* @__PURE__ */ new Map;
103394
+ try {
103395
+ const parsed = parseKicadSexpr(kicadPcbString);
103396
+ const pcb = parsed.find((node) => node instanceof KicadPcb);
103397
+ if (!pcb) {
103398
+ this.ctx.footprintEntries = [];
103399
+ this.finished = true;
103400
+ return;
103401
+ }
103402
+ const footprints = pcb.footprints ?? [];
103403
+ for (const footprint of footprints) {
103404
+ const footprintEntry = this.sanitizeFootprint({
103405
+ footprint,
103406
+ fpLibraryName,
103407
+ customFootprintNames,
103408
+ footprinterStrings
103409
+ });
103410
+ if (!uniqueFootprints.has(footprintEntry.footprintName)) {
103411
+ uniqueFootprints.set(footprintEntry.footprintName, footprintEntry);
103412
+ }
103413
+ }
103414
+ } catch (error) {
103415
+ console.warn("Failed to parse PCB for footprint extraction:", error);
103416
+ }
103417
+ this.ctx.footprintEntries = Array.from(uniqueFootprints.values());
103418
+ this.finished = true;
103419
+ }
103420
+ sanitizeFootprint({
103421
+ footprint,
103422
+ fpLibraryName,
103423
+ customFootprintNames,
103424
+ footprinterStrings
103425
+ }) {
103426
+ const libraryLink = footprint.libraryLink ?? "footprint";
103427
+ const parts = libraryLink.split(":");
103428
+ const footprintName = (parts.length > 1 ? parts[1] : parts[0])?.replace(/[\\\/]/g, "-").trim() || "footprint";
103429
+ const isBuiltin = !customFootprintNames.has(footprintName);
103430
+ footprint.libraryLink = footprintName;
103431
+ footprint.position = At.from([0, 0, 0]);
103432
+ footprint.locked = false;
103433
+ footprint.placed = false;
103434
+ footprint.version = KICAD_FP_VERSION;
103435
+ footprint.generator = KICAD_FP_GENERATOR;
103436
+ footprint.generatorVersion = KICAD_FP_GENERATOR_VERSION;
103437
+ if (!footprint.descr) {
103438
+ footprint.descr = "";
103439
+ }
103440
+ if (!footprint.tags) {
103441
+ footprint.tags = "";
103442
+ }
103443
+ if (!footprint.embeddedFonts) {
103444
+ footprint.embeddedFonts = new EmbeddedFonts4(false);
103445
+ }
103446
+ if (!footprint.attr) {
103447
+ const attr = new FootprintAttr;
103448
+ const padTypes = (footprint.fpPads ?? []).map((pad) => pad.padType);
103449
+ if (padTypes.some((padType) => padType.includes("thru_hole"))) {
103450
+ attr.type = "through_hole";
103451
+ } else if (padTypes.some((padType) => padType.includes("smd"))) {
103452
+ attr.type = "smd";
103453
+ }
103454
+ footprint.attr = attr;
103455
+ }
103456
+ footprint.uuid = undefined;
103457
+ footprint.path = undefined;
103458
+ footprint.sheetfile = undefined;
103459
+ footprint.sheetname = undefined;
103460
+ const defaultFont = new TextEffectsFont6;
103461
+ defaultFont.size = { width: 1.27, height: 1.27 };
103462
+ defaultFont.thickness = 0.15;
103463
+ const defaultEffects = new TextEffects6({ font: defaultFont });
103464
+ const fpPads = footprint.fpPads ?? [];
103465
+ let minY = 0;
103466
+ let maxY = 0;
103467
+ for (const pad of fpPads) {
103468
+ const at = pad.at;
103469
+ const size = pad.size;
103470
+ if (at && size) {
103471
+ const padY = at.y ?? 0;
103472
+ const padHeight = size.height ?? 0;
103473
+ const padTop = padY - padHeight / 2;
103474
+ const padBottom = padY + padHeight / 2;
103475
+ minY = Math.min(minY, padTop);
103476
+ maxY = Math.max(maxY, padBottom);
103477
+ }
103478
+ }
103479
+ const refY = minY - 0.5;
103480
+ const valY = maxY + 0.5;
103481
+ footprint.properties = [
103482
+ new Property({
103483
+ key: "Reference",
103484
+ value: "REF**",
103485
+ position: [0, refY, 0],
103486
+ layer: "F.SilkS",
103487
+ uuid: generateDeterministicUuid(`${footprintName}-property-Reference`),
103488
+ effects: defaultEffects
103489
+ }),
103490
+ new Property({
103491
+ key: "Value",
103492
+ value: "Val**",
103493
+ position: [0, valY, 0],
103494
+ layer: "F.Fab",
103495
+ uuid: generateDeterministicUuid(`${footprintName}-property-Value`),
103496
+ effects: defaultEffects
103497
+ }),
103498
+ new Property({
103499
+ key: "Datasheet",
103500
+ value: "",
103501
+ position: [0, 0, 0],
103502
+ layer: "F.Fab",
103503
+ hidden: true,
103504
+ uuid: generateDeterministicUuid(`${footprintName}-property-Datasheet`),
103505
+ effects: defaultEffects
103506
+ }),
103507
+ new Property({
103508
+ key: "Description",
103509
+ value: "",
103510
+ position: [0, 0, 0],
103511
+ layer: "F.Fab",
103512
+ hidden: true,
103513
+ uuid: generateDeterministicUuid(`${footprintName}-property-Description`),
103514
+ effects: defaultEffects
103515
+ })
103516
+ ];
103517
+ const texts = footprint.fpTexts ?? [];
103518
+ for (const text of texts) {
103519
+ text.uuid = undefined;
103520
+ if (text.type === "reference") {
103521
+ text.text = "REF**";
103522
+ } else if (text.type === "value" && text.text.trim().length === 0) {
103523
+ text.text = footprintName;
103524
+ }
103525
+ }
103526
+ footprint.fpTexts = texts;
103527
+ const pads = footprint.fpPads ?? [];
103528
+ for (let i = 0;i < pads.length; i++) {
103529
+ const pad = pads[i];
103530
+ if (pad) {
103531
+ pad.uuid = generateDeterministicUuid(`${footprintName}-pad-${pad.number ?? i}`);
103532
+ pad.net = undefined;
103533
+ }
103534
+ }
103535
+ footprint.fpPads = pads;
103536
+ const models = footprint.models ?? [];
103537
+ const updatedModels = [];
103538
+ const modelFiles = [];
103539
+ for (const model of models) {
103540
+ if (model.path) {
103541
+ const modelFilename = getBasename(model.path);
103542
+ const newPath = `../../3dmodels/${fpLibraryName}.3dshapes/${modelFilename}`;
103543
+ const newModel = new FootprintModel(newPath);
103544
+ if (model.offset)
103545
+ newModel.offset = model.offset;
103546
+ if (model.scale)
103547
+ newModel.scale = model.scale;
103548
+ if (model.rotate)
103549
+ newModel.rotate = model.rotate;
103550
+ updatedModels.push(newModel);
103551
+ modelFiles.push(model.path);
103552
+ }
103553
+ }
103554
+ if (updatedModels.length === 0) {
103555
+ const footprinterString = footprinterStrings.get(footprintName);
103556
+ if (footprinterString) {
103557
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinterString}.step`;
103558
+ const cdnModelFilename = getBasename(cdnUrl);
103559
+ const newPath = `../../3dmodels/tscircuit_builtin.3dshapes/${cdnModelFilename}`;
103560
+ updatedModels.push(new FootprintModel(newPath));
103561
+ modelFiles.push(cdnUrl);
103562
+ }
103563
+ }
103564
+ footprint.models = updatedModels;
103565
+ return {
103566
+ footprintName,
103567
+ kicadModString: footprint.getString(),
103568
+ model3dSourcePaths: modelFiles,
103569
+ isBuiltin
103570
+ };
103571
+ }
103572
+ getOutput() {
103573
+ return this.ctx.libraryOutput;
103574
+ }
103575
+ };
103349
103576
  function createTextEffects2(metadataEffects) {
103350
- const font = new TextEffectsFont6;
103577
+ const font = new TextEffectsFont7;
103351
103578
  if (metadataEffects?.font?.size) {
103352
103579
  font.size = {
103353
103580
  width: Number(metadataEffects.font.size.x),
@@ -103361,13 +103588,13 @@ function createTextEffects2(metadataEffects) {
103361
103588
  } else {
103362
103589
  font.thickness = 0.15;
103363
103590
  }
103364
- return new TextEffects6({ font });
103591
+ return new TextEffects7({ font });
103365
103592
  }
103366
103593
  function applyMetadataToFootprint(footprint, metadata, componentName) {
103367
103594
  if (metadata.properties) {
103368
103595
  const newProperties = [];
103369
103596
  const refMeta = metadata.properties.Reference;
103370
- newProperties.push(new Property({
103597
+ newProperties.push(new Property2({
103371
103598
  key: "Reference",
103372
103599
  value: refMeta?.value ?? componentName,
103373
103600
  position: refMeta?.at ? [
@@ -103382,7 +103609,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103382
103609
  }));
103383
103610
  const valMeta = metadata.properties.Value;
103384
103611
  const valueText = valMeta?.value ?? metadata.footprintName ?? "";
103385
- newProperties.push(new Property({
103612
+ newProperties.push(new Property2({
103386
103613
  key: "Value",
103387
103614
  value: valueText,
103388
103615
  position: valMeta?.at ? [
@@ -103396,7 +103623,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103396
103623
  hidden: valMeta?.hide
103397
103624
  }));
103398
103625
  const dsMeta = metadata.properties.Datasheet;
103399
- newProperties.push(new Property({
103626
+ newProperties.push(new Property2({
103400
103627
  key: "Datasheet",
103401
103628
  value: dsMeta?.value ?? "",
103402
103629
  position: dsMeta?.at ? [
@@ -103410,7 +103637,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103410
103637
  hidden: dsMeta?.hide ?? true
103411
103638
  }));
103412
103639
  const descMeta = metadata.properties.Description;
103413
- newProperties.push(new Property({
103640
+ newProperties.push(new Property2({
103414
103641
  key: "Description",
103415
103642
  value: descMeta?.value ?? "",
103416
103643
  position: descMeta?.at ? [
@@ -103427,7 +103654,7 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103427
103654
  }
103428
103655
  if (metadata.attributes) {
103429
103656
  if (!footprint.attr) {
103430
- footprint.attr = new FootprintAttr;
103657
+ footprint.attr = new FootprintAttr2;
103431
103658
  }
103432
103659
  if (metadata.attributes.through_hole) {
103433
103660
  footprint.attr.type = "through_hole";
@@ -103448,10 +103675,10 @@ function applyMetadataToFootprint(footprint, metadata, componentName) {
103448
103675
  footprint.layer = metadata.layer;
103449
103676
  }
103450
103677
  if (metadata.embeddedFonts !== undefined) {
103451
- footprint.embeddedFonts = new EmbeddedFonts4(metadata.embeddedFonts);
103678
+ footprint.embeddedFonts = new EmbeddedFonts5(metadata.embeddedFonts);
103452
103679
  }
103453
103680
  if (metadata.model) {
103454
- const model = new FootprintModel(metadata.model.path);
103681
+ const model = new FootprintModel2(metadata.model.path);
103455
103682
  if (metadata.model.offset) {
103456
103683
  model.offset = {
103457
103684
  x: Number(metadata.model.offset.x),
@@ -103656,9 +103883,9 @@ function createFpTextFromCircuitJson({
103656
103883
  };
103657
103884
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
103658
103885
  const fontSize = (textElement.font_size || 1) / 1.5;
103659
- const font = new TextEffectsFont7;
103886
+ const font = new TextEffectsFont8;
103660
103887
  font.size = { width: fontSize, height: fontSize };
103661
- const textEffects = new TextEffects7({
103888
+ const textEffects = new TextEffects8({
103662
103889
  font
103663
103890
  });
103664
103891
  const rotation = textElement.ccw_rotation || 0;
@@ -103699,9 +103926,9 @@ function convertNoteTexts(noteTexts, componentCenter, componentRotation) {
103699
103926
  const rotationMatrix = componentRotation !== 0 ? rotate2(componentRotation * Math.PI / 180) : identity2();
103700
103927
  const rotatedPos = applyToPoint9(rotationMatrix, { x: relX, y: relY });
103701
103928
  const fontSize = textElement.font_size || 1;
103702
- const font = new TextEffectsFont8;
103929
+ const font = new TextEffectsFont9;
103703
103930
  font.size = { width: fontSize, height: fontSize };
103704
- const textEffects = new TextEffects8({ font });
103931
+ const textEffects = new TextEffects9({ font });
103705
103932
  const fpText = new FpText3({
103706
103933
  type: "user",
103707
103934
  text: textElement.text,
@@ -103718,7 +103945,7 @@ function create3DModelsFromCadComponent(cadComponent, componentCenter) {
103718
103945
  const modelUrl = cadComponent.model_step_url || cadComponent.model_wrl_url;
103719
103946
  if (!modelUrl)
103720
103947
  return models;
103721
- const model = new FootprintModel2(modelUrl);
103948
+ const model = new FootprintModel3(modelUrl);
103722
103949
  if (cadComponent.position) {
103723
103950
  model.offset = {
103724
103951
  x: (cadComponent.position.x || 0) - componentCenter.x,
@@ -104050,6 +104277,7 @@ function convertNpthHoles(pcbHoles, componentCenter, componentRotation) {
104050
104277
  var AddFootprintsStage = class extends ConverterStage {
104051
104278
  componentsProcessed = 0;
104052
104279
  pcbComponents = [];
104280
+ includeBuiltin3dModels;
104053
104281
  getNetInfoForPcbPort(pcbPortId) {
104054
104282
  if (!pcbPortId)
104055
104283
  return;
@@ -104071,9 +104299,10 @@ var AddFootprintsStage = class extends ConverterStage {
104071
104299
  const cadComponents = this.ctx.db.cad_component?.list() || [];
104072
104300
  return cadComponents.find((cad) => cad.pcb_component_id === pcbComponentId);
104073
104301
  }
104074
- constructor(input, ctx) {
104302
+ constructor(input, ctx, options) {
104075
104303
  super(input, ctx);
104076
104304
  this.pcbComponents = this.ctx.db.pcb_component.list();
104305
+ this.includeBuiltin3dModels = options?.includeBuiltin3dModels ?? false;
104077
104306
  }
104078
104307
  _step() {
104079
104308
  const { kicadPcb, c2kMatPcb } = this.ctx;
@@ -104096,7 +104325,7 @@ var AddFootprintsStage = class extends ConverterStage {
104096
104325
  y: component.center.y
104097
104326
  });
104098
104327
  const footprintData = `footprint:${component.pcb_component_id}:${transformedPos.x},${transformedPos.y}`;
104099
- const footprint = new Footprint2({
104328
+ const footprint = new Footprint3({
104100
104329
  libraryLink: `tscircuit:${footprintName}`,
104101
104330
  layer: "F.Cu",
104102
104331
  at: [transformedPos.x, transformedPos.y, component.rotation || 0],
@@ -104141,8 +104370,37 @@ var AddFootprintsStage = class extends ConverterStage {
104141
104370
  }
104142
104371
  if (cadComponent) {
104143
104372
  const models = create3DModelsFromCadComponent(cadComponent, component.center);
104373
+ const KICAD_3D_BASE = "${KIPRJMOD}/3dmodels";
104144
104374
  if (models.length > 0) {
104145
- footprint.models = models;
104375
+ if (this.includeBuiltin3dModels) {
104376
+ footprint.models = models.map((model) => {
104377
+ if (!model.path)
104378
+ return model;
104379
+ const filename = getBasename(model.path);
104380
+ const folderName = this.ctx.projectName ?? filename.replace(/\.[^.]+$/, "");
104381
+ const newModel = new FootprintModel4(`${KICAD_3D_BASE}/${folderName}.3dshapes/${filename}`);
104382
+ if (model.offset)
104383
+ newModel.offset = model.offset;
104384
+ if (model.scale)
104385
+ newModel.scale = model.scale;
104386
+ if (model.rotate)
104387
+ newModel.rotate = model.rotate;
104388
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(model.path)) {
104389
+ this.ctx.pcbModel3dSourcePaths?.push(model.path);
104390
+ }
104391
+ return newModel;
104392
+ });
104393
+ } else {
104394
+ footprint.models = models;
104395
+ }
104396
+ } else if (cadComponent.footprinter_string && this.includeBuiltin3dModels) {
104397
+ const { footprinter_string } = cadComponent;
104398
+ const modelPath = `${KICAD_3D_BASE}/tscircuit_builtin.3dshapes/${footprinter_string}.step`;
104399
+ footprint.models = [new FootprintModel4(modelPath)];
104400
+ const cdnUrl = `${MODEL_CDN_BASE_URL}/${footprinter_string}.step`;
104401
+ if (!this.ctx.pcbModel3dSourcePaths?.includes(cdnUrl)) {
104402
+ this.ctx.pcbModel3dSourcePaths?.push(cdnUrl);
104403
+ }
104146
104404
  }
104147
104405
  }
104148
104406
  const footprintMetadata = component.metadata?.kicad_footprint;
@@ -104371,7 +104629,7 @@ function createFabricationNoteTextFromCircuitJson({
104371
104629
  };
104372
104630
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.Fab";
104373
104631
  const fontSize = (textElement.font_size || 1) / 1.5;
104374
- const font = new TextEffectsFont9;
104632
+ const font = new TextEffectsFont10;
104375
104633
  font.size = { width: fontSize, height: fontSize };
104376
104634
  const justify = new TextEffectsJustify3;
104377
104635
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -104395,13 +104653,13 @@ function createFabricationNoteTextFromCircuitJson({
104395
104653
  case "center":
104396
104654
  break;
104397
104655
  }
104398
- const textEffects = new TextEffects9({
104656
+ const textEffects = new TextEffects10({
104399
104657
  font
104400
104658
  });
104401
104659
  if (anchorAlignment !== "center") {
104402
104660
  textEffects.justify = justify;
104403
104661
  }
104404
- const position = new At([transformedPos.x, transformedPos.y, 0]);
104662
+ const position = new At2([transformedPos.x, transformedPos.y, 0]);
104405
104663
  const grText = new GrText({
104406
104664
  text: textElement.text,
104407
104665
  layer: kicadLayer,
@@ -104427,7 +104685,7 @@ function createGrTextFromCircuitJson({
104427
104685
  };
104428
104686
  const kicadLayer = layerMap[textElement.layer] || textElement.layer || "F.SilkS";
104429
104687
  const fontSize = (textElement.font_size || 1) / 1.5;
104430
- const font = new TextEffectsFont10;
104688
+ const font = new TextEffectsFont11;
104431
104689
  font.size = { width: fontSize, height: fontSize };
104432
104690
  const justify = new TextEffectsJustify4;
104433
104691
  const anchorAlignment = textElement.anchor_alignment || "center";
@@ -104451,14 +104709,14 @@ function createGrTextFromCircuitJson({
104451
104709
  case "center":
104452
104710
  break;
104453
104711
  }
104454
- const textEffects = new TextEffects10({
104712
+ const textEffects = new TextEffects11({
104455
104713
  font
104456
104714
  });
104457
104715
  if (anchorAlignment !== "center") {
104458
104716
  textEffects.justify = justify;
104459
104717
  }
104460
104718
  const rotation = textElement.ccw_rotation || 0;
104461
- const position = new At2([transformedPos.x, transformedPos.y, rotation]);
104719
+ const position = new At3([transformedPos.x, transformedPos.y, rotation]);
104462
104720
  const grText = new GrText2({
104463
104721
  text: textElement.text,
104464
104722
  layer: kicadLayer,
@@ -104584,23 +104842,27 @@ var CircuitJsonToKicadPcbConverter = class {
104584
104842
  get currentStage() {
104585
104843
  return this.pipeline[this.currentStageIndex];
104586
104844
  }
104587
- constructor(circuitJson) {
104845
+ constructor(circuitJson, options) {
104588
104846
  const CIRCUIT_JSON_TO_MM_SCALE = 1;
104589
104847
  const KICAD_PCB_CENTER_X = 100;
104590
104848
  const KICAD_PCB_CENTER_Y = 100;
104591
104849
  this.ctx = {
104592
104850
  db: cju2(circuitJson),
104593
104851
  circuitJson,
104594
- kicadPcb: new KicadPcb({
104852
+ kicadPcb: new KicadPcb2({
104595
104853
  generator: "circuit-json-to-kicad",
104596
104854
  generatorVersion: "0.0.1"
104597
104855
  }),
104598
- c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE))
104856
+ c2kMatPcb: compose4(translate4(KICAD_PCB_CENTER_X, KICAD_PCB_CENTER_Y), scale3(CIRCUIT_JSON_TO_MM_SCALE, -CIRCUIT_JSON_TO_MM_SCALE)),
104857
+ projectName: options?.projectName,
104858
+ pcbModel3dSourcePaths: []
104599
104859
  };
104600
104860
  this.pipeline = [
104601
104861
  new InitializePcbStage(circuitJson, this.ctx),
104602
104862
  new AddNetsStage(circuitJson, this.ctx),
104603
- new AddFootprintsStage(circuitJson, this.ctx),
104863
+ new AddFootprintsStage(circuitJson, this.ctx, {
104864
+ includeBuiltin3dModels: options?.includeBuiltin3dModels
104865
+ }),
104604
104866
  new AddTracesStage(circuitJson, this.ctx),
104605
104867
  new AddViasStage(circuitJson, this.ctx),
104606
104868
  new AddGraphicsStage(circuitJson, this.ctx)
@@ -104627,6 +104889,9 @@ var CircuitJsonToKicadPcbConverter = class {
104627
104889
  getOutputString() {
104628
104890
  return this.ctx.kicadPcb.getString();
104629
104891
  }
104892
+ getModel3dSourcePaths() {
104893
+ return this.ctx.pcbModel3dSourcePaths ?? [];
104894
+ }
104630
104895
  };
104631
104896
  var CircuitJsonToKicadProConverter = class {
104632
104897
  ctx;
@@ -104731,7 +104996,7 @@ var ExtractSymbolsStage = class extends ConverterStage {
104731
104996
  }
104732
104997
  const uniqueSymbols = /* @__PURE__ */ new Map;
104733
104998
  try {
104734
- const parsed = parseKicadSexpr(schContent);
104999
+ const parsed = parseKicadSexpr2(schContent);
104735
105000
  const sch = parsed.find((node) => node instanceof KicadSch2);
104736
105001
  if (!sch) {
104737
105002
  this.ctx.symbolEntries = [];
@@ -104812,227 +105077,6 @@ var ExtractSymbolsStage = class extends ConverterStage {
104812
105077
  return this.ctx.libraryOutput;
104813
105078
  }
104814
105079
  };
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
105080
  var KICAD_SYM_LIB_VERSION = 20211014;
105037
105081
  var GENERATOR = "circuit-json-to-kicad";
105038
105082
  var GenerateSymbolLibraryStage = class extends ConverterStage {
@@ -116291,7 +116335,10 @@ var exportSnippet = async ({
116291
116335
  case "kicad_zip": {
116292
116336
  const schConverter = new CircuitJsonToKicadSchConverter(circuitJson);
116293
116337
  schConverter.runUntilFinished();
116294
- const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson);
116338
+ const pcbConverter = new CircuitJsonToKicadPcbConverter(circuitJson, {
116339
+ includeBuiltin3dModels: true,
116340
+ projectName: outputBaseName
116341
+ });
116295
116342
  pcbConverter.runUntilFinished();
116296
116343
  const proConverter = new CircuitJsonToKicadProConverter(circuitJson, {
116297
116344
  projectName: outputBaseName,
@@ -116303,6 +116350,29 @@ var exportSnippet = async ({
116303
116350
  zip.file(`${outputBaseName}.kicad_sch`, schConverter.getOutputString());
116304
116351
  zip.file(`${outputBaseName}.kicad_pcb`, pcbConverter.getOutputString());
116305
116352
  zip.file(`${outputBaseName}.kicad_pro`, proConverter.getOutputString());
116353
+ const platformFetch = platformConfig?.platformFetch ?? globalThis.fetch;
116354
+ for (const modelPath of pcbConverter.getModel3dSourcePaths()) {
116355
+ const fileName = path56.basename(modelPath);
116356
+ const isRemote = modelPath.startsWith("http://") || modelPath.startsWith("https://");
116357
+ const shapesDir = isRemote ? "tscircuit_builtin.3dshapes" : `${outputBaseName}.3dshapes`;
116358
+ const zipPath = `3dmodels/${shapesDir}/${fileName}`;
116359
+ if (isRemote) {
116360
+ try {
116361
+ const response = await platformFetch(modelPath);
116362
+ if (response.ok) {
116363
+ const buffer = Buffer.from(await response.arrayBuffer());
116364
+ zip.file(zipPath, buffer);
116365
+ }
116366
+ } catch (error) {
116367
+ console.log(`Failed to fetch 3D model from ${modelPath}`);
116368
+ }
116369
+ } else {
116370
+ const resolvedPath = path56.resolve(projectDir, modelPath);
116371
+ if (fs52.existsSync(resolvedPath)) {
116372
+ zip.file(zipPath, await fs52.promises.readFile(resolvedPath));
116373
+ }
116374
+ }
116375
+ }
116306
116376
  outputContent = await zip.generateAsync({ type: "nodebuffer" });
116307
116377
  break;
116308
116378
  }
@@ -222335,6 +222405,7 @@ var snapshotFilesWithWorkerPool = async (options) => {
222335
222405
  schematicOnly: job.options.schematicOnly,
222336
222406
  forceUpdate: job.options.forceUpdate,
222337
222407
  platformConfig: job.options.platformConfig,
222408
+ pcbSnapshotSettings: job.options.pcbSnapshotSettings,
222338
222409
  createDiff: job.options.createDiff,
222339
222410
  cameraPreset: job.options.cameraPreset
222340
222411
  }
@@ -222429,6 +222500,7 @@ var processSnapshotFile = async ({
222429
222500
  schematicOnly,
222430
222501
  forceUpdate,
222431
222502
  platformConfig: platformConfig2,
222503
+ pcbSnapshotSettings,
222432
222504
  createDiff,
222433
222505
  cameraPreset
222434
222506
  }) => {
@@ -222467,7 +222539,7 @@ var processSnapshotFile = async ({
222467
222539
  };
222468
222540
  }
222469
222541
  try {
222470
- pcbSvg = convertCircuitJsonToPcbSvg4(circuitJson);
222542
+ pcbSvg = convertCircuitJsonToPcbSvg4(circuitJson, pcbSnapshotSettings);
222471
222543
  } catch (error) {
222472
222544
  const errorMessage = error instanceof Error ? error.message : String(error);
222473
222545
  return {
@@ -222635,6 +222707,7 @@ var snapshotProject = async ({
222635
222707
  threeD = true;
222636
222708
  }
222637
222709
  const projectDir = process.cwd();
222710
+ const projectConfig2 = loadProjectConfig(projectDir);
222638
222711
  const ignore = [
222639
222712
  ...DEFAULT_IGNORED_PATTERNS,
222640
222713
  ...ignored.map(normalizeIgnorePattern)
@@ -222696,6 +222769,7 @@ var snapshotProject = async ({
222696
222769
  schematicOnly,
222697
222770
  forceUpdate,
222698
222771
  platformConfig: platformConfig2,
222772
+ pcbSnapshotSettings: projectConfig2?.pcbSnapshotSettings,
222699
222773
  createDiff,
222700
222774
  cameraPreset
222701
222775
  },
@@ -222734,6 +222808,7 @@ var snapshotProject = async ({
222734
222808
  schematicOnly,
222735
222809
  forceUpdate,
222736
222810
  platformConfig: platformConfig2,
222811
+ pcbSnapshotSettings: projectConfig2?.pcbSnapshotSettings,
222737
222812
  createDiff,
222738
222813
  cameraPreset
222739
222814
  });