grid
This commit is contained in:
parent
40cc779e5d
commit
32e43c748b
5 changed files with 305 additions and 107 deletions
|
|
@ -367,6 +367,10 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
draw_sa2_holes: NfBoolProperty(name="Holes (Ø)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
draw_sa2_holes: NfBoolProperty(name="Holes (Ø)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
show_lineart: NfBoolProperty(name="Lineart Visibility", default=False) # pyright: ignore[reportInvalidTypeForm]
|
show_lineart: NfBoolProperty(name="Lineart Visibility", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
show_sa2: NfBoolProperty(name="SA2 Settings", default=False) # pyright: ignore[reportInvalidTypeForm]
|
show_sa2: NfBoolProperty(name="SA2 Settings", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_grid_xy: NfBoolProperty(name="XY Grid (Top)", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_grid_xz: NfBoolProperty(name="XZ Grid (Front)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
draw_grid_yz: NfBoolProperty(name="YZ Grid (Side)", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
show_grids: NfBoolProperty(name="Grids", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
draw_header: NfBoolProperty(name="Technical Header", default=True) # pyright: ignore[reportInvalidTypeForm]
|
draw_header: NfBoolProperty(name="Technical Header", default=True) # pyright: ignore[reportInvalidTypeForm]
|
||||||
show_header: NfBoolProperty(name="Technical Drawing Header", default=False) # pyright: ignore[reportInvalidTypeForm]
|
show_header: NfBoolProperty(name="Technical Drawing Header", default=False) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
@ -448,6 +452,14 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.prop(self, "draw_sa2_holes")
|
box.prop(self, "draw_sa2_holes")
|
||||||
|
|
||||||
|
# Grid Display subpanel
|
||||||
|
layout.prop(self, "show_grids", text="Grids", icon='TRIA_DOWN' if self.show_grids else 'TRIA_RIGHT')
|
||||||
|
if self.show_grids:
|
||||||
|
box = layout.box()
|
||||||
|
box.prop(self, "draw_grid_xy")
|
||||||
|
box.prop(self, "draw_grid_xz")
|
||||||
|
box.prop(self, "draw_grid_yz")
|
||||||
|
|
||||||
# Display settings
|
# Display settings
|
||||||
layout.prop(self, "draw_bbox")
|
layout.prop(self, "draw_bbox")
|
||||||
if self.draw_bbox:
|
if self.draw_bbox:
|
||||||
|
|
@ -599,6 +611,9 @@ class SLICENODE_MeshToPdf(ExecSliceNode):
|
||||||
bbox_color=(float(self.bbox_color[0]), float(self.bbox_color[1]), float(self.bbox_color[2])),
|
bbox_color=(float(self.bbox_color[0]), float(self.bbox_color[1]), float(self.bbox_color[2])),
|
||||||
raw_geometry=current_raw_geom,
|
raw_geometry=current_raw_geom,
|
||||||
slice_paths=current_slice_paths,
|
slice_paths=current_slice_paths,
|
||||||
|
draw_grid_xy=getattr(self, "draw_grid_xy", True),
|
||||||
|
draw_grid_xz=getattr(self, "draw_grid_xz", False),
|
||||||
|
draw_grid_yz=getattr(self, "draw_grid_yz", False),
|
||||||
)
|
)
|
||||||
if operator: operator.report({'INFO'}, f"Mesh PDF saved to {filepath}")
|
if operator: operator.report({'INFO'}, f"Mesh PDF saved to {filepath}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,26 @@ def get_first_object_rotation(
|
||||||
return (0.0, 0.0, 0.0)
|
return (0.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def map_2d_to_3d(u: float, v: float, d: float, plane: str = "XY") -> NDArray[np.float64]:
|
||||||
|
p = plane.upper()
|
||||||
|
if p == "XZ":
|
||||||
|
return np.array([u, d, v], dtype=np.float64)
|
||||||
|
elif p == "YZ":
|
||||||
|
return np.array([d, u, v], dtype=np.float64)
|
||||||
|
else: # XY
|
||||||
|
return np.array([u, v, d], dtype=np.float64)
|
||||||
|
|
||||||
|
|
||||||
|
def get_plane_2d_indices(plane: str = "XY") -> tuple[list[int], int]:
|
||||||
|
p = plane.upper()
|
||||||
|
if p == "XZ":
|
||||||
|
return [0, 2], 1
|
||||||
|
elif p == "YZ":
|
||||||
|
return [1, 2], 0
|
||||||
|
else: # XY
|
||||||
|
return [0, 1], 2
|
||||||
|
|
||||||
|
|
||||||
def generate_grid_slice_paths(
|
def generate_grid_slice_paths(
|
||||||
grid_min_x: float,
|
grid_min_x: float,
|
||||||
grid_max_x: float,
|
grid_max_x: float,
|
||||||
|
|
@ -152,10 +172,11 @@ def generate_grid_slice_paths(
|
||||||
minor_step: float,
|
minor_step: float,
|
||||||
max_grid_steps: int = 2000,
|
max_grid_steps: int = 2000,
|
||||||
z_level: float = 0.0,
|
z_level: float = 0.0,
|
||||||
|
plane: str = "XY",
|
||||||
) -> tuple[Any, Any]:
|
) -> tuple[Any, Any]:
|
||||||
"""
|
"""
|
||||||
Generates 3D grid line geometry sitting at local Z=z_level as two SlicePaths objects:
|
Generates 3D grid line geometry sitting at local depth d=z_level on specified local plane (XY, XZ, YZ)
|
||||||
(minor_grid_slice_paths, main_grid_slice_paths).
|
as two SlicePaths objects: (minor_grid_slice_paths, main_grid_slice_paths).
|
||||||
"""
|
"""
|
||||||
from ..slice import SlicePaths
|
from ..slice import SlicePaths
|
||||||
|
|
||||||
|
|
@ -173,19 +194,23 @@ def generate_grid_slice_paths(
|
||||||
minor_paths: list[NDArray[np.float64]] = []
|
minor_paths: list[NDArray[np.float64]] = []
|
||||||
main_paths: list[NDArray[np.float64]] = []
|
main_paths: list[NDArray[np.float64]] = []
|
||||||
|
|
||||||
# Vertical grid lines (constant X)
|
# Vertical grid lines (constant U)
|
||||||
for k in k_x:
|
for k in k_x:
|
||||||
gx = float(k * minor_step)
|
gu = float(k * minor_step)
|
||||||
line = np.array([[gx, grid_min_y, z_level], [gx, grid_max_y, z_level]], dtype=np.float64)
|
p1 = map_2d_to_3d(gu, grid_min_y, z_level, plane)
|
||||||
|
p2 = map_2d_to_3d(gu, grid_max_y, z_level, plane)
|
||||||
|
line = np.array([p1, p2], dtype=np.float64)
|
||||||
if k % 10 == 0:
|
if k % 10 == 0:
|
||||||
main_paths.append(line)
|
main_paths.append(line)
|
||||||
else:
|
else:
|
||||||
minor_paths.append(line)
|
minor_paths.append(line)
|
||||||
|
|
||||||
# Horizontal grid lines (constant Y)
|
# Horizontal grid lines (constant V)
|
||||||
for k in k_y:
|
for k in k_y:
|
||||||
gy = float(k * minor_step)
|
gv = float(k * minor_step)
|
||||||
line = np.array([[grid_min_x, gy, z_level], [grid_max_x, gy, z_level]], dtype=np.float64)
|
p1 = map_2d_to_3d(grid_min_x, gv, z_level, plane)
|
||||||
|
p2 = map_2d_to_3d(grid_max_x, gv, z_level, plane)
|
||||||
|
line = np.array([p1, p2], dtype=np.float64)
|
||||||
if k % 10 == 0:
|
if k % 10 == 0:
|
||||||
main_paths.append(line)
|
main_paths.append(line)
|
||||||
else:
|
else:
|
||||||
|
|
@ -203,9 +228,15 @@ def generate_legend_slice_paths(
|
||||||
line_width_pt: float,
|
line_width_pt: float,
|
||||||
font_size_pt: float,
|
font_size_pt: float,
|
||||||
z_level: float = 0.0,
|
z_level: float = 0.0,
|
||||||
|
plane: str = "XY",
|
||||||
|
draw_x: bool = True,
|
||||||
|
draw_y: bool = True,
|
||||||
|
draw_z: bool = False,
|
||||||
|
local_origin_3d: tuple[float, float, float] | None = None,
|
||||||
|
local_max_3d: tuple[float, float, float] | None = None,
|
||||||
) -> tuple[Any, list[dict[str, Any]]]:
|
) -> tuple[Any, list[dict[str, Any]]]:
|
||||||
"""
|
"""
|
||||||
Generates 3D legend line geometry (axes, arrowheads, tick marks) sitting at local Z=z_level
|
Generates 3D legend line geometry (axes, arrowheads, tick marks) starting at origin (min x, max y, min z)
|
||||||
as a SlicePaths object, alongside tick and label metadata for text positioning.
|
as a SlicePaths object, alongside tick and label metadata for text positioning.
|
||||||
Returns: (legend_slice_paths, label_metadata_list)
|
Returns: (legend_slice_paths, label_metadata_list)
|
||||||
"""
|
"""
|
||||||
|
|
@ -213,74 +244,129 @@ def generate_legend_slice_paths(
|
||||||
from .sizing import LegendSizing, MM_TO_PT
|
from .sizing import LegendSizing, MM_TO_PT
|
||||||
|
|
||||||
leg_sizing = LegendSizing(main_step, minor_step, line_width_pt, font_size_pt)
|
leg_sizing = LegendSizing(main_step, minor_step, line_width_pt, font_size_pt)
|
||||||
x0 = grid_min_x
|
|
||||||
y0 = grid_min_y
|
|
||||||
|
|
||||||
x_grid_end_mm = min(grid_max_x, grid_min_x + 10.0 * main_step)
|
if local_origin_3d is not None:
|
||||||
num_main_steps = max(1, int(np.round((x_grid_end_mm - grid_min_x) / main_step)))
|
x0, y0, z0 = local_origin_3d
|
||||||
|
else:
|
||||||
|
x0 = grid_min_x
|
||||||
|
y0 = grid_min_y
|
||||||
|
z0 = z_level
|
||||||
|
|
||||||
|
max_x_val = local_max_3d[0] if local_max_3d else grid_max_x
|
||||||
|
min_y_val = local_max_3d[1] if local_max_3d else grid_min_y
|
||||||
|
max_z_val = local_max_3d[2] if local_max_3d else z_level
|
||||||
|
|
||||||
arrow_len_mm = leg_sizing.arrow_len_pt / MM_TO_PT
|
arrow_len_mm = leg_sizing.arrow_len_pt / MM_TO_PT
|
||||||
arrow_half_w_mm = leg_sizing.arrow_half_w_pt / MM_TO_PT
|
arrow_half_w_mm = leg_sizing.arrow_half_w_pt / MM_TO_PT
|
||||||
label_margin_mm = leg_sizing.label_margin_h_pt / MM_TO_PT
|
label_margin_mm = leg_sizing.label_margin_h_pt / MM_TO_PT
|
||||||
|
|
||||||
x_arrow_base = x_grid_end_mm + leg_sizing.x_axis_extension_mm
|
|
||||||
x_arrow_tip = x_arrow_base + arrow_len_mm
|
|
||||||
|
|
||||||
y_arrow_base = y0 + leg_sizing.y_axis_height_mm
|
|
||||||
y_arrow_tip = y_arrow_base + arrow_len_mm
|
|
||||||
|
|
||||||
legend_paths: list[NDArray[np.float64]] = []
|
legend_paths: list[NDArray[np.float64]] = []
|
||||||
|
|
||||||
# 1. Main horizontal X-axis line & Arrowhead
|
|
||||||
legend_paths.append(np.array([[x0, y0, z_level], [x_arrow_tip, y0, z_level]], dtype=np.float64))
|
|
||||||
legend_paths.append(np.array([[x_arrow_base, y0 + arrow_half_w_mm, z_level], [x_arrow_tip, y0, z_level]], dtype=np.float64))
|
|
||||||
legend_paths.append(np.array([[x_arrow_tip, y0, z_level], [x_arrow_base, y0 - arrow_half_w_mm, z_level]], dtype=np.float64))
|
|
||||||
|
|
||||||
# 2. X-axis tick marks & label positions
|
|
||||||
label_meta: list[dict[str, Any]] = []
|
label_meta: list[dict[str, Any]] = []
|
||||||
|
|
||||||
for k in range(num_main_steps + 1):
|
# 1. Local X Axis (extends in +X direction from x0)
|
||||||
xk = x0 + k * main_step
|
if draw_x:
|
||||||
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
x_grid_end_mm = min(max_x_val, x0 + 10.0 * main_step) if max_x_val > x0 else x0 + 10.0 * main_step
|
||||||
legend_paths.append(np.array([[xk, y0, z_level], [xk, y0 + tick_h, z_level]], dtype=np.float64))
|
num_main_steps = max(1, int(np.round(abs(x_grid_end_mm - x0) / main_step)))
|
||||||
|
|
||||||
if k > 0:
|
x_arrow_base = x_grid_end_mm + leg_sizing.x_axis_extension_mm
|
||||||
val_mm = k * main_step
|
x_arrow_tip = x_arrow_base + arrow_len_mm
|
||||||
if main_step >= 1000.0:
|
|
||||||
val_num = val_mm / 1000.0
|
|
||||||
unit = " m" if k == num_main_steps else ""
|
|
||||||
tick_str = f"{val_num:g}{unit}"
|
|
||||||
else:
|
|
||||||
unit = " mm" if k == num_main_steps else ""
|
|
||||||
tick_str = f"{val_mm:g}{unit}"
|
|
||||||
|
|
||||||
label_meta.append({
|
legend_paths.append(np.array([[x0, y0, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
||||||
"type": "tick",
|
legend_paths.append(np.array([[x_arrow_base, y0 + arrow_half_w_mm, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
||||||
"text": tick_str,
|
legend_paths.append(np.array([[x_arrow_tip, y0, z0], [x_arrow_base, y0 - arrow_half_w_mm, z0]], dtype=np.float64))
|
||||||
"local_pt": (xk, y0 + tick_h + label_margin_mm, z_level),
|
|
||||||
})
|
|
||||||
|
|
||||||
# 3. Main vertical Y-axis line & Arrowhead
|
for k in range(num_main_steps + 1):
|
||||||
legend_paths.append(np.array([[x0, y0, z_level], [x0, y_arrow_tip, z_level]], dtype=np.float64))
|
xk = x0 + k * main_step
|
||||||
legend_paths.append(np.array([[x0 - arrow_half_w_mm, y_arrow_base, z_level], [x0, y_arrow_tip, z_level]], dtype=np.float64))
|
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
||||||
legend_paths.append(np.array([[x0, y_arrow_tip, z_level], [x0 + arrow_half_w_mm, y_arrow_base, z_level]], dtype=np.float64))
|
legend_paths.append(np.array([[xk, y0, z0], [xk, y0 - tick_h, z0]], dtype=np.float64))
|
||||||
|
|
||||||
# 4. Axis labels meta
|
if k > 0:
|
||||||
label_meta.append({
|
val_mm = k * main_step
|
||||||
"type": "x_axis",
|
unit = " m" if (main_step >= 1000.0 and k == num_main_steps) else (" mm" if (main_step < 1000.0 and k == num_main_steps) else "")
|
||||||
"text": "X",
|
val_str = f"{val_mm/1000.0:g}{unit}" if main_step >= 1000.0 else f"{val_mm:g}{unit}"
|
||||||
"local_pt": (x_arrow_tip + label_margin_mm, y0, z_level),
|
label_meta.append({
|
||||||
})
|
"type": "tick",
|
||||||
label_meta.append({
|
"text": val_str,
|
||||||
"type": "y_axis",
|
"local_pt": (xk, y0 - tick_h - label_margin_mm, z0),
|
||||||
"text": "Y",
|
})
|
||||||
"local_pt": (x0, y_arrow_tip + label_margin_mm, z_level),
|
|
||||||
})
|
label_meta.append({
|
||||||
|
"type": "x_axis",
|
||||||
|
"text": "X",
|
||||||
|
"local_pt": (x_arrow_tip + label_margin_mm, y0, z0),
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2. Local Y Axis (extends in -Y direction from y0 towards min_y_val)
|
||||||
|
if draw_y:
|
||||||
|
y_grid_end_mm = max(min_y_val, y0 - 10.0 * main_step) if min_y_val < y0 else y0 - 10.0 * main_step
|
||||||
|
num_main_steps = max(1, int(np.round(abs(y0 - y_grid_end_mm) / main_step)))
|
||||||
|
|
||||||
|
y_arrow_base = y_grid_end_mm - leg_sizing.y_axis_height_mm
|
||||||
|
y_arrow_tip = y_arrow_base - arrow_len_mm
|
||||||
|
|
||||||
|
legend_paths.append(np.array([[x0, y0, z0], [x0, y_arrow_tip, z0]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0 - arrow_half_w_mm, y_arrow_base, z0], [x0, y_arrow_tip, z0]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0, y_arrow_tip, z0], [x0 + arrow_half_w_mm, y_arrow_base, z0]], dtype=np.float64))
|
||||||
|
|
||||||
|
if not draw_x:
|
||||||
|
for k in range(num_main_steps + 1):
|
||||||
|
yk = y0 - k * main_step
|
||||||
|
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
||||||
|
legend_paths.append(np.array([[x0, yk, z0], [x0 + tick_h, yk, z0]], dtype=np.float64))
|
||||||
|
|
||||||
|
if k > 0:
|
||||||
|
val_mm = k * main_step
|
||||||
|
unit = " m" if (main_step >= 1000.0 and k == num_main_steps) else (" mm" if (main_step < 1000.0 and k == num_main_steps) else "")
|
||||||
|
val_str = f"{val_mm/1000.0:g}{unit}" if main_step >= 1000.0 else f"{val_mm:g}{unit}"
|
||||||
|
label_meta.append({
|
||||||
|
"type": "tick",
|
||||||
|
"text": val_str,
|
||||||
|
"local_pt": (x0 + tick_h + label_margin_mm, yk, z0),
|
||||||
|
})
|
||||||
|
|
||||||
|
label_meta.append({
|
||||||
|
"type": "y_axis",
|
||||||
|
"text": "Y",
|
||||||
|
"local_pt": (x0, y_arrow_tip - label_margin_mm, z0),
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3. Local Z Axis (extends in +Z direction from z0 towards max_z_val)
|
||||||
|
if draw_z:
|
||||||
|
z_grid_end_mm = min(max_z_val, z0 + 10.0 * main_step) if max_z_val > z0 else z0 + 10.0 * main_step
|
||||||
|
num_main_steps = max(1, int(np.round(abs(z_grid_end_mm - z0) / main_step)))
|
||||||
|
|
||||||
|
z_arrow_base = z_grid_end_mm + leg_sizing.y_axis_height_mm
|
||||||
|
z_arrow_tip = z_arrow_base + arrow_len_mm
|
||||||
|
|
||||||
|
legend_paths.append(np.array([[x0, y0, z0], [x0, y0, z_arrow_tip]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0 - arrow_half_w_mm, y0, z_arrow_base], [x0, y0, z_arrow_tip]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0, y0, z_arrow_tip], [x0 + arrow_half_w_mm, y0, z_arrow_base]], dtype=np.float64))
|
||||||
|
|
||||||
|
if not draw_x:
|
||||||
|
for k in range(num_main_steps + 1):
|
||||||
|
zk = z0 + k * main_step
|
||||||
|
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
||||||
|
legend_paths.append(np.array([[x0, y0, zk], [x0 + tick_h, y0, zk]], dtype=np.float64))
|
||||||
|
|
||||||
|
if k > 0:
|
||||||
|
val_mm = k * main_step
|
||||||
|
unit = " m" if (main_step >= 1000.0 and k == num_main_steps) else (" mm" if (main_step < 1000.0 and k == num_main_steps) else "")
|
||||||
|
val_str = f"{val_mm/1000.0:g}{unit}" if main_step >= 1000.0 else f"{val_mm:g}{unit}"
|
||||||
|
label_meta.append({
|
||||||
|
"type": "tick",
|
||||||
|
"text": val_str,
|
||||||
|
"local_pt": (x0 + tick_h + label_margin_mm, y0, zk),
|
||||||
|
})
|
||||||
|
|
||||||
|
label_meta.append({
|
||||||
|
"type": "z_axis",
|
||||||
|
"text": "Z",
|
||||||
|
"local_pt": (x0, y0, z_arrow_tip + label_margin_mm),
|
||||||
|
})
|
||||||
|
|
||||||
return SlicePaths(legend_paths), label_meta
|
return SlicePaths(legend_paths), label_meta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def extract_slice_paths_edges(slice_paths: Any) -> NDArray[np.float64]:
|
def extract_slice_paths_edges(slice_paths: Any) -> NDArray[np.float64]:
|
||||||
"""
|
"""
|
||||||
Extracts 2D line segments (shape N x 2 x 2) from SlicePaths or a list/collection of SlicePaths.
|
Extracts 2D line segments (shape N x 2 x 2) from SlicePaths or a list/collection of SlicePaths.
|
||||||
|
|
@ -298,7 +384,8 @@ def extract_slice_paths_edges(slice_paths: Any) -> NDArray[np.float64]:
|
||||||
elif isinstance(item, SlicePaths):
|
elif isinstance(item, SlicePaths):
|
||||||
for path in item:
|
for path in item:
|
||||||
if path is not None and len(path) >= 2:
|
if path is not None and len(path) >= 2:
|
||||||
pts = np.asarray(path, dtype=np.float64)[:, :2]
|
p_arr = np.asarray(path, dtype=np.float64)
|
||||||
|
pts = p_arr[:, :2]
|
||||||
segs = np.stack([pts[:-1], pts[1:]], axis=1)
|
segs = np.stack([pts[:-1], pts[1:]], axis=1)
|
||||||
all_segments.append(segs)
|
all_segments.append(segs)
|
||||||
elif isinstance(item, (list, tuple)):
|
elif isinstance(item, (list, tuple)):
|
||||||
|
|
@ -440,20 +527,14 @@ def extract_lineart_edges(mesh_data: Any, max_angle_deg: float = 30.0) -> NDArra
|
||||||
if num_tris == 0:
|
if num_tris == 0:
|
||||||
return np.empty((0, 2, 2), dtype=np.float64)
|
return np.empty((0, 2, 2), dtype=np.float64)
|
||||||
|
|
||||||
# Top-view 2D vertices
|
# 2D vertices projected on plane
|
||||||
verts_2d = tris_3d[:, :, :2] # (N, 3, 2)
|
verts_2d = tris_3d[:, :, :2] # (N, 3, 2)
|
||||||
|
|
||||||
# For silhouette detection, categorize faces by normal.z sign
|
|
||||||
# For view direction (0,0,-1):
|
|
||||||
# - normal.z < 0: front-facing (facing viewer)
|
|
||||||
# - normal.z > 0: back-facing (facing away)
|
|
||||||
# - normal.z == 0: edge-on / vertical
|
|
||||||
normal_z = normals_unit[:, 2]
|
normal_z = normals_unit[:, 2]
|
||||||
epsilon = 1e-3
|
epsilon = 1e-3
|
||||||
face_category = np.zeros(num_tris, dtype=int)
|
face_category = np.zeros(num_tris, dtype=int)
|
||||||
face_category[normal_z < -epsilon] = 1 # Front-facing
|
face_category[normal_z < -epsilon] = 1 # Front-facing
|
||||||
face_category[normal_z > epsilon] = 2 # Back-facing
|
face_category[normal_z > epsilon] = 2 # Back-facing
|
||||||
# face_category == 0 means vertical (|normal_z| <= epsilon)
|
|
||||||
|
|
||||||
# Extract all 3 edges per triangle
|
# Extract all 3 edges per triangle
|
||||||
e0_3d = tris_3d[:, [0, 1], :]
|
e0_3d = tris_3d[:, [0, 1], :]
|
||||||
|
|
@ -481,7 +562,6 @@ def extract_lineart_edges(mesh_data: Any, max_angle_deg: float = 30.0) -> NDArra
|
||||||
if len(edges_3d) == 0:
|
if len(edges_3d) == 0:
|
||||||
return np.empty((0, 2, 2), dtype=np.float64)
|
return np.empty((0, 2, 2), dtype=np.float64)
|
||||||
|
|
||||||
# Canonical undirected edge sorting in 3D (rounded to 4 decimal places)
|
|
||||||
rounded_3d = np.round(edges_3d, decimals=4)
|
rounded_3d = np.round(edges_3d, decimals=4)
|
||||||
pA = rounded_3d[:, 0, :]
|
pA = rounded_3d[:, 0, :]
|
||||||
pB = rounded_3d[:, 1, :]
|
pB = rounded_3d[:, 1, :]
|
||||||
|
|
@ -497,7 +577,6 @@ def extract_lineart_edges(mesh_data: Any, max_angle_deg: float = 30.0) -> NDArra
|
||||||
canonical_2d = edges_2d.copy()
|
canonical_2d = edges_2d.copy()
|
||||||
canonical_2d[swap_mask] = canonical_2d[swap_mask][:, ::-1, :]
|
canonical_2d[swap_mask] = canonical_2d[swap_mask][:, ::-1, :]
|
||||||
|
|
||||||
# Unique 3D edge identification using NumPy structured view
|
|
||||||
flat_3d = canonical_3d.reshape(len(canonical_3d), 6)
|
flat_3d = canonical_3d.reshape(len(canonical_3d), 6)
|
||||||
view_bytes = np.ascontiguousarray(flat_3d).view(np.dtype((np.void, 48))).ravel()
|
view_bytes = np.ascontiguousarray(flat_3d).view(np.dtype((np.void, 48))).ravel()
|
||||||
unique_keys, first_indices, inverse_indices, counts = np.unique(
|
unique_keys, first_indices, inverse_indices, counts = np.unique(
|
||||||
|
|
@ -511,13 +590,9 @@ def extract_lineart_edges(mesh_data: Any, max_angle_deg: float = 30.0) -> NDArra
|
||||||
np.maximum.at(max_cat, inverse_indices, edge_category)
|
np.maximum.at(max_cat, inverse_indices, edge_category)
|
||||||
np.minimum.at(min_cat, inverse_indices, edge_category)
|
np.minimum.at(min_cat, inverse_indices, edge_category)
|
||||||
|
|
||||||
# Rule 1: Silhouette edge - adjacent faces have different categories
|
|
||||||
rule1_unique = (counts >= 2) & (max_cat != min_cat)
|
rule1_unique = (counts >= 2) & (max_cat != min_cat)
|
||||||
|
|
||||||
# Rule 2: Boundary edge (owned by 1 face)
|
|
||||||
rule2_unique = counts == 1
|
rule2_unique = counts == 1
|
||||||
|
|
||||||
# Rule 3: Crease edge (owned by 2+ faces with normal angle > max_angle_deg)
|
|
||||||
cos_threshold = math.cos(math.radians(max_angle_deg))
|
cos_threshold = math.cos(math.radians(max_angle_deg))
|
||||||
rule3_unique = np.zeros(num_unique, dtype=bool)
|
rule3_unique = np.zeros(num_unique, dtype=bool)
|
||||||
|
|
||||||
|
|
@ -527,7 +602,6 @@ def extract_lineart_edges(mesh_data: Any, max_angle_deg: float = 30.0) -> NDArra
|
||||||
sorted_edge_ids = inverse_indices[sort_by_edge]
|
sorted_edge_ids = inverse_indices[sort_by_edge]
|
||||||
sorted_normals = edge_normals[sort_by_edge]
|
sorted_normals = edge_normals[sort_by_edge]
|
||||||
|
|
||||||
# Vectorized pair matching for adjacent faces sharing the same edge ID
|
|
||||||
match = sorted_edge_ids[:-1] == sorted_edge_ids[1:]
|
match = sorted_edge_ids[:-1] == sorted_edge_ids[1:]
|
||||||
if np.any(match):
|
if np.any(match):
|
||||||
n1 = sorted_normals[:-1][match]
|
n1 = sorted_normals[:-1][match]
|
||||||
|
|
@ -688,8 +762,6 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
||||||
if num_tris == 0:
|
if num_tris == 0:
|
||||||
return np.empty((0, 2, 2), dtype=np.float64)
|
return np.empty((0, 2, 2), dtype=np.float64)
|
||||||
|
|
||||||
# Filter for top-facing faces (normal_z > epsilon).
|
|
||||||
# Back-facing (normal_z < -epsilon) and vertical (|normal_z| <= epsilon) faces are masked out.
|
|
||||||
normal_z = normals_unit[:, 2]
|
normal_z = normals_unit[:, 2]
|
||||||
epsilon = 1e-3
|
epsilon = 1e-3
|
||||||
front_mask = normal_z > epsilon
|
front_mask = normal_z > epsilon
|
||||||
|
|
@ -698,7 +770,6 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
||||||
if len(tris_3d) == 0:
|
if len(tris_3d) == 0:
|
||||||
return np.empty((0, 2, 2), dtype=np.float64)
|
return np.empty((0, 2, 2), dtype=np.float64)
|
||||||
|
|
||||||
# Extract 3 edges per front-facing triangle
|
|
||||||
e0_3d = tris_3d[:, [0, 1], :]
|
e0_3d = tris_3d[:, [0, 1], :]
|
||||||
e1_3d = tris_3d[:, [1, 2], :]
|
e1_3d = tris_3d[:, [1, 2], :]
|
||||||
e2_3d = tris_3d[:, [2, 0], :]
|
e2_3d = tris_3d[:, [2, 0], :]
|
||||||
|
|
@ -710,7 +781,6 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
||||||
e2_2d = verts_2d[:, [2, 0], :]
|
e2_2d = verts_2d[:, [2, 0], :]
|
||||||
edges_2d = np.concatenate([e0_2d, e1_2d, e2_2d], axis=0) # (3M, 2, 2)
|
edges_2d = np.concatenate([e0_2d, e1_2d, e2_2d], axis=0) # (3M, 2, 2)
|
||||||
|
|
||||||
# Filter out near zero-length 2D edges
|
|
||||||
dists_sq_2d = np.sum((edges_2d[:, 0, :] - edges_2d[:, 1, :]) ** 2, axis=1)
|
dists_sq_2d = np.sum((edges_2d[:, 0, :] - edges_2d[:, 1, :]) ** 2, axis=1)
|
||||||
valid_len_mask = dists_sq_2d >= 1e-8
|
valid_len_mask = dists_sq_2d >= 1e-8
|
||||||
|
|
||||||
|
|
@ -720,7 +790,6 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
||||||
if len(edges_3d) == 0:
|
if len(edges_3d) == 0:
|
||||||
return np.empty((0, 2, 2), dtype=np.float64)
|
return np.empty((0, 2, 2), dtype=np.float64)
|
||||||
|
|
||||||
# Canonical undirected edge sorting in 3D
|
|
||||||
rounded_3d = np.round(edges_3d, decimals=4)
|
rounded_3d = np.round(edges_3d, decimals=4)
|
||||||
pA = rounded_3d[:, 0, :]
|
pA = rounded_3d[:, 0, :]
|
||||||
pB = rounded_3d[:, 1, :]
|
pB = rounded_3d[:, 1, :]
|
||||||
|
|
@ -736,14 +805,12 @@ def extract_silhouette_contour_edges(mesh_data: Any) -> NDArray[np.float64]:
|
||||||
canonical_2d = edges_2d.copy()
|
canonical_2d = edges_2d.copy()
|
||||||
canonical_2d[swap_mask] = canonical_2d[swap_mask][:, ::-1, :]
|
canonical_2d[swap_mask] = canonical_2d[swap_mask][:, ::-1, :]
|
||||||
|
|
||||||
# Unique 3D edge identification
|
|
||||||
flat_3d = canonical_3d.reshape(len(canonical_3d), 6)
|
flat_3d = canonical_3d.reshape(len(canonical_3d), 6)
|
||||||
view_bytes = np.ascontiguousarray(flat_3d).view(np.dtype((np.void, 48))).ravel()
|
view_bytes = np.ascontiguousarray(flat_3d).view(np.dtype((np.void, 48))).ravel()
|
||||||
_, first_indices, counts = np.unique(
|
_, first_indices, counts = np.unique(
|
||||||
view_bytes, return_index=True, return_counts=True
|
view_bytes, return_index=True, return_counts=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Boundary edges of the front-facing surface have count == 1
|
|
||||||
contour_edges = canonical_2d[first_indices[counts == 1]]
|
contour_edges = canonical_2d[first_indices[counts == 1]]
|
||||||
return deduplicate_2d_edges(contour_edges)
|
return deduplicate_2d_edges(contour_edges)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from .geometry import (
|
||||||
extract_boundary_edges,
|
extract_boundary_edges,
|
||||||
extract_outer_contour_edges,
|
extract_outer_contour_edges,
|
||||||
extract_crease_edges,
|
extract_crease_edges,
|
||||||
|
get_plane_2d_indices,
|
||||||
)
|
)
|
||||||
from .sizing import SA2Sizing, compute_effective_stroke_and_font, MM_TO_PT
|
from .sizing import SA2Sizing, compute_effective_stroke_and_font, MM_TO_PT
|
||||||
from .pdf_utils import PdfStream, escape_pdf_text
|
from .pdf_utils import PdfStream, escape_pdf_text
|
||||||
|
|
@ -156,6 +157,7 @@ def extract_slicing_object_features(
|
||||||
draw_crease: bool = False,
|
draw_crease: bool = False,
|
||||||
crease_angle: float = 30.0,
|
crease_angle: float = 30.0,
|
||||||
bounds: tuple[float, float, float, float] | None = None,
|
bounds: tuple[float, float, float, float] | None = None,
|
||||||
|
plane: str = "XY",
|
||||||
) -> list[tuple[float, float, float]]:
|
) -> list[tuple[float, float, float]]:
|
||||||
"""
|
"""
|
||||||
Extracts circular bore / hole features (center_x, center_y, diameter_mm) from a SlicingObject
|
Extracts circular bore / hole features (center_x, center_y, diameter_mm) from a SlicingObject
|
||||||
|
|
@ -511,6 +513,7 @@ def render_sa2_measures_pdf(
|
||||||
draw_contour: bool = False,
|
draw_contour: bool = False,
|
||||||
draw_crease: bool = False,
|
draw_crease: bool = False,
|
||||||
crease_angle: float = 30.0,
|
crease_angle: float = 30.0,
|
||||||
|
plane: str = "XY",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Renders ISO 129-1 compliant SA2 hole measurements (bores, concentric steps, Z-depth, pitch) on PDF stream.
|
Renders ISO 129-1 compliant SA2 hole measurements (bores, concentric steps, Z-depth, pitch) on PDF stream.
|
||||||
|
|
|
||||||
|
|
@ -484,14 +484,18 @@ class PDFLegendElement(PDFElement):
|
||||||
|
|
||||||
# Generate legend 3D SlicePaths & label metadata
|
# Generate legend 3D SlicePaths & label metadata
|
||||||
legend_sp, label_meta = generate_legend_slice_paths(
|
legend_sp, label_meta = generate_legend_slice_paths(
|
||||||
exp.local_min_x,
|
exp.local_min_x_3d,
|
||||||
exp.local_min_y,
|
exp.local_min_y_3d,
|
||||||
exp.local_max_x,
|
exp.local_max_x_3d,
|
||||||
exp.main_step,
|
exp.main_step,
|
||||||
exp.minor_step,
|
exp.minor_step,
|
||||||
exp.line_width_pt,
|
exp.line_width_pt,
|
||||||
exp.font_size_pt,
|
exp.font_size_pt,
|
||||||
z_level=getattr(exp, "local_min_z", 0.0),
|
draw_x=getattr(exp, "draw_x_axis", True),
|
||||||
|
draw_y=getattr(exp, "draw_y_axis", True),
|
||||||
|
draw_z=getattr(exp, "draw_z_axis", False),
|
||||||
|
local_origin_3d=getattr(exp, "legend_origin_3d", None),
|
||||||
|
local_max_3d=getattr(exp, "legend_max_3d", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rotate legend SlicePaths with existing transform_slice_paths
|
# Rotate legend SlicePaths with existing transform_slice_paths
|
||||||
|
|
@ -725,8 +729,22 @@ class export_mesh_to_pdf:
|
||||||
raw_geometry: Any = None,
|
raw_geometry: Any = None,
|
||||||
slice_paths: Any = None,
|
slice_paths: Any = None,
|
||||||
settings: Any = None,
|
settings: Any = None,
|
||||||
|
projection_plane: str | None = None,
|
||||||
|
draw_grid_xy: bool = True,
|
||||||
|
draw_grid_xz: bool = False,
|
||||||
|
draw_grid_yz: bool = False,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if projection_plane is not None:
|
||||||
|
p_up = projection_plane.upper()
|
||||||
|
draw_grid_xy = (p_up == "XY")
|
||||||
|
draw_grid_xz = (p_up == "XZ")
|
||||||
|
draw_grid_yz = (p_up == "YZ")
|
||||||
|
|
||||||
|
self.draw_grid_xy = draw_grid_xy
|
||||||
|
self.draw_grid_xz = draw_grid_xz
|
||||||
|
self.draw_grid_yz = draw_grid_yz
|
||||||
|
self.projection_plane = "XY" if draw_grid_xy else ("XZ" if draw_grid_xz else "YZ")
|
||||||
self.edges_2d = edges_2d if edges_2d is not None else np.empty((0, 2, 2), dtype=np.float64)
|
self.edges_2d = edges_2d if edges_2d is not None else np.empty((0, 2, 2), dtype=np.float64)
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
self.draw_wireframe = draw_wireframe
|
self.draw_wireframe = draw_wireframe
|
||||||
|
|
@ -860,37 +878,77 @@ class export_mesh_to_pdf:
|
||||||
l_min_x, l_max_x = float(pts_local[:, 0].min()), float(pts_local[:, 0].max())
|
l_min_x, l_max_x = float(pts_local[:, 0].min()), float(pts_local[:, 0].max())
|
||||||
l_min_y, l_max_y = float(pts_local[:, 1].min()), float(pts_local[:, 1].max())
|
l_min_y, l_max_y = float(pts_local[:, 1].min()), float(pts_local[:, 1].max())
|
||||||
l_min_z, l_max_z = float(pts_local[:, 2].min()), float(pts_local[:, 2].max())
|
l_min_z, l_max_z = float(pts_local[:, 2].min()), float(pts_local[:, 2].max())
|
||||||
if R.T[2, 1] < -1e-6:
|
|
||||||
l_min_z = l_max_z
|
|
||||||
else:
|
else:
|
||||||
l_min_x, l_max_x, l_min_y, l_max_y = -50.0, 50.0, -50.0, 50.0
|
l_min_x, l_max_x, l_min_y, l_max_y, l_min_z, l_max_z = -50.0, 50.0, -50.0, 50.0, -50.0, 50.0
|
||||||
l_min_z = 0.0
|
|
||||||
|
w_geom, h_geom = max(l_max_x - l_min_x, 1.0), max(l_max_y - l_min_y, 1.0)
|
||||||
|
if self.draw_grid_xz and not self.draw_grid_xy:
|
||||||
|
w_geom, h_geom = max(l_max_x - l_min_x, 1.0), max(l_max_z - l_min_z, 1.0)
|
||||||
|
elif self.draw_grid_yz and not self.draw_grid_xy and not self.draw_grid_xz:
|
||||||
|
w_geom, h_geom = max(l_max_y - l_min_y, 1.0), max(l_max_z - l_min_z, 1.0)
|
||||||
|
|
||||||
self.local_min_z = l_min_z
|
|
||||||
w_geom = max(l_max_x - l_min_x, 1.0)
|
|
||||||
h_geom = max(l_max_y - l_min_y, 1.0)
|
|
||||||
self.main_step, self.minor_step = compute_grid_steps(w_geom, h_geom)
|
self.main_step, self.minor_step = compute_grid_steps(w_geom, h_geom)
|
||||||
|
|
||||||
self.local_min_x = math.floor((l_min_x - self.minor_step) / self.minor_step) * self.minor_step
|
self.grid_min_x = math.floor((l_min_x - self.minor_step) / self.minor_step) * self.minor_step
|
||||||
self.local_max_x = math.ceil((l_max_x + self.minor_step) / self.minor_step) * self.minor_step
|
self.grid_max_x = math.ceil((l_max_x + self.minor_step) / self.minor_step) * self.minor_step
|
||||||
self.local_min_y = math.floor((l_min_y - self.minor_step) / self.minor_step) * self.minor_step
|
self.grid_min_y = math.floor((l_min_y - self.minor_step) / self.minor_step) * self.minor_step
|
||||||
self.local_max_y = math.ceil((l_max_y + self.minor_step) / self.minor_step) * self.minor_step
|
self.grid_max_y = math.ceil((l_max_y + self.minor_step) / self.minor_step) * self.minor_step
|
||||||
|
|
||||||
|
# Object sits on XY grid: exact min Z (no margin below), margin over object on max Z
|
||||||
|
self.grid_min_z = l_min_z
|
||||||
|
self.grid_max_z = math.ceil((l_max_z + self.minor_step) / self.minor_step) * self.minor_step
|
||||||
|
|
||||||
# Generate 3D grid line SlicePaths sitting on local XY plane at the bottom of the object (Z=l_min_z)
|
self.local_min_x_3d = self.grid_min_x
|
||||||
minor_grid_sp, main_grid_sp = generate_grid_slice_paths(
|
self.local_max_x_3d = self.grid_max_x
|
||||||
self.local_min_x,
|
self.local_min_y_3d = self.grid_min_y
|
||||||
self.local_max_x,
|
self.local_max_y_3d = self.grid_max_y
|
||||||
self.local_min_y,
|
self.local_min_z_3d = self.grid_min_z
|
||||||
self.local_max_y,
|
self.local_max_z_3d = self.grid_max_z
|
||||||
self.minor_step,
|
|
||||||
z_level=l_min_z,
|
# Shared origin point (min X, max Y, min Z)
|
||||||
)
|
x0 = self.grid_min_x
|
||||||
|
y0 = self.grid_max_y
|
||||||
|
z0 = self.grid_min_z
|
||||||
|
|
||||||
|
self.legend_origin_3d = (x0, y0, z0)
|
||||||
|
self.legend_max_3d = (self.grid_max_x, self.grid_min_y, self.grid_max_z)
|
||||||
|
|
||||||
|
self.draw_x_axis = self.draw_grid_xy or self.draw_grid_xz
|
||||||
|
self.draw_y_axis = self.draw_grid_xy or self.draw_grid_yz
|
||||||
|
self.draw_z_axis = self.draw_grid_xz or self.draw_grid_yz
|
||||||
|
|
||||||
|
self.local_min_x = self.grid_min_x
|
||||||
|
self.local_max_x = self.grid_max_x
|
||||||
|
self.local_min_y = self.grid_min_y
|
||||||
|
self.local_max_y = self.grid_max_y
|
||||||
|
self.local_min_z = z0
|
||||||
|
|
||||||
|
grid_minor_paths: list[NDArray[np.float64]] = []
|
||||||
|
grid_main_paths: list[NDArray[np.float64]] = []
|
||||||
|
|
||||||
|
if self.draw_grid_xy:
|
||||||
|
min_sp, main_sp = generate_grid_slice_paths(self.grid_min_x, self.grid_max_x, self.grid_min_y, self.grid_max_y, self.minor_step, z_level=z0, plane="XY")
|
||||||
|
grid_minor_paths.extend(min_sp)
|
||||||
|
grid_main_paths.extend(main_sp)
|
||||||
|
|
||||||
|
if self.draw_grid_xz:
|
||||||
|
min_sp, main_sp = generate_grid_slice_paths(self.grid_min_x, self.grid_max_x, self.grid_min_z, self.grid_max_z, self.minor_step, z_level=y0, plane="XZ")
|
||||||
|
grid_minor_paths.extend(min_sp)
|
||||||
|
grid_main_paths.extend(main_sp)
|
||||||
|
|
||||||
|
if self.draw_grid_yz:
|
||||||
|
min_sp, main_sp = generate_grid_slice_paths(self.grid_min_y, self.grid_max_y, self.grid_min_z, self.grid_max_z, self.minor_step, z_level=x0, plane="YZ")
|
||||||
|
grid_minor_paths.extend(min_sp)
|
||||||
|
grid_main_paths.extend(main_sp)
|
||||||
|
|
||||||
|
from ..slice import SlicePaths
|
||||||
|
minor_grid_sp = SlicePaths(grid_minor_paths)
|
||||||
|
main_grid_sp = SlicePaths(grid_main_paths)
|
||||||
|
|
||||||
# Rotate grid SlicePaths back into world space
|
# Rotate grid SlicePaths back into world space
|
||||||
rot_minor_sp = transform_slice_paths(minor_grid_sp, np.zeros(3), r_arr, np.ones(3))
|
rot_minor_sp = transform_slice_paths(minor_grid_sp, np.zeros(3), r_arr, np.ones(3))
|
||||||
rot_main_sp = transform_slice_paths(main_grid_sp, np.zeros(3), r_arr, np.ones(3))
|
rot_main_sp = transform_slice_paths(main_grid_sp, np.zeros(3), r_arr, np.ones(3))
|
||||||
|
|
||||||
# Extract 2D top-view edge segments from rotated SlicePaths
|
|
||||||
minor_edges_2d = extract_slice_paths_edges(rot_minor_sp)
|
minor_edges_2d = extract_slice_paths_edges(rot_minor_sp)
|
||||||
main_edges_2d = extract_slice_paths_edges(rot_main_sp)
|
main_edges_2d = extract_slice_paths_edges(rot_main_sp)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1687,6 +1687,61 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
self.assertGreater(len(minor_sp), 0)
|
self.assertGreater(len(minor_sp), 0)
|
||||||
self.assertAlmostEqual(float(minor_sp[0][0, 2]), 15.0)
|
self.assertAlmostEqual(float(minor_sp[0][0, 2]), 15.0)
|
||||||
|
|
||||||
|
def test_projection_plane_selection_xz_and_yz(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingObject
|
||||||
|
from NodeSlicer.core.nodeslicer.nodes.output import SLICENODE_MeshToPdf
|
||||||
|
|
||||||
|
so = SlicingObject()
|
||||||
|
so.name = "Box3D"
|
||||||
|
# 3D triangle spanning X: [10, 50], Y: [20, 80], Z: [5, 40]
|
||||||
|
tri = np.array([[10.0, 20.0, 5.0], [50.0, 20.0, 5.0], [10.0, 80.0, 40.0]], dtype=np.float64)
|
||||||
|
so.mesh = np.array([tri])
|
||||||
|
|
||||||
|
# Default XY plane
|
||||||
|
exp_xy = export_mesh_to_pdf(raw_geometry=so, draw_grid_xy=True, draw_grid_xz=False, draw_grid_yz=False)
|
||||||
|
self.assertTrue(exp_xy.draw_grid_xy)
|
||||||
|
self.assertFalse(exp_xy.draw_grid_xz)
|
||||||
|
self.assertGreater(len(exp_xy.minor_lines_arr) + len(exp_xy.main_lines_arr), 0)
|
||||||
|
|
||||||
|
# Front view XZ plane
|
||||||
|
exp_xz = export_mesh_to_pdf(raw_geometry=so, draw_grid_xy=False, draw_grid_xz=True, draw_grid_yz=False)
|
||||||
|
self.assertTrue(exp_xz.draw_grid_xz)
|
||||||
|
self.assertGreater(len(exp_xz.minor_lines_arr) + len(exp_xz.main_lines_arr), 0)
|
||||||
|
|
||||||
|
# Side view YZ plane
|
||||||
|
exp_yz = export_mesh_to_pdf(raw_geometry=so, draw_grid_xy=False, draw_grid_xz=False, draw_grid_yz=True)
|
||||||
|
self.assertTrue(exp_yz.draw_grid_yz)
|
||||||
|
self.assertGreater(len(exp_yz.minor_lines_arr) + len(exp_yz.main_lines_arr), 0)
|
||||||
|
|
||||||
|
# Multi-grid plane selection (XY + YZ)
|
||||||
|
exp_multi = export_mesh_to_pdf(raw_geometry=so, draw_grid_xy=True, draw_grid_xz=False, draw_grid_yz=True)
|
||||||
|
self.assertTrue(exp_multi.draw_grid_xy)
|
||||||
|
self.assertTrue(exp_multi.draw_grid_yz)
|
||||||
|
self.assertTrue(exp_multi.draw_x_axis)
|
||||||
|
self.assertTrue(exp_multi.draw_y_axis)
|
||||||
|
self.assertTrue(exp_multi.draw_z_axis)
|
||||||
|
|
||||||
|
# Rotated object by X = 90 deg with XZ front grid
|
||||||
|
import math
|
||||||
|
so_rot = SlicingObject()
|
||||||
|
so_rot.name = "RotatedFrontBox"
|
||||||
|
so_rot.transforms = [{"rotation": [math.pi / 2.0, 0.0, 0.0]}]
|
||||||
|
tri_rot = np.array([[0.0, 0.0, 0.0], [100.0, 0.0, 0.0], [0.0, 50.0, 0.0]], dtype=np.float64)
|
||||||
|
so_rot.mesh = np.array([tri_rot])
|
||||||
|
|
||||||
|
exp_rot_front = export_mesh_to_pdf(raw_geometry=so_rot, draw_grid_xy=False, draw_grid_xz=True, draw_grid_yz=False)
|
||||||
|
self.assertTrue(exp_rot_front.draw_grid_xz)
|
||||||
|
self.assertGreater(len(exp_rot_front.minor_lines_arr) + len(exp_rot_front.main_lines_arr), 0)
|
||||||
|
|
||||||
|
# Node check
|
||||||
|
node = SLICENODE_MeshToPdf()
|
||||||
|
self.assertIn("draw_grid_xy", SLICENODE_MeshToPdf.__annotations__)
|
||||||
|
self.assertIn("draw_grid_xz", SLICENODE_MeshToPdf.__annotations__)
|
||||||
|
self.assertIn("draw_grid_yz", SLICENODE_MeshToPdf.__annotations__)
|
||||||
|
self.assertTrue(getattr(node, "draw_grid_xy", True))
|
||||||
|
self.assertFalse(getattr(node, "draw_grid_xz", False))
|
||||||
|
self.assertFalse(getattr(node, "draw_grid_yz", False))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue