kicad-sch-api 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- kicad_sch_api/__init__.py +2 -2
- kicad_sch_api/core/components.py +69 -2
- kicad_sch_api/core/formatter.py +56 -11
- kicad_sch_api/core/ic_manager.py +187 -0
- kicad_sch_api/core/junctions.py +206 -0
- kicad_sch_api/core/parser.py +637 -35
- kicad_sch_api/core/schematic.py +739 -8
- kicad_sch_api/core/types.py +102 -7
- kicad_sch_api/core/wires.py +248 -0
- kicad_sch_api/library/cache.py +321 -10
- kicad_sch_api/utils/validation.py +3 -3
- {kicad_sch_api-0.0.1.dist-info → kicad_sch_api-0.1.0.dist-info}/METADATA +14 -33
- kicad_sch_api-0.1.0.dist-info/RECORD +21 -0
- kicad_sch_api/mcp/__init__.py +0 -5
- kicad_sch_api/mcp/server.py +0 -500
- kicad_sch_api-0.0.1.dist-info/RECORD +0 -20
- {kicad_sch_api-0.0.1.dist-info → kicad_sch_api-0.1.0.dist-info}/WHEEL +0 -0
- {kicad_sch_api-0.0.1.dist-info → kicad_sch_api-0.1.0.dist-info}/entry_points.txt +0 -0
- {kicad_sch_api-0.0.1.dist-info → kicad_sch_api-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {kicad_sch_api-0.0.1.dist-info → kicad_sch_api-0.1.0.dist-info}/top_level.txt +0 -0
kicad_sch_api/core/parser.py
CHANGED
|
@@ -179,7 +179,9 @@ class SExpressionParser:
|
|
|
179
179
|
schematic_data = {
|
|
180
180
|
"version": None,
|
|
181
181
|
"generator": None,
|
|
182
|
+
"generator_version": None,
|
|
182
183
|
"uuid": None,
|
|
184
|
+
"paper": None,
|
|
183
185
|
"title_block": {},
|
|
184
186
|
"components": [],
|
|
185
187
|
"wires": [],
|
|
@@ -187,6 +189,9 @@ class SExpressionParser:
|
|
|
187
189
|
"labels": [],
|
|
188
190
|
"nets": [],
|
|
189
191
|
"lib_symbols": {},
|
|
192
|
+
"sheet_instances": [],
|
|
193
|
+
"symbol_instances": [],
|
|
194
|
+
"embedded_fonts": None,
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
# Process top-level elements
|
|
@@ -200,9 +205,13 @@ class SExpressionParser:
|
|
|
200
205
|
element_type = str(item[0]) if isinstance(item[0], sexpdata.Symbol) else None
|
|
201
206
|
|
|
202
207
|
if element_type == "version":
|
|
203
|
-
schematic_data["version"] = item[1] if len(item) > 1 else None
|
|
208
|
+
schematic_data["version"] = str(item[1]) if len(item) > 1 else None
|
|
204
209
|
elif element_type == "generator":
|
|
205
210
|
schematic_data["generator"] = item[1] if len(item) > 1 else None
|
|
211
|
+
elif element_type == "generator_version":
|
|
212
|
+
schematic_data["generator_version"] = item[1] if len(item) > 1 else None
|
|
213
|
+
elif element_type == "paper":
|
|
214
|
+
schematic_data["paper"] = item[1] if len(item) > 1 else None
|
|
206
215
|
elif element_type == "uuid":
|
|
207
216
|
schematic_data["uuid"] = item[1] if len(item) > 1 else None
|
|
208
217
|
elif element_type == "title_block":
|
|
@@ -225,6 +234,12 @@ class SExpressionParser:
|
|
|
225
234
|
schematic_data["labels"].append(label)
|
|
226
235
|
elif element_type == "lib_symbols":
|
|
227
236
|
schematic_data["lib_symbols"] = self._parse_lib_symbols(item)
|
|
237
|
+
elif element_type == "sheet_instances":
|
|
238
|
+
schematic_data["sheet_instances"] = self._parse_sheet_instances(item)
|
|
239
|
+
elif element_type == "symbol_instances":
|
|
240
|
+
schematic_data["symbol_instances"] = self._parse_symbol_instances(item)
|
|
241
|
+
elif element_type == "embedded_fonts":
|
|
242
|
+
schematic_data["embedded_fonts"] = item[1] if len(item) > 1 else None
|
|
228
243
|
|
|
229
244
|
return schematic_data
|
|
230
245
|
|
|
@@ -232,25 +247,29 @@ class SExpressionParser:
|
|
|
232
247
|
"""Convert internal schematic format to S-expression data."""
|
|
233
248
|
sexp_data = [sexpdata.Symbol("kicad_sch")]
|
|
234
249
|
|
|
235
|
-
# Add version and generator
|
|
250
|
+
# Add version and generator info
|
|
236
251
|
if schematic_data.get("version"):
|
|
237
|
-
sexp_data.append([sexpdata.Symbol("version"), schematic_data["version"]])
|
|
252
|
+
sexp_data.append([sexpdata.Symbol("version"), int(schematic_data["version"])])
|
|
238
253
|
if schematic_data.get("generator"):
|
|
239
254
|
sexp_data.append([sexpdata.Symbol("generator"), schematic_data["generator"]])
|
|
255
|
+
if schematic_data.get("generator_version"):
|
|
256
|
+
sexp_data.append([sexpdata.Symbol("generator_version"), schematic_data["generator_version"]])
|
|
240
257
|
if schematic_data.get("uuid"):
|
|
241
258
|
sexp_data.append([sexpdata.Symbol("uuid"), schematic_data["uuid"]])
|
|
259
|
+
if schematic_data.get("paper"):
|
|
260
|
+
sexp_data.append([sexpdata.Symbol("paper"), schematic_data["paper"]])
|
|
242
261
|
|
|
243
262
|
# Add title block
|
|
244
263
|
if schematic_data.get("title_block"):
|
|
245
264
|
sexp_data.append(self._title_block_to_sexp(schematic_data["title_block"]))
|
|
246
265
|
|
|
247
|
-
# Add lib_symbols
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
# Add lib_symbols (always include for KiCAD compatibility)
|
|
267
|
+
lib_symbols = schematic_data.get("lib_symbols", {})
|
|
268
|
+
sexp_data.append(self._lib_symbols_to_sexp(lib_symbols))
|
|
250
269
|
|
|
251
270
|
# Add components
|
|
252
271
|
for component in schematic_data.get("components", []):
|
|
253
|
-
sexp_data.append(self._symbol_to_sexp(component))
|
|
272
|
+
sexp_data.append(self._symbol_to_sexp(component, schematic_data.get("uuid")))
|
|
254
273
|
|
|
255
274
|
# Add wires
|
|
256
275
|
for wire in schematic_data.get("wires", []):
|
|
@@ -264,6 +283,36 @@ class SExpressionParser:
|
|
|
264
283
|
for label in schematic_data.get("labels", []):
|
|
265
284
|
sexp_data.append(self._label_to_sexp(label))
|
|
266
285
|
|
|
286
|
+
# Add hierarchical labels
|
|
287
|
+
for hlabel in schematic_data.get("hierarchical_labels", []):
|
|
288
|
+
sexp_data.append(self._hierarchical_label_to_sexp(hlabel))
|
|
289
|
+
|
|
290
|
+
# Add hierarchical sheets
|
|
291
|
+
for sheet in schematic_data.get("sheets", []):
|
|
292
|
+
sexp_data.append(self._sheet_to_sexp(sheet, schematic_data.get("uuid")))
|
|
293
|
+
|
|
294
|
+
# Add text elements
|
|
295
|
+
for text in schematic_data.get("texts", []):
|
|
296
|
+
sexp_data.append(self._text_to_sexp(text))
|
|
297
|
+
|
|
298
|
+
# Add text boxes
|
|
299
|
+
for text_box in schematic_data.get("text_boxes", []):
|
|
300
|
+
sexp_data.append(self._text_box_to_sexp(text_box))
|
|
301
|
+
|
|
302
|
+
# Add sheet_instances (required by KiCAD)
|
|
303
|
+
sheet_instances = schematic_data.get("sheet_instances", [])
|
|
304
|
+
if sheet_instances:
|
|
305
|
+
sexp_data.append(self._sheet_instances_to_sexp(sheet_instances))
|
|
306
|
+
|
|
307
|
+
# Add symbol_instances (required by KiCAD)
|
|
308
|
+
symbol_instances = schematic_data.get("symbol_instances", [])
|
|
309
|
+
if symbol_instances or schematic_data.get("components"):
|
|
310
|
+
sexp_data.append([sexpdata.Symbol("symbol_instances")])
|
|
311
|
+
|
|
312
|
+
# Add embedded_fonts (required by KiCAD)
|
|
313
|
+
if schematic_data.get("embedded_fonts") is not None:
|
|
314
|
+
sexp_data.append([sexpdata.Symbol("embedded_fonts"), schematic_data["embedded_fonts"]])
|
|
315
|
+
|
|
267
316
|
return sexp_data
|
|
268
317
|
|
|
269
318
|
def _parse_title_block(self, item: List[Any]) -> Dict[str, Any]:
|
|
@@ -321,7 +370,11 @@ class SExpressionParser:
|
|
|
321
370
|
elif prop_name == "Footprint":
|
|
322
371
|
symbol_data["footprint"] = prop_data.get("value")
|
|
323
372
|
else:
|
|
324
|
-
|
|
373
|
+
# Unescape quotes in property values when loading
|
|
374
|
+
prop_value = prop_data.get("value")
|
|
375
|
+
if prop_value:
|
|
376
|
+
prop_value = str(prop_value).replace('\\"', '"')
|
|
377
|
+
symbol_data["properties"][prop_name] = prop_value
|
|
325
378
|
elif element_type == "in_bom":
|
|
326
379
|
symbol_data["in_bom"] = sub_item[1] == "yes" if len(sub_item) > 1 else True
|
|
327
380
|
elif element_type == "on_board":
|
|
@@ -372,62 +425,611 @@ class SExpressionParser:
|
|
|
372
425
|
sexp.append([sexpdata.Symbol(key), value])
|
|
373
426
|
return sexp
|
|
374
427
|
|
|
375
|
-
def _symbol_to_sexp(self, symbol_data: Dict[str, Any]) -> List[Any]:
|
|
428
|
+
def _symbol_to_sexp(self, symbol_data: Dict[str, Any], schematic_uuid: str = None) -> List[Any]:
|
|
376
429
|
"""Convert symbol to S-expression."""
|
|
377
430
|
sexp = [sexpdata.Symbol("symbol")]
|
|
378
431
|
|
|
379
432
|
if symbol_data.get("lib_id"):
|
|
380
433
|
sexp.append([sexpdata.Symbol("lib_id"), symbol_data["lib_id"]])
|
|
381
434
|
|
|
382
|
-
# Add position and rotation
|
|
435
|
+
# Add position and rotation (preserve original format)
|
|
383
436
|
pos = symbol_data.get("position", Point(0, 0))
|
|
384
437
|
rotation = symbol_data.get("rotation", 0)
|
|
385
|
-
if
|
|
386
|
-
|
|
387
|
-
else
|
|
388
|
-
|
|
438
|
+
# Format numbers as integers if they are whole numbers
|
|
439
|
+
x = int(pos.x) if pos.x == int(pos.x) else pos.x
|
|
440
|
+
y = int(pos.y) if pos.y == int(pos.y) else pos.y
|
|
441
|
+
r = int(rotation) if rotation == int(rotation) else rotation
|
|
442
|
+
# Always include rotation for format consistency with KiCAD
|
|
443
|
+
sexp.append([sexpdata.Symbol("at"), x, y, r])
|
|
444
|
+
|
|
445
|
+
# Add unit (required by KiCAD)
|
|
446
|
+
unit = symbol_data.get("unit", 1)
|
|
447
|
+
sexp.append([sexpdata.Symbol("unit"), unit])
|
|
448
|
+
|
|
449
|
+
# Add simulation and board settings (required by KiCAD)
|
|
450
|
+
sexp.append([sexpdata.Symbol("exclude_from_sim"), "no"])
|
|
451
|
+
sexp.append([sexpdata.Symbol("in_bom"), "yes" if symbol_data.get("in_bom", True) else "no"])
|
|
452
|
+
sexp.append([sexpdata.Symbol("on_board"), "yes" if symbol_data.get("on_board", True) else "no"])
|
|
453
|
+
sexp.append([sexpdata.Symbol("dnp"), "no"])
|
|
454
|
+
sexp.append([sexpdata.Symbol("fields_autoplaced"), "yes"])
|
|
389
455
|
|
|
390
456
|
if symbol_data.get("uuid"):
|
|
391
457
|
sexp.append([sexpdata.Symbol("uuid"), symbol_data["uuid"]])
|
|
392
458
|
|
|
393
|
-
# Add properties
|
|
459
|
+
# Add properties with proper positioning and effects
|
|
460
|
+
lib_id = symbol_data.get("lib_id", "")
|
|
461
|
+
is_power_symbol = "power:" in lib_id
|
|
462
|
+
|
|
394
463
|
if symbol_data.get("reference"):
|
|
395
|
-
|
|
464
|
+
# Power symbol references should be hidden by default
|
|
465
|
+
ref_hide = is_power_symbol
|
|
466
|
+
ref_prop = self._create_property_with_positioning(
|
|
467
|
+
"Reference", symbol_data["reference"], pos, 0, "left", hide=ref_hide
|
|
468
|
+
)
|
|
469
|
+
sexp.append(ref_prop)
|
|
470
|
+
|
|
396
471
|
if symbol_data.get("value"):
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
472
|
+
# Power symbol values need different positioning
|
|
473
|
+
if is_power_symbol:
|
|
474
|
+
val_prop = self._create_power_symbol_value_property(
|
|
475
|
+
symbol_data["value"], pos, lib_id
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
val_prop = self._create_property_with_positioning(
|
|
479
|
+
"Value", symbol_data["value"], pos, 1, "left"
|
|
480
|
+
)
|
|
481
|
+
sexp.append(val_prop)
|
|
482
|
+
|
|
483
|
+
footprint = symbol_data.get("footprint")
|
|
484
|
+
if footprint is not None: # Include empty strings but not None
|
|
485
|
+
fp_prop = self._create_property_with_positioning(
|
|
486
|
+
"Footprint", footprint, pos, 2, "left", hide=True
|
|
487
|
+
)
|
|
488
|
+
sexp.append(fp_prop)
|
|
400
489
|
|
|
401
490
|
for prop_name, prop_value in symbol_data.get("properties", {}).items():
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
)
|
|
491
|
+
escaped_value = str(prop_value).replace('"', '\\"')
|
|
492
|
+
prop = self._create_property_with_positioning(
|
|
493
|
+
prop_name, escaped_value, pos, 3, "left", hide=True
|
|
494
|
+
)
|
|
495
|
+
sexp.append(prop)
|
|
496
|
+
|
|
497
|
+
# Add pin UUID assignments (required by KiCAD)
|
|
498
|
+
for pin in symbol_data.get("pins", []):
|
|
499
|
+
pin_uuid = str(uuid.uuid4())
|
|
500
|
+
# Ensure pin number is a string for proper quoting
|
|
501
|
+
pin_number = str(pin.number)
|
|
502
|
+
sexp.append([sexpdata.Symbol("pin"), pin_number, [sexpdata.Symbol("uuid"), pin_uuid]])
|
|
503
|
+
|
|
504
|
+
# Add instances section (required by KiCAD)
|
|
505
|
+
project_name = "simple_circuit" # TODO: Get from schematic context
|
|
506
|
+
root_uuid = schematic_uuid or symbol_data.get("root_uuid", str(uuid.uuid4()))
|
|
507
|
+
logger.debug(f"🔧 Using UUID {root_uuid} for component {symbol_data.get('reference', 'unknown')}")
|
|
508
|
+
logger.debug(f"🔧 Component properties keys: {list(symbol_data.get('properties', {}).keys())}")
|
|
509
|
+
sexp.append([
|
|
510
|
+
sexpdata.Symbol("instances"),
|
|
511
|
+
[sexpdata.Symbol("project"), project_name,
|
|
512
|
+
[sexpdata.Symbol("path"), f"/{root_uuid}",
|
|
513
|
+
[sexpdata.Symbol("reference"), symbol_data.get("reference", "U?")],
|
|
514
|
+
[sexpdata.Symbol("unit"), symbol_data.get("unit", 1)]]]
|
|
515
|
+
])
|
|
409
516
|
|
|
410
517
|
return sexp
|
|
411
518
|
|
|
519
|
+
def _create_property_with_positioning(self, prop_name: str, prop_value: str,
|
|
520
|
+
component_pos: Point, offset_index: int,
|
|
521
|
+
justify: str = "left", hide: bool = False) -> List[Any]:
|
|
522
|
+
"""Create a property with proper positioning and effects like KiCAD."""
|
|
523
|
+
# Calculate property position relative to component
|
|
524
|
+
# Based on KiCAD's positioning: Reference above component, Value below
|
|
525
|
+
prop_x = component_pos.x + 2.54 # Standard offset to the right
|
|
526
|
+
|
|
527
|
+
if prop_name == "Reference":
|
|
528
|
+
prop_y = component_pos.y - 1.27 # Reference above component
|
|
529
|
+
elif prop_name == "Value":
|
|
530
|
+
prop_y = component_pos.y + 1.27 # Value below component
|
|
531
|
+
else:
|
|
532
|
+
prop_y = component_pos.y + (1.27 * (offset_index + 1)) # Other properties below
|
|
533
|
+
|
|
534
|
+
prop_sexp = [
|
|
535
|
+
sexpdata.Symbol("property"),
|
|
536
|
+
prop_name,
|
|
537
|
+
prop_value,
|
|
538
|
+
[sexpdata.Symbol("at"),
|
|
539
|
+
round(prop_x, 4) if prop_x != int(prop_x) else int(prop_x),
|
|
540
|
+
round(prop_y, 4) if prop_y != int(prop_y) else int(prop_y),
|
|
541
|
+
0],
|
|
542
|
+
[sexpdata.Symbol("effects"),
|
|
543
|
+
[sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
|
|
544
|
+
[sexpdata.Symbol("justify"), sexpdata.Symbol(justify)]]
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
if hide:
|
|
548
|
+
prop_sexp[4].append([sexpdata.Symbol("hide"), sexpdata.Symbol("yes")])
|
|
549
|
+
|
|
550
|
+
return prop_sexp
|
|
551
|
+
|
|
552
|
+
def _create_power_symbol_value_property(self, value: str, component_pos: Point, lib_id: str) -> List[Any]:
|
|
553
|
+
"""Create Value property for power symbols with correct positioning."""
|
|
554
|
+
# Power symbols have different value positioning based on type
|
|
555
|
+
if "GND" in lib_id:
|
|
556
|
+
# GND value goes below the symbol
|
|
557
|
+
prop_x = component_pos.x
|
|
558
|
+
prop_y = component_pos.y + 5.08 # Below GND symbol
|
|
559
|
+
elif "+3.3V" in lib_id or "VDD" in lib_id:
|
|
560
|
+
# Positive voltage values go below the symbol
|
|
561
|
+
prop_x = component_pos.x
|
|
562
|
+
prop_y = component_pos.y - 5.08 # Above symbol (negative offset)
|
|
563
|
+
else:
|
|
564
|
+
# Default power symbol positioning
|
|
565
|
+
prop_x = component_pos.x
|
|
566
|
+
prop_y = component_pos.y + 3.556
|
|
567
|
+
|
|
568
|
+
prop_sexp = [
|
|
569
|
+
sexpdata.Symbol("property"),
|
|
570
|
+
"Value",
|
|
571
|
+
value,
|
|
572
|
+
[sexpdata.Symbol("at"),
|
|
573
|
+
round(prop_x, 4) if prop_x != int(prop_x) else int(prop_x),
|
|
574
|
+
round(prop_y, 4) if prop_y != int(prop_y) else int(prop_y),
|
|
575
|
+
0],
|
|
576
|
+
[sexpdata.Symbol("effects"),
|
|
577
|
+
[sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]]
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
return prop_sexp
|
|
581
|
+
|
|
412
582
|
def _wire_to_sexp(self, wire_data: Dict[str, Any]) -> List[Any]:
|
|
413
583
|
"""Convert wire to S-expression."""
|
|
414
|
-
|
|
415
|
-
|
|
584
|
+
sexp = [sexpdata.Symbol("wire")]
|
|
585
|
+
|
|
586
|
+
# Add points (pts section)
|
|
587
|
+
points = wire_data.get("points", [])
|
|
588
|
+
if len(points) >= 2:
|
|
589
|
+
pts_sexp = [sexpdata.Symbol("pts")]
|
|
590
|
+
for point in points:
|
|
591
|
+
if isinstance(point, dict):
|
|
592
|
+
x, y = point["x"], point["y"]
|
|
593
|
+
elif isinstance(point, (list, tuple)) and len(point) >= 2:
|
|
594
|
+
x, y = point[0], point[1]
|
|
595
|
+
else:
|
|
596
|
+
# Assume it's a Point object
|
|
597
|
+
x, y = point.x, point.y
|
|
598
|
+
|
|
599
|
+
# Format coordinates properly (avoid unnecessary .0 for integers)
|
|
600
|
+
if isinstance(x, float) and x.is_integer():
|
|
601
|
+
x = int(x)
|
|
602
|
+
if isinstance(y, float) and y.is_integer():
|
|
603
|
+
y = int(y)
|
|
604
|
+
|
|
605
|
+
pts_sexp.append([sexpdata.Symbol("xy"), x, y])
|
|
606
|
+
sexp.append(pts_sexp)
|
|
607
|
+
|
|
608
|
+
# Add stroke information
|
|
609
|
+
stroke_width = wire_data.get("stroke_width", 0)
|
|
610
|
+
stroke_type = wire_data.get("stroke_type", "default")
|
|
611
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
612
|
+
|
|
613
|
+
# Format stroke width (use int for 0, preserve float for others)
|
|
614
|
+
if isinstance(stroke_width, float) and stroke_width == 0.0:
|
|
615
|
+
stroke_width = 0
|
|
616
|
+
|
|
617
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
618
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
619
|
+
sexp.append(stroke_sexp)
|
|
620
|
+
|
|
621
|
+
# Add UUID
|
|
622
|
+
if "uuid" in wire_data:
|
|
623
|
+
sexp.append([sexpdata.Symbol("uuid"), wire_data["uuid"]])
|
|
624
|
+
|
|
625
|
+
return sexp
|
|
416
626
|
|
|
417
627
|
def _junction_to_sexp(self, junction_data: Dict[str, Any]) -> List[Any]:
|
|
418
628
|
"""Convert junction to S-expression."""
|
|
419
|
-
|
|
420
|
-
|
|
629
|
+
sexp = [sexpdata.Symbol("junction")]
|
|
630
|
+
|
|
631
|
+
# Add position
|
|
632
|
+
pos = junction_data["position"]
|
|
633
|
+
if isinstance(pos, dict):
|
|
634
|
+
x, y = pos["x"], pos["y"]
|
|
635
|
+
elif isinstance(pos, (list, tuple)) and len(pos) >= 2:
|
|
636
|
+
x, y = pos[0], pos[1]
|
|
637
|
+
else:
|
|
638
|
+
# Assume it's a Point object
|
|
639
|
+
x, y = pos.x, pos.y
|
|
640
|
+
|
|
641
|
+
# Format coordinates properly
|
|
642
|
+
if isinstance(x, float) and x.is_integer():
|
|
643
|
+
x = int(x)
|
|
644
|
+
if isinstance(y, float) and y.is_integer():
|
|
645
|
+
y = int(y)
|
|
646
|
+
|
|
647
|
+
sexp.append([sexpdata.Symbol("at"), x, y])
|
|
648
|
+
|
|
649
|
+
# Add diameter
|
|
650
|
+
diameter = junction_data.get("diameter", 0)
|
|
651
|
+
sexp.append([sexpdata.Symbol("diameter"), diameter])
|
|
652
|
+
|
|
653
|
+
# Add color (RGBA)
|
|
654
|
+
color = junction_data.get("color", (0, 0, 0, 0))
|
|
655
|
+
if isinstance(color, (list, tuple)) and len(color) >= 4:
|
|
656
|
+
sexp.append([sexpdata.Symbol("color"), color[0], color[1], color[2], color[3]])
|
|
657
|
+
else:
|
|
658
|
+
sexp.append([sexpdata.Symbol("color"), 0, 0, 0, 0])
|
|
659
|
+
|
|
660
|
+
# Add UUID
|
|
661
|
+
if "uuid" in junction_data:
|
|
662
|
+
sexp.append([sexpdata.Symbol("uuid"), junction_data["uuid"]])
|
|
663
|
+
|
|
664
|
+
return sexp
|
|
421
665
|
|
|
422
666
|
def _label_to_sexp(self, label_data: Dict[str, Any]) -> List[Any]:
|
|
423
|
-
"""Convert label to S-expression."""
|
|
424
|
-
|
|
425
|
-
|
|
667
|
+
"""Convert local label to S-expression."""
|
|
668
|
+
sexp = [sexpdata.Symbol("label"), label_data["text"]]
|
|
669
|
+
|
|
670
|
+
# Add position
|
|
671
|
+
pos = label_data["position"]
|
|
672
|
+
x, y = pos["x"], pos["y"]
|
|
673
|
+
rotation = label_data.get("rotation", 0)
|
|
674
|
+
|
|
675
|
+
# Format coordinates properly
|
|
676
|
+
if isinstance(x, float) and x.is_integer():
|
|
677
|
+
x = int(x)
|
|
678
|
+
if isinstance(y, float) and y.is_integer():
|
|
679
|
+
y = int(y)
|
|
680
|
+
|
|
681
|
+
sexp.append([sexpdata.Symbol("at"), x, y, rotation])
|
|
682
|
+
|
|
683
|
+
# Add effects (font properties)
|
|
684
|
+
size = label_data.get("size", 1.27)
|
|
685
|
+
effects = [sexpdata.Symbol("effects")]
|
|
686
|
+
font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
|
|
687
|
+
effects.append(font)
|
|
688
|
+
effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("bottom")])
|
|
689
|
+
sexp.append(effects)
|
|
690
|
+
|
|
691
|
+
# Add UUID
|
|
692
|
+
if "uuid" in label_data:
|
|
693
|
+
sexp.append([sexpdata.Symbol("uuid"), label_data["uuid"]])
|
|
694
|
+
|
|
695
|
+
return sexp
|
|
696
|
+
|
|
697
|
+
def _hierarchical_label_to_sexp(self, hlabel_data: Dict[str, Any]) -> List[Any]:
|
|
698
|
+
"""Convert hierarchical label to S-expression."""
|
|
699
|
+
sexp = [sexpdata.Symbol("hierarchical_label"), hlabel_data["text"]]
|
|
700
|
+
|
|
701
|
+
# Add shape
|
|
702
|
+
shape = hlabel_data.get("shape", "input")
|
|
703
|
+
sexp.append([sexpdata.Symbol("shape"), sexpdata.Symbol(shape)])
|
|
704
|
+
|
|
705
|
+
# Add position
|
|
706
|
+
pos = hlabel_data["position"]
|
|
707
|
+
x, y = pos["x"], pos["y"]
|
|
708
|
+
rotation = hlabel_data.get("rotation", 0)
|
|
709
|
+
sexp.append([sexpdata.Symbol("at"), x, y, rotation])
|
|
710
|
+
|
|
711
|
+
# Add effects (font properties)
|
|
712
|
+
size = hlabel_data.get("size", 1.27)
|
|
713
|
+
effects = [sexpdata.Symbol("effects")]
|
|
714
|
+
font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
|
|
715
|
+
effects.append(font)
|
|
716
|
+
effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol("left")])
|
|
717
|
+
sexp.append(effects)
|
|
718
|
+
|
|
719
|
+
# Add UUID
|
|
720
|
+
if "uuid" in hlabel_data:
|
|
721
|
+
sexp.append([sexpdata.Symbol("uuid"), hlabel_data["uuid"]])
|
|
722
|
+
|
|
723
|
+
return sexp
|
|
724
|
+
|
|
725
|
+
def _sheet_to_sexp(self, sheet_data: Dict[str, Any], schematic_uuid: str) -> List[Any]:
|
|
726
|
+
"""Convert hierarchical sheet to S-expression."""
|
|
727
|
+
sexp = [sexpdata.Symbol("sheet")]
|
|
728
|
+
|
|
729
|
+
# Add position
|
|
730
|
+
pos = sheet_data["position"]
|
|
731
|
+
x, y = pos["x"], pos["y"]
|
|
732
|
+
if isinstance(x, float) and x.is_integer():
|
|
733
|
+
x = int(x)
|
|
734
|
+
if isinstance(y, float) and y.is_integer():
|
|
735
|
+
y = int(y)
|
|
736
|
+
sexp.append([sexpdata.Symbol("at"), x, y])
|
|
737
|
+
|
|
738
|
+
# Add size
|
|
739
|
+
size = sheet_data["size"]
|
|
740
|
+
w, h = size["width"], size["height"]
|
|
741
|
+
sexp.append([sexpdata.Symbol("size"), w, h])
|
|
742
|
+
|
|
743
|
+
# Add basic properties
|
|
744
|
+
sexp.append([sexpdata.Symbol("exclude_from_sim"),
|
|
745
|
+
sexpdata.Symbol("yes" if sheet_data.get("exclude_from_sim", False) else "no")])
|
|
746
|
+
sexp.append([sexpdata.Symbol("in_bom"),
|
|
747
|
+
sexpdata.Symbol("yes" if sheet_data.get("in_bom", True) else "no")])
|
|
748
|
+
sexp.append([sexpdata.Symbol("on_board"),
|
|
749
|
+
sexpdata.Symbol("yes" if sheet_data.get("on_board", True) else "no")])
|
|
750
|
+
sexp.append([sexpdata.Symbol("dnp"),
|
|
751
|
+
sexpdata.Symbol("yes" if sheet_data.get("dnp", False) else "no")])
|
|
752
|
+
sexp.append([sexpdata.Symbol("fields_autoplaced"),
|
|
753
|
+
sexpdata.Symbol("yes" if sheet_data.get("fields_autoplaced", True) else "no")])
|
|
754
|
+
|
|
755
|
+
# Add stroke
|
|
756
|
+
stroke_width = sheet_data.get("stroke_width", 0.1524)
|
|
757
|
+
stroke_type = sheet_data.get("stroke_type", "solid")
|
|
758
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
759
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
760
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
761
|
+
sexp.append(stroke_sexp)
|
|
762
|
+
|
|
763
|
+
# Add fill
|
|
764
|
+
fill_color = sheet_data.get("fill_color", (0, 0, 0, 0.0))
|
|
765
|
+
fill_sexp = [sexpdata.Symbol("fill")]
|
|
766
|
+
fill_sexp.append([sexpdata.Symbol("color"), fill_color[0], fill_color[1], fill_color[2], fill_color[3]])
|
|
767
|
+
sexp.append(fill_sexp)
|
|
768
|
+
|
|
769
|
+
# Add UUID
|
|
770
|
+
if "uuid" in sheet_data:
|
|
771
|
+
sexp.append([sexpdata.Symbol("uuid"), sheet_data["uuid"]])
|
|
772
|
+
|
|
773
|
+
# Add sheet properties (name and filename)
|
|
774
|
+
name = sheet_data.get("name", "Sheet")
|
|
775
|
+
filename = sheet_data.get("filename", "sheet.kicad_sch")
|
|
776
|
+
|
|
777
|
+
# Sheetname property
|
|
778
|
+
name_prop = [sexpdata.Symbol("property"), "Sheetname", name]
|
|
779
|
+
name_prop.append([sexpdata.Symbol("at"), x, y - 0.7116, 0]) # Above sheet
|
|
780
|
+
name_prop.append([sexpdata.Symbol("effects"),
|
|
781
|
+
[sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
|
|
782
|
+
[sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("bottom")]])
|
|
783
|
+
sexp.append(name_prop)
|
|
784
|
+
|
|
785
|
+
# Sheetfile property
|
|
786
|
+
file_prop = [sexpdata.Symbol("property"), "Sheetfile", filename]
|
|
787
|
+
file_prop.append([sexpdata.Symbol("at"), x, y + h + 0.5754, 0]) # Below sheet
|
|
788
|
+
file_prop.append([sexpdata.Symbol("effects"),
|
|
789
|
+
[sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]],
|
|
790
|
+
[sexpdata.Symbol("justify"), sexpdata.Symbol("left"), sexpdata.Symbol("top")]])
|
|
791
|
+
sexp.append(file_prop)
|
|
792
|
+
|
|
793
|
+
# Add sheet pins if any
|
|
794
|
+
for pin in sheet_data.get("pins", []):
|
|
795
|
+
pin_sexp = self._sheet_pin_to_sexp(pin)
|
|
796
|
+
sexp.append(pin_sexp)
|
|
797
|
+
|
|
798
|
+
# Add instances
|
|
799
|
+
if schematic_uuid:
|
|
800
|
+
instances_sexp = [sexpdata.Symbol("instances")]
|
|
801
|
+
project_name = sheet_data.get("project_name", "")
|
|
802
|
+
page_number = sheet_data.get("page_number", "2")
|
|
803
|
+
project_sexp = [sexpdata.Symbol("project"), project_name]
|
|
804
|
+
path_sexp = [sexpdata.Symbol("path"), f"/{schematic_uuid}"]
|
|
805
|
+
path_sexp.append([sexpdata.Symbol("page"), page_number])
|
|
806
|
+
project_sexp.append(path_sexp)
|
|
807
|
+
instances_sexp.append(project_sexp)
|
|
808
|
+
sexp.append(instances_sexp)
|
|
809
|
+
|
|
810
|
+
return sexp
|
|
811
|
+
|
|
812
|
+
def _sheet_pin_to_sexp(self, pin_data: Dict[str, Any]) -> List[Any]:
|
|
813
|
+
"""Convert sheet pin to S-expression."""
|
|
814
|
+
pin_sexp = [sexpdata.Symbol("pin"), pin_data["name"], sexpdata.Symbol(pin_data.get("pin_type", "input"))]
|
|
815
|
+
|
|
816
|
+
# Add position
|
|
817
|
+
pos = pin_data["position"]
|
|
818
|
+
x, y = pos["x"], pos["y"]
|
|
819
|
+
rotation = pin_data.get("rotation", 0)
|
|
820
|
+
pin_sexp.append([sexpdata.Symbol("at"), x, y, rotation])
|
|
821
|
+
|
|
822
|
+
# Add UUID
|
|
823
|
+
if "uuid" in pin_data:
|
|
824
|
+
pin_sexp.append([sexpdata.Symbol("uuid"), pin_data["uuid"]])
|
|
825
|
+
|
|
826
|
+
# Add effects
|
|
827
|
+
size = pin_data.get("size", 1.27)
|
|
828
|
+
effects = [sexpdata.Symbol("effects")]
|
|
829
|
+
font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
|
|
830
|
+
effects.append(font)
|
|
831
|
+
justify = pin_data.get("justify", "right")
|
|
832
|
+
effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol(justify)])
|
|
833
|
+
pin_sexp.append(effects)
|
|
834
|
+
|
|
835
|
+
return pin_sexp
|
|
836
|
+
|
|
837
|
+
def _text_to_sexp(self, text_data: Dict[str, Any]) -> List[Any]:
|
|
838
|
+
"""Convert text element to S-expression."""
|
|
839
|
+
sexp = [sexpdata.Symbol("text"), text_data["text"]]
|
|
840
|
+
|
|
841
|
+
# Add exclude_from_sim
|
|
842
|
+
exclude_sim = text_data.get("exclude_from_sim", False)
|
|
843
|
+
sexp.append([sexpdata.Symbol("exclude_from_sim"),
|
|
844
|
+
sexpdata.Symbol("yes" if exclude_sim else "no")])
|
|
845
|
+
|
|
846
|
+
# Add position
|
|
847
|
+
pos = text_data["position"]
|
|
848
|
+
x, y = pos["x"], pos["y"]
|
|
849
|
+
rotation = text_data.get("rotation", 0)
|
|
850
|
+
|
|
851
|
+
# Format coordinates properly
|
|
852
|
+
if isinstance(x, float) and x.is_integer():
|
|
853
|
+
x = int(x)
|
|
854
|
+
if isinstance(y, float) and y.is_integer():
|
|
855
|
+
y = int(y)
|
|
856
|
+
|
|
857
|
+
sexp.append([sexpdata.Symbol("at"), x, y, rotation])
|
|
858
|
+
|
|
859
|
+
# Add effects (font properties)
|
|
860
|
+
size = text_data.get("size", 1.27)
|
|
861
|
+
effects = [sexpdata.Symbol("effects")]
|
|
862
|
+
font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]]
|
|
863
|
+
effects.append(font)
|
|
864
|
+
sexp.append(effects)
|
|
865
|
+
|
|
866
|
+
# Add UUID
|
|
867
|
+
if "uuid" in text_data:
|
|
868
|
+
sexp.append([sexpdata.Symbol("uuid"), text_data["uuid"]])
|
|
869
|
+
|
|
870
|
+
return sexp
|
|
871
|
+
|
|
872
|
+
def _text_box_to_sexp(self, text_box_data: Dict[str, Any]) -> List[Any]:
|
|
873
|
+
"""Convert text box element to S-expression."""
|
|
874
|
+
sexp = [sexpdata.Symbol("text_box"), text_box_data["text"]]
|
|
875
|
+
|
|
876
|
+
# Add exclude_from_sim
|
|
877
|
+
exclude_sim = text_box_data.get("exclude_from_sim", False)
|
|
878
|
+
sexp.append([sexpdata.Symbol("exclude_from_sim"),
|
|
879
|
+
sexpdata.Symbol("yes" if exclude_sim else "no")])
|
|
880
|
+
|
|
881
|
+
# Add position
|
|
882
|
+
pos = text_box_data["position"]
|
|
883
|
+
x, y = pos["x"], pos["y"]
|
|
884
|
+
rotation = text_box_data.get("rotation", 0)
|
|
885
|
+
|
|
886
|
+
# Format coordinates properly
|
|
887
|
+
if isinstance(x, float) and x.is_integer():
|
|
888
|
+
x = int(x)
|
|
889
|
+
if isinstance(y, float) and y.is_integer():
|
|
890
|
+
y = int(y)
|
|
891
|
+
|
|
892
|
+
sexp.append([sexpdata.Symbol("at"), x, y, rotation])
|
|
893
|
+
|
|
894
|
+
# Add size
|
|
895
|
+
size = text_box_data["size"]
|
|
896
|
+
w, h = size["width"], size["height"]
|
|
897
|
+
sexp.append([sexpdata.Symbol("size"), w, h])
|
|
898
|
+
|
|
899
|
+
# Add margins
|
|
900
|
+
margins = text_box_data.get("margins", (0.9525, 0.9525, 0.9525, 0.9525))
|
|
901
|
+
sexp.append([sexpdata.Symbol("margins"), margins[0], margins[1], margins[2], margins[3]])
|
|
902
|
+
|
|
903
|
+
# Add stroke
|
|
904
|
+
stroke_width = text_box_data.get("stroke_width", 0)
|
|
905
|
+
stroke_type = text_box_data.get("stroke_type", "solid")
|
|
906
|
+
stroke_sexp = [sexpdata.Symbol("stroke")]
|
|
907
|
+
stroke_sexp.append([sexpdata.Symbol("width"), stroke_width])
|
|
908
|
+
stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)])
|
|
909
|
+
sexp.append(stroke_sexp)
|
|
910
|
+
|
|
911
|
+
# Add fill
|
|
912
|
+
fill_type = text_box_data.get("fill_type", "none")
|
|
913
|
+
fill_sexp = [sexpdata.Symbol("fill")]
|
|
914
|
+
fill_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(fill_type)])
|
|
915
|
+
sexp.append(fill_sexp)
|
|
916
|
+
|
|
917
|
+
# Add effects (font properties and justification)
|
|
918
|
+
font_size = text_box_data.get("font_size", 1.27)
|
|
919
|
+
justify_h = text_box_data.get("justify_horizontal", "left")
|
|
920
|
+
justify_v = text_box_data.get("justify_vertical", "top")
|
|
921
|
+
|
|
922
|
+
effects = [sexpdata.Symbol("effects")]
|
|
923
|
+
font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), font_size, font_size]]
|
|
924
|
+
effects.append(font)
|
|
925
|
+
effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol(justify_h), sexpdata.Symbol(justify_v)])
|
|
926
|
+
sexp.append(effects)
|
|
927
|
+
|
|
928
|
+
# Add UUID
|
|
929
|
+
if "uuid" in text_box_data:
|
|
930
|
+
sexp.append([sexpdata.Symbol("uuid"), text_box_data["uuid"]])
|
|
931
|
+
|
|
932
|
+
return sexp
|
|
426
933
|
|
|
427
934
|
def _lib_symbols_to_sexp(self, lib_symbols: Dict[str, Any]) -> List[Any]:
|
|
428
935
|
"""Convert lib_symbols to S-expression."""
|
|
429
|
-
|
|
430
|
-
|
|
936
|
+
sexp = [sexpdata.Symbol("lib_symbols")]
|
|
937
|
+
|
|
938
|
+
# Add each symbol definition
|
|
939
|
+
for symbol_name, symbol_def in lib_symbols.items():
|
|
940
|
+
if isinstance(symbol_def, list):
|
|
941
|
+
# Raw S-expression data from parsed library file - use directly
|
|
942
|
+
sexp.append(symbol_def)
|
|
943
|
+
elif isinstance(symbol_def, dict):
|
|
944
|
+
# Dictionary format - convert to S-expression
|
|
945
|
+
symbol_sexp = self._create_basic_symbol_definition(symbol_name)
|
|
946
|
+
sexp.append(symbol_sexp)
|
|
947
|
+
|
|
948
|
+
return sexp
|
|
949
|
+
|
|
950
|
+
def _create_basic_symbol_definition(self, lib_id: str) -> List[Any]:
|
|
951
|
+
"""Create a basic symbol definition for KiCAD compatibility."""
|
|
952
|
+
symbol_sexp = [sexpdata.Symbol("symbol"), lib_id]
|
|
953
|
+
|
|
954
|
+
# Add basic symbol properties
|
|
955
|
+
symbol_sexp.extend([
|
|
956
|
+
[sexpdata.Symbol("pin_numbers"), [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]],
|
|
957
|
+
[sexpdata.Symbol("pin_names"), [sexpdata.Symbol("offset"), 0]],
|
|
958
|
+
[sexpdata.Symbol("exclude_from_sim"), sexpdata.Symbol("no")],
|
|
959
|
+
[sexpdata.Symbol("in_bom"), sexpdata.Symbol("yes")],
|
|
960
|
+
[sexpdata.Symbol("on_board"), sexpdata.Symbol("yes")]
|
|
961
|
+
])
|
|
962
|
+
|
|
963
|
+
# Add basic properties for the symbol
|
|
964
|
+
if "R" in lib_id: # Resistor
|
|
965
|
+
symbol_sexp.extend([
|
|
966
|
+
[sexpdata.Symbol("property"), "Reference", "R",
|
|
967
|
+
[sexpdata.Symbol("at"), 2.032, 0, 90],
|
|
968
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]]],
|
|
969
|
+
[sexpdata.Symbol("property"), "Value", "R",
|
|
970
|
+
[sexpdata.Symbol("at"), 0, 0, 90],
|
|
971
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]]],
|
|
972
|
+
[sexpdata.Symbol("property"), "Footprint", "",
|
|
973
|
+
[sexpdata.Symbol("at"), -1.778, 0, 90],
|
|
974
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]], [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]]],
|
|
975
|
+
[sexpdata.Symbol("property"), "Datasheet", "~",
|
|
976
|
+
[sexpdata.Symbol("at"), 0, 0, 0],
|
|
977
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]], [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]]]
|
|
978
|
+
])
|
|
979
|
+
|
|
980
|
+
elif "C" in lib_id: # Capacitor
|
|
981
|
+
symbol_sexp.extend([
|
|
982
|
+
[sexpdata.Symbol("property"), "Reference", "C",
|
|
983
|
+
[sexpdata.Symbol("at"), 0.635, 2.54, 0],
|
|
984
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]]],
|
|
985
|
+
[sexpdata.Symbol("property"), "Value", "C",
|
|
986
|
+
[sexpdata.Symbol("at"), 0.635, -2.54, 0],
|
|
987
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]]],
|
|
988
|
+
[sexpdata.Symbol("property"), "Footprint", "",
|
|
989
|
+
[sexpdata.Symbol("at"), 0, -1.27, 0],
|
|
990
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]], [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]]],
|
|
991
|
+
[sexpdata.Symbol("property"), "Datasheet", "~",
|
|
992
|
+
[sexpdata.Symbol("at"), 0, 0, 0],
|
|
993
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]], [sexpdata.Symbol("hide"), sexpdata.Symbol("yes")]]]
|
|
994
|
+
])
|
|
995
|
+
|
|
996
|
+
# Add basic graphics and pins (minimal for now)
|
|
997
|
+
symbol_sexp.append([sexpdata.Symbol("embedded_fonts"), sexpdata.Symbol("no")])
|
|
998
|
+
|
|
999
|
+
return symbol_sexp
|
|
1000
|
+
|
|
1001
|
+
def _parse_sheet_instances(self, item: List[Any]) -> List[Dict[str, Any]]:
|
|
1002
|
+
"""Parse sheet_instances section."""
|
|
1003
|
+
sheet_instances = []
|
|
1004
|
+
for sheet_item in item[1:]: # Skip 'sheet_instances' header
|
|
1005
|
+
if isinstance(sheet_item, list) and len(sheet_item) > 0:
|
|
1006
|
+
sheet_data = {"path": "/", "page": "1"}
|
|
1007
|
+
for element in sheet_item[1:]: # Skip element header
|
|
1008
|
+
if isinstance(element, list) and len(element) >= 2:
|
|
1009
|
+
key = str(element[0]) if isinstance(element[0], sexpdata.Symbol) else str(element[0])
|
|
1010
|
+
if key == "path":
|
|
1011
|
+
sheet_data["path"] = element[1]
|
|
1012
|
+
elif key == "page":
|
|
1013
|
+
sheet_data["page"] = element[1]
|
|
1014
|
+
sheet_instances.append(sheet_data)
|
|
1015
|
+
return sheet_instances
|
|
1016
|
+
|
|
1017
|
+
def _parse_symbol_instances(self, item: List[Any]) -> List[Any]:
|
|
1018
|
+
"""Parse symbol_instances section."""
|
|
1019
|
+
# For now, just return the raw structure minus the header
|
|
1020
|
+
return item[1:] if len(item) > 1 else []
|
|
1021
|
+
|
|
1022
|
+
def _sheet_instances_to_sexp(self, sheet_instances: List[Dict[str, Any]]) -> List[Any]:
|
|
1023
|
+
"""Convert sheet_instances to S-expression."""
|
|
1024
|
+
sexp = [sexpdata.Symbol("sheet_instances")]
|
|
1025
|
+
for sheet in sheet_instances:
|
|
1026
|
+
# Create: (path "/" (page "1"))
|
|
1027
|
+
sheet_sexp = [
|
|
1028
|
+
sexpdata.Symbol("path"), sheet.get("path", "/"),
|
|
1029
|
+
[sexpdata.Symbol("page"), str(sheet.get("page", "1"))]
|
|
1030
|
+
]
|
|
1031
|
+
sexp.append(sheet_sexp)
|
|
1032
|
+
return sexp
|
|
431
1033
|
|
|
432
1034
|
def get_validation_issues(self) -> List[ValidationIssue]:
|
|
433
1035
|
"""Get list of validation issues from last parse operation."""
|