sa2
This commit is contained in:
parent
7e43f42c1c
commit
46582f82d6
6 changed files with 177 additions and 50 deletions
48
AGENTS.md
Normal file
48
AGENTS.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# NodeSlicer Agent Instructions
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
NodeSlicer is a high-performance Blender extension providing a custom node-based interface for mesh slicing and G-code generation.
|
||||||
|
|
||||||
|
## Critical Context
|
||||||
|
- **Purpose**: 3D printing slicing, CNC toolpathing, and procedural geometry tasks
|
||||||
|
- **Main Technologies**: Python, Blender API (`bpy`), NumPy, Pyclipper, Shapely
|
||||||
|
- **Blender Version**: 4.2 or newer (required for the Extension platform)
|
||||||
|
- **Architecture**: Separates Blender-specific UI and data capture (`infra/`) from core geometric and slicing algorithms (`core/`)
|
||||||
|
|
||||||
|
## Development Conventions
|
||||||
|
|
||||||
|
### Registration
|
||||||
|
All Blender classes (Nodes, Sockets, Operators, Menus) must be decorated with `@register_class` from `registry.py` to ensure they are registered correctly during initialization.
|
||||||
|
|
||||||
|
### Node Evaluation
|
||||||
|
Nodes follow a pull-based evaluation model. Use `resolve_input(self.inputs.get("SocketName"))` within a node's `evaluate_output` method to trigger the evaluation of upstream nodes.
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
For any geometry processing, prioritize NumPy vectorized operations over standard Python loops to maintain high performance.
|
||||||
|
|
||||||
|
### Scaling
|
||||||
|
2D polygon operations via Pyclipper use a `SCALE` factor (defined in `core/slice.py`) to convert floats to integers, as Pyclipper operates in integer space.
|
||||||
|
|
||||||
|
### Type Hinting
|
||||||
|
Use exhaustive type hints for all new functions and classes.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
Update `doc/NODES.md` when adding new nodes and `doc/MODULES.md` when adding new modules.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- `__init__.py`: Main entry point. Defines the `NodeSlicer Tree`, custom Sockets, and all Nodes.
|
||||||
|
- `registry.py`: Utility for automatic registration of Blender classes using the `@register_class` decorator.
|
||||||
|
- `core/slice.py`: Core slicing engine (plane-mesh intersection), buffering (offsetting), and infill generation.
|
||||||
|
- `core/geometry.py`: Shared geometry utility functions.
|
||||||
|
- `infra/blender_mesh_capture.py`: Logic for extracting mesh data from Blender objects into NumPy arrays.
|
||||||
|
- `infra/blender_slice_nodes_generic.py`: Base classes for the node system and the evaluation logic (`resolve_input`).
|
||||||
|
- `doc/`: Comprehensive documentation on modules and nodes.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- Build the extension: `./build.sh` (generates a zip file in the `release/` directory)
|
||||||
|
- Install dependencies: `uv sync`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Tests are located in the `tests/` directory
|
||||||
|
- Run PDF exporter tests: `python -m unittest tests.test_pdf_exporter -v`
|
||||||
|
- Requires the virtual environment: `source .venv/bin/activate`
|
||||||
|
|
@ -363,12 +363,17 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
draw_crease: NfBoolProperty(name="Crease Angle", default=False) # pyright: ignore[reportInvalidTypeForm]
|
draw_crease: NfBoolProperty(name="Crease Angle", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
crease_angle: NfFloatProperty(name="Angle (°)", default=30.0, min=0.0, max=180.0) # pyright: ignore[reportInvalidTypeForm]
|
crease_angle: NfFloatProperty(name="Angle (°)", default=30.0, min=0.0, max=180.0) # pyright: ignore[reportInvalidTypeForm]
|
||||||
draw_bbox: NfBoolProperty(name="Bounding Box", default=False) # pyright: ignore[reportInvalidTypeForm]
|
draw_bbox: NfBoolProperty(name="Bounding Box", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
draw_sa2_measures: NfBoolProperty(name="SA2 measures", default=False) # pyright: ignore[reportInvalidTypeForm]
|
draw_sa2_holes: NfBoolProperty(name="Holes", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_sa2_arcs: NfBoolProperty(name="Arcs", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_sa2_chamfers: NfBoolProperty(name="Chamfers", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_sa2_step_features: NfBoolProperty(name="Step Features", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
show_lineart: NfBoolProperty(name="Lineart Visibility", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
show_sa2: NfBoolProperty(name="SA2 Settings", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||||
show_name: NfBoolProperty(name="Show Name", default=False) # pyright: ignore[reportInvalidTypeForm]
|
show_name: NfBoolProperty(name="Show Name", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
show_bbox_size: NfBoolProperty(name="Bounding Box Sizes", default=False) # pyright: ignore[reportInvalidTypeForm]
|
show_bbox_size: NfBoolProperty(name="Bounding Box Sizes", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
line_width: NfFloatProperty(name="Line Width (mm)", default=0.5, min=0.01, max=50.0) # pyright: ignore[reportInvalidTypeForm]
|
line_width: NfFloatProperty(name="Line Width (mm)", default=0.0, min=0.0, max=50.0) # pyright: ignore[reportInvalidTypeForm]
|
||||||
font_size: NfFloatProperty(name="Font Size (pt)", default=8.0, min=1.0, max=100.0) # pyright: ignore[reportInvalidTypeForm]
|
font_size: NfFloatProperty(name="Font Size (pt)", default=0.0, min=0.0, max=100.0) # pyright: ignore[reportInvalidTypeForm]
|
||||||
margin: NfFloatProperty(name="Margin (mm)", default=10.0, min=0.0, max=500.0) # pyright: ignore[reportInvalidTypeForm]
|
margin: NfFloatProperty(name="Margin (mm)", default=10.0, min=0.0, max=500.0) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
bg_color: NfFloatVectorProperty(
|
bg_color: NfFloatVectorProperty(
|
||||||
|
|
@ -406,18 +411,35 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
def draw_buttons(self, context, layout):
|
||||||
super().draw_buttons(context, layout)
|
super().draw_buttons(context, layout)
|
||||||
layout.prop(self, "draw_wireframe")
|
|
||||||
layout.prop(self, "draw_boundary")
|
# Lineart Visibility subpanel (collapsible)
|
||||||
layout.prop(self, "draw_contour")
|
layout.prop(self, "show_lineart", text="Lineart Visibility", icon='TRIA_DOWN' if self.show_lineart else 'TRIA_RIGHT')
|
||||||
layout.prop(self, "draw_crease")
|
if self.show_lineart:
|
||||||
if self.draw_crease:
|
box = layout.box()
|
||||||
layout.prop(self, "crease_angle")
|
box.prop(self, "draw_wireframe")
|
||||||
|
box.prop(self, "draw_boundary")
|
||||||
|
box.prop(self, "draw_contour")
|
||||||
|
box.prop(self, "draw_crease")
|
||||||
|
if self.draw_crease:
|
||||||
|
box.prop(self, "crease_angle")
|
||||||
|
|
||||||
|
# SA2 Settings subpanel (collapsible)
|
||||||
|
layout.prop(self, "show_sa2", text="SA2 Settings", icon='TRIA_DOWN' if self.show_sa2 else 'TRIA_RIGHT')
|
||||||
|
if self.show_sa2:
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, "draw_sa2_holes")
|
||||||
|
box.prop(self, "draw_sa2_arcs")
|
||||||
|
box.prop(self, "draw_sa2_chamfers")
|
||||||
|
box.prop(self, "draw_sa2_step_features")
|
||||||
|
|
||||||
|
# Display settings
|
||||||
layout.prop(self, "draw_bbox")
|
layout.prop(self, "draw_bbox")
|
||||||
if self.draw_bbox:
|
if self.draw_bbox:
|
||||||
layout.prop(self, "bbox_color")
|
layout.prop(self, "bbox_color")
|
||||||
layout.prop(self, "draw_sa2_measures")
|
|
||||||
layout.prop(self, "show_name")
|
layout.prop(self, "show_name")
|
||||||
layout.prop(self, "show_bbox_size")
|
layout.prop(self, "show_bbox_size")
|
||||||
|
|
||||||
|
# Styling settings
|
||||||
layout.prop(self, "line_width")
|
layout.prop(self, "line_width")
|
||||||
layout.prop(self, "font_size")
|
layout.prop(self, "font_size")
|
||||||
layout.prop(self, "margin")
|
layout.prop(self, "margin")
|
||||||
|
|
@ -477,7 +499,10 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
draw_contour=self.draw_contour,
|
draw_contour=self.draw_contour,
|
||||||
draw_crease=self.draw_crease,
|
draw_crease=self.draw_crease,
|
||||||
draw_bbox=self.draw_bbox,
|
draw_bbox=self.draw_bbox,
|
||||||
draw_sa2_measures=self.draw_sa2_measures,
|
draw_sa2_holes=self.draw_sa2_holes,
|
||||||
|
draw_sa2_arcs=self.draw_sa2_arcs,
|
||||||
|
draw_sa2_chamfers=self.draw_sa2_chamfers,
|
||||||
|
draw_sa2_step_features=self.draw_sa2_step_features,
|
||||||
show_name=self.show_name,
|
show_name=self.show_name,
|
||||||
show_bbox_size=self.show_bbox_size,
|
show_bbox_size=self.show_bbox_size,
|
||||||
crease_angle=self.crease_angle,
|
crease_angle=self.crease_angle,
|
||||||
|
|
|
||||||
|
|
@ -270,24 +270,39 @@ def extract_slicing_object_features(so_item: Any) -> tuple[
|
||||||
def render_sa2_measures_pdf(
|
def render_sa2_measures_pdf(
|
||||||
stream_cmds: list[str],
|
stream_cmds: list[str],
|
||||||
raw_geometry: Any,
|
raw_geometry: Any,
|
||||||
draw_sa2_measures: bool,
|
draw_sa2_holes: bool = False,
|
||||||
line_width_pt: float,
|
draw_sa2_arcs: bool = False,
|
||||||
main_step: float,
|
draw_sa2_chamfers: bool = False,
|
||||||
to_pdf_pt: Callable[[NDArray[np.float64]], NDArray[np.float64]],
|
draw_sa2_step_features: bool = False,
|
||||||
font_size_pt: float | None = None
|
minor_step_mm: float = 1.0,
|
||||||
|
main_step: float = 10.0,
|
||||||
|
to_pdf_pt: Callable[[NDArray[np.float64]], NDArray[np.float64]] | None = None,
|
||||||
|
font_size_pt: float | None = None,
|
||||||
|
line_width_pt: float | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Renders ISO 129-1 compliant SA2 automated technical measurements on PDF stream.
|
Renders ISO 129-1 compliant SA2 automated technical measurements on PDF stream.
|
||||||
"""
|
"""
|
||||||
if not draw_sa2_measures or raw_geometry is None:
|
if raw_geometry is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if any SA2 feature is enabled
|
||||||
|
if not (draw_sa2_holes or draw_sa2_arcs or draw_sa2_chamfers or draw_sa2_step_features):
|
||||||
return
|
return
|
||||||
|
|
||||||
sa2_objects = extract_slicing_objects_bounds(raw_geometry)
|
sa2_objects = extract_slicing_objects_bounds(raw_geometry)
|
||||||
if not sa2_objects:
|
if not sa2_objects:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Automatic sizing based on minor grid
|
||||||
|
MM_TO_PT = 2.834645669291339
|
||||||
|
if line_width_pt is None or line_width_pt <= 0:
|
||||||
|
line_width_pt = minor_step_mm * 0.05 * MM_TO_PT
|
||||||
|
if font_size_pt is None or font_size_pt <= 0:
|
||||||
|
font_size_pt = minor_step_mm * MM_TO_PT
|
||||||
|
|
||||||
stream_cmds.append("/GS100 gs")
|
stream_cmds.append("/GS100 gs")
|
||||||
sa2_lw_pt = max(0.4 * MM_TO_PT, line_width_pt * 0.8)
|
sa2_lw_pt = line_width_pt * 0.8
|
||||||
stream_cmds.append(f"{sa2_lw_pt:.2f} w")
|
stream_cmds.append(f"{sa2_lw_pt:.2f} w")
|
||||||
stream_cmds.append("1 j 1 J")
|
stream_cmds.append("1 j 1 J")
|
||||||
|
|
||||||
|
|
@ -295,9 +310,8 @@ def render_sa2_measures_pdf(
|
||||||
red_r, red_g, red_b = 0.85, 0.15, 0.15
|
red_r, red_g, red_b = 0.85, 0.15, 0.15
|
||||||
stream_cmds.append(f"{red_r:.3f} {red_g:.3f} {red_b:.3f} RG")
|
stream_cmds.append(f"{red_r:.3f} {red_g:.3f} {red_b:.3f} RG")
|
||||||
|
|
||||||
if font_size_pt is None or font_size_pt <= 0:
|
# SA2 arrow length: scale with line width (which is based on minor grid)
|
||||||
font_size_pt = max(8.0, line_width_pt * 4.0)
|
arrow_len_pt = line_width_pt * 4.0
|
||||||
arrow_len_pt = max(3.5 * MM_TO_PT, line_width_pt * 4.0)
|
|
||||||
arrow_half_w_pt = arrow_len_pt * 0.40
|
arrow_half_w_pt = arrow_len_pt * 0.40
|
||||||
|
|
||||||
for so_item, obj_name, min_x, max_x, min_y, max_y in sa2_objects:
|
for so_item, obj_name, min_x, max_x, min_y, max_y in sa2_objects:
|
||||||
|
|
@ -388,7 +402,7 @@ def render_sa2_measures_pdf(
|
||||||
holes, arcs, chamfers, step_features = extract_slicing_object_features(so_item)
|
holes, arcs, chamfers, step_features = extract_slicing_object_features(so_item)
|
||||||
|
|
||||||
# 1. ISO 129-1 Circular Bores / Hole Diameter Callouts (Ø - Electric Blue)
|
# 1. ISO 129-1 Circular Bores / Hole Diameter Callouts (Ø - Electric Blue)
|
||||||
if holes:
|
if draw_sa2_holes and holes:
|
||||||
blue_r, blue_g, blue_b = 0.10, 0.45, 0.90
|
blue_r, blue_g, blue_b = 0.10, 0.45, 0.90
|
||||||
stream_cmds.append(f"{blue_r:.3f} {blue_g:.3f} {blue_b:.3f} RG")
|
stream_cmds.append(f"{blue_r:.3f} {blue_g:.3f} {blue_b:.3f} RG")
|
||||||
stream_cmds.append(f"{blue_r:.3f} {blue_g:.3f} {blue_b:.3f} rg")
|
stream_cmds.append(f"{blue_r:.3f} {blue_g:.3f} {blue_b:.3f} rg")
|
||||||
|
|
@ -425,7 +439,7 @@ def render_sa2_measures_pdf(
|
||||||
stream_cmds.append("ET")
|
stream_cmds.append("ET")
|
||||||
|
|
||||||
# 2. ISO 129-1 Arc Cutouts & Corner Fillets (R - Amber Orange)
|
# 2. ISO 129-1 Arc Cutouts & Corner Fillets (R - Amber Orange)
|
||||||
if arcs:
|
if draw_sa2_arcs and arcs:
|
||||||
amber_r, amber_g, amber_b = 0.90, 0.45, 0.10
|
amber_r, amber_g, amber_b = 0.90, 0.45, 0.10
|
||||||
stream_cmds.append(f"{amber_r:.3f} {amber_g:.3f} {amber_b:.3f} RG")
|
stream_cmds.append(f"{amber_r:.3f} {amber_g:.3f} {amber_b:.3f} RG")
|
||||||
stream_cmds.append(f"{amber_r:.3f} {amber_g:.3f} {amber_b:.3f} rg")
|
stream_cmds.append(f"{amber_r:.3f} {amber_g:.3f} {amber_b:.3f} rg")
|
||||||
|
|
@ -462,7 +476,7 @@ def render_sa2_measures_pdf(
|
||||||
stream_cmds.append("ET")
|
stream_cmds.append("ET")
|
||||||
|
|
||||||
# 3. ISO 129-1 Bevels & Chamfers (C / 45° - Violet Purple)
|
# 3. ISO 129-1 Bevels & Chamfers (C / 45° - Violet Purple)
|
||||||
if chamfers:
|
if draw_sa2_chamfers and chamfers:
|
||||||
purple_r, purple_g, purple_b = 0.60, 0.20, 0.85
|
purple_r, purple_g, purple_b = 0.60, 0.20, 0.85
|
||||||
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} RG")
|
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} RG")
|
||||||
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} rg")
|
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} rg")
|
||||||
|
|
@ -498,7 +512,7 @@ def render_sa2_measures_pdf(
|
||||||
stream_cmds.append("ET")
|
stream_cmds.append("ET")
|
||||||
|
|
||||||
# 4. Hole Center Pitch Distances (Teal Green)
|
# 4. Hole Center Pitch Distances (Teal Green)
|
||||||
if len(holes) >= 2:
|
if draw_sa2_holes and len(holes) >= 2:
|
||||||
teal_r, teal_g, teal_b = 0.05, 0.58, 0.53
|
teal_r, teal_g, teal_b = 0.05, 0.58, 0.53
|
||||||
stream_cmds.append(f"{teal_r:.3f} {teal_g:.3f} {teal_b:.3f} RG")
|
stream_cmds.append(f"{teal_r:.3f} {teal_g:.3f} {teal_b:.3f} RG")
|
||||||
stream_cmds.append(f"{teal_r:.3f} {teal_g:.3f} {teal_b:.3f} rg")
|
stream_cmds.append(f"{teal_r:.3f} {teal_g:.3f} {teal_b:.3f} rg")
|
||||||
|
|
@ -523,7 +537,7 @@ def render_sa2_measures_pdf(
|
||||||
stream_cmds.append("ET")
|
stream_cmds.append("ET")
|
||||||
|
|
||||||
# 5. Interior Step / Notch Feature Segment Lengths (Deep Purple)
|
# 5. Interior Step / Notch Feature Segment Lengths (Deep Purple)
|
||||||
if step_features:
|
if draw_sa2_step_features and step_features:
|
||||||
purple_r, purple_g, purple_b = 0.48, 0.23, 0.92
|
purple_r, purple_g, purple_b = 0.48, 0.23, 0.92
|
||||||
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} RG")
|
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} RG")
|
||||||
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} rg")
|
stream_cmds.append(f"{purple_r:.3f} {purple_g:.3f} {purple_b:.3f} rg")
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,10 @@ def export_mesh_to_pdf(
|
||||||
draw_contour: bool = False,
|
draw_contour: bool = False,
|
||||||
draw_crease: bool = False,
|
draw_crease: bool = False,
|
||||||
draw_bbox: bool = False,
|
draw_bbox: bool = False,
|
||||||
draw_sa2_measures: bool = False,
|
draw_sa2_holes: bool = False,
|
||||||
|
draw_sa2_arcs: bool = False,
|
||||||
|
draw_sa2_chamfers: bool = False,
|
||||||
|
draw_sa2_step_features: bool = False,
|
||||||
show_name: bool = False,
|
show_name: bool = False,
|
||||||
show_bbox_size: bool = False,
|
show_bbox_size: bool = False,
|
||||||
crease_angle: float = 30.0,
|
crease_angle: float = 30.0,
|
||||||
|
|
@ -158,26 +161,30 @@ def export_mesh_to_pdf(
|
||||||
pad_x = minor_step
|
pad_x = minor_step
|
||||||
pad_y = minor_step
|
pad_y = minor_step
|
||||||
|
|
||||||
half_span_x = math.ceil(max(abs((max_x + pad_x) - center_x), abs((min_x - pad_x) - center_x)) / main_step) * main_step
|
# Calculate required grid span with padding
|
||||||
half_span_y = math.ceil(max(abs((max_y + pad_y) - center_y), abs((min_y - pad_y) - center_y)) / main_step) * main_step
|
span_x = max(abs(max_x - center_x), abs(min_x - center_x)) + pad_x
|
||||||
|
span_y = max(abs(max_y - center_y), abs(min_y - center_y)) + pad_y
|
||||||
|
|
||||||
|
# Round up to nearest main_step
|
||||||
|
half_span_x = math.ceil(span_x / main_step) * main_step
|
||||||
|
half_span_y = math.ceil(span_y / main_step) * main_step
|
||||||
|
|
||||||
if half_span_x == 0:
|
if half_span_x == 0:
|
||||||
half_span_x = main_step
|
half_span_x = main_step
|
||||||
if half_span_y == 0:
|
if half_span_y == 0:
|
||||||
half_span_y = main_step
|
half_span_y = main_step
|
||||||
|
|
||||||
grid_min_x = math.floor((center_x - half_span_x) / main_step) * main_step
|
# Ensure grid is symmetric around center
|
||||||
grid_max_x = math.ceil((center_x + half_span_x) / main_step) * main_step
|
grid_min_x = center_x - half_span_x
|
||||||
grid_min_y = math.floor((center_y - half_span_y) / main_step) * main_step
|
grid_max_x = center_x + half_span_x
|
||||||
grid_max_y = math.ceil((center_y + half_span_y) / main_step) * main_step
|
grid_min_y = center_y - half_span_y
|
||||||
|
grid_max_y = center_y + half_span_y
|
||||||
|
|
||||||
if min_x - grid_min_x < minor_step:
|
# Ensure grid aligns with main_step grid lines
|
||||||
grid_min_x -= main_step
|
grid_min_x = math.floor(grid_min_x / main_step) * main_step
|
||||||
if grid_max_x - max_x < minor_step:
|
grid_max_x = math.ceil(grid_max_x / main_step) * main_step
|
||||||
grid_max_x += main_step
|
grid_min_y = math.floor(grid_min_y / main_step) * main_step
|
||||||
if min_y - grid_min_y < minor_step:
|
grid_max_y = math.ceil(grid_max_y / main_step) * main_step
|
||||||
grid_min_y -= main_step
|
|
||||||
if grid_max_y - max_y < minor_step:
|
|
||||||
grid_max_y += main_step
|
|
||||||
|
|
||||||
if grid_min_x == grid_max_x:
|
if grid_min_x == grid_max_x:
|
||||||
grid_max_x += main_step
|
grid_max_x += main_step
|
||||||
|
|
@ -198,7 +205,15 @@ def export_mesh_to_pdf(
|
||||||
page_w_pt = w_total_mm * MM_TO_PT
|
page_w_pt = w_total_mm * MM_TO_PT
|
||||||
page_h_pt = h_total_mm * MM_TO_PT
|
page_h_pt = h_total_mm * MM_TO_PT
|
||||||
|
|
||||||
line_width_pt = line_width_mm * MM_TO_PT
|
# Automatic sizing based on minor grid
|
||||||
|
if line_width_mm <= 0:
|
||||||
|
line_width_pt = minor_step * 0.05 * MM_TO_PT
|
||||||
|
else:
|
||||||
|
line_width_pt = line_width_mm * MM_TO_PT
|
||||||
|
|
||||||
|
# Automatic font size: equal to minor grid spacing
|
||||||
|
if font_size_pt is None or font_size_pt <= 0:
|
||||||
|
font_size_pt = minor_step * MM_TO_PT
|
||||||
|
|
||||||
def to_pdf_pt(pt_mm: NDArray[np.float64]) -> NDArray[np.float64]:
|
def to_pdf_pt(pt_mm: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||||
x_pt = (pt_mm[:, 0] - grid_min_x + total_margin_mm) * MM_TO_PT
|
x_pt = (pt_mm[:, 0] - grid_min_x + total_margin_mm) * MM_TO_PT
|
||||||
|
|
@ -316,8 +331,20 @@ def export_mesh_to_pdf(
|
||||||
stream_cmds.append("S")
|
stream_cmds.append("S")
|
||||||
|
|
||||||
# 5.6. SA2 Automated Technical Measures Block
|
# 5.6. SA2 Automated Technical Measures Block
|
||||||
if draw_sa2_measures and raw_geometry is not None:
|
if raw_geometry is not None and (draw_sa2_holes or draw_sa2_arcs or draw_sa2_chamfers or draw_sa2_step_features):
|
||||||
render_sa2_measures_pdf(stream_cmds, raw_geometry, draw_sa2_measures, line_width_pt, main_step, to_pdf_pt, font_size_pt=font_size_pt)
|
render_sa2_measures_pdf(
|
||||||
|
stream_cmds,
|
||||||
|
raw_geometry,
|
||||||
|
draw_sa2_holes=draw_sa2_holes,
|
||||||
|
draw_sa2_arcs=draw_sa2_arcs,
|
||||||
|
draw_sa2_chamfers=draw_sa2_chamfers,
|
||||||
|
draw_sa2_step_features=draw_sa2_step_features,
|
||||||
|
minor_step_mm=minor_step,
|
||||||
|
main_step=main_step,
|
||||||
|
to_pdf_pt=to_pdf_pt,
|
||||||
|
font_size_pt=font_size_pt,
|
||||||
|
line_width_pt=line_width_pt
|
||||||
|
)
|
||||||
|
|
||||||
# 6. Size Scale Legend (Bottom Left, aligned directly on bottom-left main grid lines with arrows and X/Y labels)
|
# 6. Size Scale Legend (Bottom Left, aligned directly on bottom-left main grid lines with arrows and X/Y labels)
|
||||||
stream_cmds.append("/GS100 gs")
|
stream_cmds.append("/GS100 gs")
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,12 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **BBox Color**: Color picker for the bounding box rectangle lines (default blue).
|
- **BBox Color**: Color picker for the bounding box rectangle lines (default blue).
|
||||||
- **Show Name**: Checkbox to render the object name to the right of the rightmost vertex of each `SlicingObject`.
|
- **Show Name**: Checkbox to render the object name to the right of the rightmost vertex of each `SlicingObject`.
|
||||||
- **Bounding Box Sizes**: Checkbox to render 2D bounding box size dimensions (e.g. `X: 120mm Y: 230mm` or `X: 1.2m Y: 2.3m`) next to each object label.
|
- **Bounding Box Sizes**: Checkbox to render 2D bounding box size dimensions (e.g. `X: 120mm Y: 230mm` or `X: 1.2m Y: 2.3m`) next to each object label.
|
||||||
- **Line Width (mm)**: Stroke width in millimeters for the exported lines.
|
- **Holes**: Checkbox to render circular bore/hole diameter callouts (Ø) with ISO 129-1 compliant markings.
|
||||||
- **Font Size (pt)**: Font size in PostScript points for all text labels and measurement callouts.
|
- **Arcs**: Checkbox to render arc cutouts and corner fillet radius callouts (R) with ISO 129-1 compliant markings.
|
||||||
|
- **Chamfers**: Checkbox to render bevel/chamfer callouts (C) with ISO 129-1 compliant markings.
|
||||||
|
- **Step Features**: Checkbox to render interior step/notch feature segment length callouts with ISO 129-1 compliant markings.
|
||||||
|
- **Line Width (mm)**: Stroke width in millimeters for the exported lines. Set to 0 for automatic sizing (5% of minor grid spacing).
|
||||||
|
- **Font Size (pt)**: Font size in PostScript points for all text labels and measurement callouts. Set to 0 for automatic sizing (equal to minor grid spacing).
|
||||||
- **Margin (mm)**: Margin around the geometry grid in mm.
|
- **Margin (mm)**: Margin around the geometry grid in mm.
|
||||||
- **Background**: Color picker for the canvas background.
|
- **Background**: Color picker for the canvas background.
|
||||||
- **Wireframe**: Color picker for the wireframe lines and grid.
|
- **Wireframe**: Color picker for the wireframe lines and grid.
|
||||||
|
|
|
||||||
|
|
@ -545,7 +545,10 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
export_mesh_to_pdf(
|
export_mesh_to_pdf(
|
||||||
edges_2d,
|
edges_2d,
|
||||||
pdf_path,
|
pdf_path,
|
||||||
draw_sa2_measures=True,
|
draw_sa2_holes=True,
|
||||||
|
draw_sa2_arcs=True,
|
||||||
|
draw_sa2_chamfers=True,
|
||||||
|
draw_sa2_step_features=True,
|
||||||
raw_geometry=so
|
raw_geometry=so
|
||||||
)
|
)
|
||||||
self.assertTrue(os.path.exists(pdf_path))
|
self.assertTrue(os.path.exists(pdf_path))
|
||||||
|
|
@ -590,7 +593,10 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
export_mesh_to_pdf(
|
export_mesh_to_pdf(
|
||||||
edges_2d,
|
edges_2d,
|
||||||
pdf_path,
|
pdf_path,
|
||||||
draw_sa2_measures=True,
|
draw_sa2_holes=True,
|
||||||
|
draw_sa2_arcs=True,
|
||||||
|
draw_sa2_chamfers=True,
|
||||||
|
draw_sa2_step_features=True,
|
||||||
raw_geometry=so
|
raw_geometry=so
|
||||||
)
|
)
|
||||||
self.assertTrue(os.path.exists(pdf_path))
|
self.assertTrue(os.path.exists(pdf_path))
|
||||||
|
|
@ -640,7 +646,10 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
export_mesh_to_pdf(
|
export_mesh_to_pdf(
|
||||||
edges_2d,
|
edges_2d,
|
||||||
pdf_path,
|
pdf_path,
|
||||||
draw_sa2_measures=True,
|
draw_sa2_holes=True,
|
||||||
|
draw_sa2_arcs=True,
|
||||||
|
draw_sa2_chamfers=True,
|
||||||
|
draw_sa2_step_features=True,
|
||||||
raw_geometry=so
|
raw_geometry=so
|
||||||
)
|
)
|
||||||
self.assertTrue(os.path.exists(pdf_path))
|
self.assertTrue(os.path.exists(pdf_path))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue