jf/opzomeren#177
Conversation
Build System: - Migrate from setup.py/setup.cfg to pyproject.toml - Remove MANIFEST.in, pytest.ini (config now in pyproject.toml) - Update Python requirement to >=3.9 - Replace flake8/autopep8/isort with ruff for linting/formatting - Add mypy for optional type checking Dependencies: - compas>=2.0 (was <2.0) - numpy>=1.24 (remove restrictive upper bound) - networkx>=3.0 - progressbar2>=4.0 - pyclipper>=1.3 - libigl>=2.5 COMPAS 2.x Migration: - Replace compas._os.absjoin() with pathlib.Path - Update pairwise import: compas.utilities -> compas.itertools - Replace mesh_weld(mesh) with mesh.weld() method - Modernize compas_slicer_ghpython install.py for COMPAS 2.x Python 3.9+ Modernization: - Remove all __future__ imports (14 files) - Remove explicit (object) inheritance from classes - Use pathlib.Path throughout - Use f-strings consistently CI/CD: - Test matrix: Python 3.9/3.10/3.11/3.12 - Test on ubuntu/windows/macos - Use ruff for linting, mypy for type checking - Update GitHub Actions to v4/v5 Tasks: - Add invoke tasks: lint, format, typecheck - Update release task to use python -m build
- geometry/path.py: full type annotations for Path class - geometry/layer.py: type hints for Layer, VerticalLayer, VerticalLayersManager - geometry/print_point.py: full type annotations for PrintPoint - slicers/base_slicer.py: type hints for BaseSlicer base class Uses Python 3.9+ style hints with `from __future__ import annotations` for forward references and modern union syntax (X | Y).
- convert % formatting to f-strings - fix SIM102/SIM103 (collapsible if) - fix B007 (unused loop vars) - fix B904 (raise from err) - update mesh_bounding_box -> bounding_box(vertices) - resolve circular imports via direct module imports
- mesh.edge_faces() now takes edge tuple instead of u/v kwargs - fix Path import in layer.py (needed at runtime for isinstance) - fix seams_align import in generate_brim.py
Major refactoring to use typed dataclasses instead of unstructured dicts. All dataclasses inherit from compas.data.Data for COMPAS serialization. New dataclasses: - GcodeParameters: typed G-code params with 25+ fields and defaults - PrintPath: wraps list[PrintPoint] with iteration/indexing - PrintLayer: wraps list[PrintPath] with iteration/indexing - PrintPointsCollection: replaces dict[str, dict[str, list[PrintPoint]]] Converted to dataclasses: - PrintPoint: @DataClass(Data) with __data__/__from_data__ - Path: @DataClass(Data) with legacy dict format compat - Layer/VerticalLayer: @DataClass(Data) with inheritance Key changes: - Integer indices instead of string keys ('layer_0' -> [0]) - Access: collection[0][1][2] vs dict['layer_0']['path_1'][2] - Type-safe with IDE autocomplete - printpoints_dict property for backward compat - Updated all print organizers and utilities
- new config.py: SlicerConfig, InterpolationConfig, GcodeConfig, PrintConfig dataclasses - enums: GeodesicsMethod, UnionMethod for type-safe config - full type hints: planar/interpolation/scalar_field/uv slicers - full type hints: planar/interpolation/scalar_field print organizers - fix direct imports in planar_slicer (create_planar_paths) - fix mypy issues (optional types, null checks)
- type hints: get_param, defaults_layers, defaults_gcode, defaults_interpolation_slicing - add min_layer_height, max_layer_height to layers defaults - add filament_diameter key (underscore) alongside legacy space key - deprecation note on GcodeParameters (prefer GcodeConfig)
- add _numpy_ops.py with vectorized helpers (KDTree, batch distances) - add test_performance.py with benchmarks + regression tests - vectorize gradient.py (vertex/face/edge gradient, divergence) - vectorize topological_sorting.py (point cloud neighbor check) - add KDTree to mesh_attributes_handling.py (restore_mesh_attributes) - pre-factor sparse matrix in geodesics.py heat diffusion (250x speedup) - vectorize data_smoothing.py iterative loop - vectorize sort_paths_minimum_travel_time.py (seam adjustment) - vectorize seams_align.py (distance calculations) - add numba, pytest-benchmark to deps Test fixtures use Sphere (Box ignores u,v params): - small: ~180 faces - medium: ~2k faces - large: ~8k faces Benchmark results: - vertex gradient (8k faces): ~860μs - divergence (2k faces): ~125μs - batch closest points (1k pts): ~650μs
convert list back to Vector in set_ppt_up_vec
curved slicing: 22s -> 13.5s (38% speedup)
curved slicing: 13.5s -> 12s
- generate_brim: CGAL offset_polygon w/ pyclipper fallback - offset_polygon_with_holes for complex slices (CGAL-only) - remesh_mesh via CGAL trimesh_remesh for mesh quality
- add get_cgal_HEAT_geodesic_distances using compas_cgal.HeatGeodesicSolver - add HEAT_CGAL enum to GeodesicsMethod - CGAL heat ~2.1s vs libigl ~3.1s in full workflow (30% faster)
- add pure NumPy cotmatrix/massmatrix/cotans in utils.py - geodesics.py uses CGAL HeatGeodesicSolver - gradient.py removes use_igl param, always vectorized - region_split.py adds pure Python mesh cut/components - update defaults to heat_cgal method - keep backwards-compatible aliases
- new post_processing/infill module - generate_medial_axis_infill() extracts skeleton edges as infill paths - uses interior_straight_skeleton from compas_cgal - add example 7
- 01 planar slicing: step-by-step walkthrough with brim/raft/seam control - 02 curved slicing: complete with math background and ETH references - 03 vertical sorting: explain branching geometry handling - 04 gcode: document GcodeConfig params and volumetric extrusion math - 05 scalar field: custom distance fields and isocurve extraction - 06 attributes: face vs vertex attributes with barycentric interpolation
- replace 27 assert statements with ValueError/TypeError/RuntimeError - convert old-style string concatenation to f-strings throughout - improves reliability with python -O flag
- remove dead 'default' heat diffusion method that had boundary drift bug - simplify diffuse_heat() to only use working backward Euler iteration - change default geodesics_method from heat_igl to heat_cgal - CGAL uses intrinsic Delaunay triangulation for better accuracy - add docstrings explaining custom vs CGAL implementations
- add mike to docs dependencies - update workflow to deploy with mike - only trigger on docs/ changes - master deploys to latest/stable - enable manual workflow dispatch
|
merging this PR shouldn't happen before the PRs in |
|
sorry for inviting many reviewers; consensus seems called for |
There was a problem hiding this comment.
Pull request overview
This is a comprehensive refactoring of the compas_slicer package that modernizes the codebase with significant improvements to code quality, performance, and maintainability. The PR introduces:
Key Changes
- Modernization: Added type hints throughout, removed Python 2 compatibility code (
from __future__imports), updated to usepathlib.Pathinstead ofos.path - Performance: Introduced vectorized NumPy operations (~10x speedup), added CGAL integration for faster planar slicing, created comprehensive performance benchmarks
- Documentation: Migrated from Sphinx to MkDocs
- Tooling: Updated from flake8 to ruff for linting/formatting, added mypy type checking, modernized build system
- Architecture: Refactored data structures (introduced
PrintPointsCollection), improved gradient computation with pure Python implementations as fallbacks - Testing: Added performance regression tests, example integration tests
Reviewed changes
Copilot reviewed 176 out of 178 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_planar_slicing.py | Updated imports, modernized path handling, removed slicer_type parameter |
| tests/test_planar_print_organization_horizontal_layers.py | Updated to use Path, removed commented code |
| tests/test_performance.py | New comprehensive performance benchmarks with regression guards |
| tests/test_examples.py | New integration tests for examples |
| temp/PLACEHOLDER | Removed temporary file placeholder |
| tasks.py | Modernized invoke tasks, added ruff/mypy/format commands, integrated yak workflow |
| src/compas_slicer_ghpython/yak_template/* | Added Grasshopper package management files |
| src/compas_slicer_ghpython/visualization.py | Updated to use Path, modernized string formatting |
| src/compas_slicer_ghpython/install.py | Added rhino dependency checks, improved error handling |
| src/compas_slicer/visualization/* | New visualization module using compas_viewer |
| src/compas_slicer/utilities/* | Extensive refactoring with type hints, vectorized operations, added pure Python mesh utilities |
| src/compas_slicer/slicers/* | Removed legacy planar slicing, standardized to CGAL, added type hints and config objects |
| src/compas_slicer/print_organization/* | Major refactoring of printpoints data structure, improved G-code generation |
| src/compas_slicer/pre_processing/* | Vectorized computations, added fallback implementations |
| src/compas_slicer/post_processing/* | Minor type hint additions |
| pytest.ini | Removed (configuration likely moved to pyproject.toml) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| paths_data = data["paths"] | ||
| # Handle both list format and legacy dict format | ||
| if isinstance(paths_data, dict): | ||
| paths = [Path.from_data(paths_data[key]) for key in sorted(paths_data.keys(), key=lambda x: int(x))] |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| paths_data = data["paths"] | ||
| # Handle both list format and legacy dict format | ||
| if isinstance(paths_data, dict): | ||
| paths = [Path.from_data(paths_data[key]) for key in sorted(paths_data.keys(), key=lambda x: int(x))] |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
| if isinstance(points_data, dict): | ||
| pts = [ | ||
| Point.__from_data__(points_data[key]) | ||
| for key in sorted(points_data.keys(), key=lambda x: int(x)) |
There was a problem hiding this comment.
This 'lambda' is just a simple wrapper around a callable object. Use that object directly.
|
|
||
| logger = logging.getLogger('logger') | ||
| logging.basicConfig(format='%(levelname)s-%(message)s', level=logging.INFO) | ||
| import compas_slicer.utilities as utils |
There was a problem hiding this comment.
Module 'compas_slicer.utilities' is imported with both 'import' and 'import from'.
| from compas_slicer.geometry import Layer | ||
| import logging | ||
| logger = logging.getLogger('logger') | ||
| from loguru import logger |
There was a problem hiding this comment.
Import of 'logger' is not used.
| import copy | ||
| import networkx as nx | ||
| import logging | ||
| from loguru import logger |
There was a problem hiding this comment.
Import of 'logger' is not used.
|
|
||
| self.G = nx.DiGraph() | ||
| self.G: nx.DiGraph = nx.DiGraph() | ||
| self.create_graph_nodes() |
There was a problem hiding this comment.
This call to DirectedGraph.create_graph_nodes in an initialization method is overridden by MeshDirectedGraph.create_graph_nodes.
This call to DirectedGraph.create_graph_nodes in an initialization method is overridden by SegmentsDirectedGraph.create_graph_nodes.
| self.G = nx.DiGraph() | ||
| self.G: nx.DiGraph = nx.DiGraph() | ||
| self.create_graph_nodes() | ||
| self.root_indices = self.find_roots() |
There was a problem hiding this comment.
This call to DirectedGraph.find_roots in an initialization method is overridden by MeshDirectedGraph.find_roots.
This call to DirectedGraph.find_roots in an initialization method is overridden by SegmentsDirectedGraph.find_roots.
| logger.info(f'Graph roots: {self.root_indices}') | ||
| if len(self.root_indices) == 0: | ||
| raise ValueError("No root nodes were found. At least one root node is needed.") | ||
| self.end_indices = self.find_ends() |
There was a problem hiding this comment.
This call to DirectedGraph.find_ends in an initialization method is overridden by MeshDirectedGraph.find_ends.
This call to DirectedGraph.find_ends in an initialization method is overridden by SegmentsDirectedGraph.find_ends.
|
This is amazing!! Thanks a lot @jf--- !!!! |
|
@jf--- really cool indeed. maybe the review is easier if you split off at least the docs part to a different PR... :) |
|
@gonzalocasas & @tomvanmele what about the following:
|
|
sounds good to me |
|
@gonzalocasas would you do the honors pls? if you could double check this does justice to the original authors, I think that's the pressing thing. |
@ioannaMitropoulou @joburger & @stratocaster dear
compas_slicerauthorsThe last week I’ve been working on compas_slicer.
I’ve refactored the code significantly:
via this PR I'd love to join you as co-maintainer