Skip to content

Initial placement generation for svg-pcb backend#413

Merged
ducky64 merged 25 commits intomasterfrom
footprintpacking
Jun 23, 2025
Merged

Initial placement generation for svg-pcb backend#413
ducky64 merged 25 commits intomasterfrom
footprintpacking

Conversation

@ducky64
Copy link
Copy Markdown
Collaborator

@ducky64 ducky64 commented Jun 23, 2025

Changes to the experimental svg-pcb backend. Adds a better initial placement, instead of all components stacked, tries to pack components with a fixed aspect ratio, recursively by group bottom-up. Also now generates the entire svg-pcb code block instead of just snippets.

Also adds svgpcb backend to unit test outputs.

Changes the footprint data json to store area and bounding box. Moves the FootprintDataTable into electronics_model, where the svg-pcb backend and the footprint data generation is now.

@ducky64 ducky64 requested a review from Copilot June 23, 2025 04:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances the SVG-PCB backend by adding a hierarchical initial placement algorithm, moving and extending footprint metadata (area and bounding box), updating example outputs with concrete placement coordinates, and bundling the full SVG-PCB code block.

  • Introduces arrange_netlist / flatten_packed_block for recursive placement and generates complete PCB code including outline and rendering.
  • Moves footprint area/bbox JSON handling into FootprintDataTable under electronics_model, replacing scattered FootprintAreaTable references.
  • Updates all example .svgpcb.js files with generated translation coordinates instead of stacked defaults.

Reviewed Changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
examples/.../*.svgpcb.js Replace default translate: pt(0,0) with computed coordinates from the new placer
edg/parts/JlcPart.py & JlcOscillator.py & JlcPartsBase.py Swap FootprintAreaTable.area_of calls for new _row_area helper in sorting lambdas
edg/parts/Generic*.py Switch to internal _footprint_area for fuzzy-in footprint filtering
edg/electronics_model/KicadFootprintData.py New FootprintDataTable class providing area + bbox lookup
edg/electronics_model/resources/...build_kicad_footprint_table.py Expanded footprint extractor to compute both area and bbox, output JSON with full data
edg/electronics_model/SvgPcbBackend.py Integrates placement, generates full SVG-PCB code, and updates backend signatures
edg/abstract_parts/SelectorArea.py Removes local footprint-area table, uses FootprintDataTable, adds _row_area helper
Comments suppressed due to low confidence (2)

edg/electronics_model/SvgPcbBackend.py:17

  • TransformUtil is referenced here but not imported in this module, leading to a NameError at runtime. Add from .TransformUtil import TransformUtil at the top.
    elts: Dict[str, Tuple[Union['PlacedBlock', TransformUtil.Path], Tuple[float, float]]]  # name -> elt, (x, y)

edg/abstract_parts/SelectorArea.py:26

  • Ensure that the new _footprint_area and _row_area methods are correctly indented inside the SelectorArea class to match existing method definitions and avoid indentation errors.
  @classmethod


return cls._jlc_table().map_new_columns(parse_row).sort_by( # TODO dedup w/ JlcTableSelector._row_sort_by
lambda row: [row[cls.BASIC_PART_HEADER], FootprintAreaTable.area_of(row[cls.KICAD_FOOTPRINT]), row[cls.COST]]
lambda row: [row[cls.BASIC_PART_HEADER], cls._row_area(row), row[cls.COST]]
Copy link

Copilot AI Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This sorting lambda duplicates logic found in JlcTableSelector._row_sort_by. Extract the sort key into a shared helper to reduce duplication and ensure consistency.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +157
for subdir in os.listdir(kicad_dir):
if not subdir.endswith('.pretty') or not os.path.isdir(os.path.join(kicad_dir, subdir)):
continue
lib_name = subdir.split('.pretty')[0]
for file in os.listdir(os.path.join(kicad_dir, subdir)):
if not file.endswith('.kicad_mod'):
continue
fp_filename = file.split('.kicad_mod')[0]

with open(os.path.join(kicad_dir, subdir, file)) as f:
fp_data = f.read()
fp_area = calculate_area(fp_data)
fp_bbox = calculate_bbox(fp_data)
if fp_area is None and fp_bbox is not None:
fp_area = (fp_bbox[2] - fp_bbox[0]) * (fp_bbox[3] - fp_bbox[1])

fp_name = lib_name + ":" + fp_filename
if fp_area is not None and fp_bbox is not None:
fp_data_dict[fp_name] = FootprintData(
area=fp_area,
bbox=fp_bbox
)
print(f" {fp_name} -> {fp_area:.3g}, {fp_bbox}")
else:
print(f"skip {fp_name} {fp_area} {fp_bbox}")
Copy link

Copilot AI Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using os.walk or a recursive directory traversal to capture footprints in nested .pretty directories or subfolders, ensuring no valid footprints are missed.

Copilot uses AI. Check for mistakes.
@ducky64 ducky64 merged commit 9c510dc into master Jun 23, 2025
12 checks passed
@ducky64 ducky64 deleted the footprintpacking branch June 23, 2025 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants