DRY refactoring, restore APIs, and full magnetic assembly tests#12
DRY refactoring, restore APIs, and full magnetic assembly tests#12tinix84 wants to merge 11 commits intoOpenMagnetics:full-magneticfrom
Conversation
Implement dual-mode winding generation that uses exact MAS turnsDescription coordinates when available, falling back to calculated positions otherwise. Changes: - Add TurnDescription dataclass with from_dict() factory method - Add create_turn_from_description() for single turn from MAS coordinates - Add get_winding_from_mas() to build winding from turnsDescription array - Update get_winding() to check for MAS data first, then fall back - Update AssemblyBuilder to pass complete coil data with turnsDescription - Add comprehensive tests for MAS mode and fallback behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The full-magnetic branch uses a different API (get_magnetic instead of get_winding/get_bobbin/get_magnetic_assembly). Remove old test files as they are replaced by test_magnetic.py with comprehensive tests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract shared flatten_dimensions() and BuilderBase into utils.py, create shape_configs.py for shared dimension dicts, make C inherit from U, add default get_shape_extras in CadQuery IPiece, extract _rotate_piece_z_180, _create_rectangular_sketch, and _create_dimension_svg helpers in FreeCAD builder, and fix broken imports in magnetic_builder.py. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add get_piece_technical_drawing stub to CadQuery IPiece (method only existed in FreeCAD engine). Fix UR shape S-dimension boolean failure by extending the right keyway hole slightly past the winding column surface to avoid tangent-surface OCC kernel errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add get_magnetic_assembly, get_bobbin, get_winding to Builder facade - Add get_magnetic_assembly, get_bobbin, get_winding to CadQueryBuilder - Restore IBobbin/StandardBobbin and IWinding/RoundWireWinding inner classes (lost during full-magnetic merge in 1b925c7) - Add Ut shape class to CadQuery builder for UT core family - Implement get_piece_technical_drawing with CadQuery SVG export - Implement get_core_gapping_technical_drawing with CadQuery SVG export - Fix SVG strokeColor: convert hex string to RGB tuple for CadQuery exporter - Rename internal get_bobbin(BobbinProcessedDescription) to _build_bobbin_geometry - Restore test_assembly.py, test_bobbin.py, test_winding.py from pre-merge - Add ETD49 and PQ4040 MAS test data files - Add TestFullMagnetic tests for ETD49 (round wire) and PQ4040 (foil wire) All 34 tests pass (31 passed, 3 skipped). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR performs a substantial DRY refactoring to eliminate ~350 lines of duplicated code across FreeCAD and CadQuery engines, while also fixing test failures, restoring previously lost APIs, and adding comprehensive test coverage for magnetic assemblies.
Changes:
- Extracts shared code into
utils.py(flatten_dimensions, BuilderBase) andshape_configs.py(dimension/subtype configurations) - Restores critical APIs to Builder facade:
get_magnetic_assembly,get_bobbin,get_winding - Implements UT core shape family and SVG technical drawing generation with hex-to-RGB color conversion
- Adds comprehensive test suites for assembly (537 lines), bobbin (132 lines), and winding (216 lines) components
- Switches test_builder.py from FreeCAD to CadQuery engine and adds test_3 and test_4 for UR shape edge cases
- Adds full magnetic assembly tests for ETD49 (round wire) and PQ4040 (foil wire) with geometry validation
Reviewed changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| src/OpenMagneticsVirtualBuilder/utils.py | Adds shared flatten_dimensions function and BuilderBase class for factory/families logic |
| src/OpenMagneticsVirtualBuilder/shape_configs.py | New file with shared P, RM, UR, U shape dimension/subtype configurations |
| src/OpenMagneticsVirtualBuilder/freecad_builder.py | Inherits from BuilderBase, uses shared configs, extracts _create_dimension_svg and _create_rectangular_sketch helpers |
| src/OpenMagneticsVirtualBuilder/cadquery_builder.py | Implements get_magnetic_assembly, get_bobbin, get_winding, get_piece_technical_drawing, adds UT shape, fixes UR S-dimension boolean failure with epsilon offset |
| src/OpenMagneticsVirtualBuilder/builder.py | Restores API facade methods for magnetic_assembly, bobbin, and winding |
| src/OpenMagneticsVirtualBuilder/magnetic_builder.py | Removes unused imports (convert_axis_toroidal, build_magnetic_from_mas) |
| tests/test_assembly.py | New comprehensive assembly test suite (9 tests including MAS-based full assemblies) |
| tests/test_bobbin.py | New bobbin test suite (4 tests covering standard bobbins with/without pins) |
| tests/test_winding.py | New winding test suite (7 tests covering round wire, bulk, and MAS turnsDescription modes) |
| tests/test_magnetic.py | Adds TestFullMagnetic class with ETD49 and PQ4040 real-world assembly validation |
| tests/test_builder.py | Switches engine from FreeCAD to CadQuery, adds test_3 (UR type 2) and test_4 (UR 35/27.5/13 with S-dimension fix) |
| pyproject.toml | Bumps version from 0.6.11 to 0.6.12 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if len(gaps_per_column) > 1 and gap_index < len(gaps_per_column) - 1: | ||
| next_gap = gaps_per_column[gap_index + 1] | ||
| chunk_size = (-gap['length'] / 2 - gap['coordinates'][1]) - (next_gap['length'] / 2 - next_gap['coordinates'][1]) | ||
| dimension_label = f"{round(chunk_size * 1000, 2)}" | ||
| if chunk_size * 1000 * scale > 70: | ||
| dimension_label += " mm" | ||
| svgFile_data += create_dimension(ending_coordinates=[((gap['coordinates'][0] + gap['sectionDimensions'][0] / 2) * 1000 + center_offset) * scale, (-gap['length'] / 2 - gap['coordinates'][1]) * scale * 1000], | ||
| starting_coordinates=[((gap['coordinates'][0] + gap['sectionDimensions'][0] / 2) * 1000 + center_offset) * scale, (next_gap['length'] / 2 - next_gap['coordinates'][1]) * scale * 1000], | ||
| dimension_type="DistanceY", | ||
| dimension_label=dimension_label, | ||
| label_offset=min(base_width / 2, horizontal_offset), | ||
| label_alignment=(-gap['coordinates'][1] - gap['length'] / 2 - chunk_size / 2) * scale * 1000) |
There was a problem hiding this comment.
Duplicate code block detected: Lines 485-496 are identical to lines 498-509. This appears to be an accidental duplication where the same chunk size calculation and dimension creation for gaps_per_column is executed twice. The second occurrence (lines 498-509) should be removed.
tests/test_magnetic.py
Outdated
| def test_etd49_round_wire_5_turns(self): | ||
| """Test ETD49 core with 6 round wire turns and bobbin.""" | ||
| step_path, stl_path, solid_info = self._run_test( | ||
| 'ETD49_N87_10uH_5T.json', validate_geometry=True | ||
| ) | ||
|
|
||
| print(f"\n=== ETD49 Geometry ===") | ||
| print(f"Total solids: {len(solid_info)}") | ||
| for s in solid_info: | ||
| print(f" Solid {s['index']}: vol={s['volume']:.1f}") | ||
|
|
||
| # 2 core halves + 1 bobbin + 6 turns = 9 | ||
| assert len(solid_info) == 9, f"Expected 9 solids, got {len(solid_info)}" |
There was a problem hiding this comment.
Inconsistency between test name and expectations: The test function is named test_etd49_round_wire_5_turns and loads ETD49_N87_10uH_5T.json (both suggesting 5 turns), but the docstring says "6 round wire turns", the inline comment says "6 turns", and the assertion expects 9 solids (2 core halves + 1 bobbin + 6 turns). Either rename the test to test_etd49_round_wire_6_turns or update the docstring, comment, and assertion to reflect 5 turns and expect 8 solids total.
| wall_thickness = processed.get("wallThickness", dims.get("wallThickness", 0.0005)) | ||
| column_shape = processed.get("columnShape", "rectangular") | ||
| column_width = processed.get("columnWidth", 0) | ||
| column_depth = processed.get("columnDepth", column_width) |
There was a problem hiding this comment.
Variable column_depth is not used.
| ww_height = winding_window.get("height", 0) | ||
| column_shape = winding_window.get("columnShape", "rectangular") | ||
| column_width = winding_window.get("columnWidth", 0) | ||
| column_depth = winding_window.get("columnDepth", column_width) |
There was a problem hiding this comment.
Variable column_depth is not used.
| def get_bulk_winding(self, data, bobbin_dims): | ||
| wire_diameter = data.get("wireDiameter", 0.0005) | ||
| insulation = data.get("insulationThickness", 0.00005) | ||
| num_turns = data.get("numberOfTurns", 1) |
There was a problem hiding this comment.
Variable num_turns is not used.
| num_turns = data.get("numberOfTurns", 1) |
tests/test_winding.py
Outdated
| @@ -0,0 +1,216 @@ | |||
| import unittest | |||
| import os | |||
| import glob | |||
There was a problem hiding this comment.
Import of 'glob' is not used.
| import glob |
| import glob | ||
| import json | ||
|
|
||
| import context # noqa: F401 |
There was a problem hiding this comment.
Import of 'context' is not used.
| else: | ||
| return pieces | ||
|
|
||
| except: # noqa: E722 |
There was a problem hiding this comment.
Except block directly handles BaseException.
| except: # noqa: E722 | |
| except Exception: # noqa: E722 |
| data = json.loads(ndjson_line) | ||
| if data["name"] == "ETD 49/25/16": | ||
| return data | ||
| except FileNotFoundError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| data = json.loads(ndjson_line) | ||
| if data["name"] == "PQ 40/40": | ||
| return data | ||
| except FileNotFoundError: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
- Remove duplicate code block in freecad_builder.py gaps_per_column - Remove unused column_depth in StandardBobbin.get_bobbin_body - Remove unused num_turns in RoundWireWinding.get_bulk_winding - Remove unused dimensions assignment in freecad_builder toroidal branch - Remove dead 't'/'ut' family branches in test_builder.py (already excluded by filter) - Replace unnecessary core= assignments with direct calls in test_builder.py - Remove unused glob imports from test_bobbin.py and test_winding.py - Replace bare pass in except clauses with explicit return in test_assembly.py - Fix test name: test_etd49_round_wire_5_turns -> test_etd49_round_wire_6_turns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set up ruff linter and vulture dead code detector as pre-commit hooks. Fixed 267 auto-fixable issues and 40 manual fixes including: trailing whitespace in docstrings, assert False -> raise AssertionError, open() without context manager, unused variables, and capitalized env vars. Applied ruff format to all source and test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement multi-format 2D technical drawing generation using OCC hidden-line removal (HLRBRep_Algo) for correct visible-edge projection. DXF uses CadQuery's DxfDocument, FCMacro uses CadQuery edge API with degenerate edge filter to fix FreeCAD "Both points are equal" crash. Dimensions removed from all formats (kept as unused code for future re-enablement). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
flatten_dimensions,BuilderBase,shape_configs, defaultget_shape_extras, extracted SVG helper)get_piece_technical_drawing) and test_4 (UR S-dimension OCC boolean failure) issuesget_magnetic_assembly,get_bobbin,get_windingto Builder facade and CadQueryBuilder (lost during full-magnetic merge)test_assembly.py,test_bobbin.py,test_winding.pywith all original testsget_piece_technical_drawingandget_core_gapping_technical_drawingusing CadQuery SVG export (fix hex-to-RGB color conversion)Test plan
🤖 Generated with Claude Code