exporter
This commit is contained in:
parent
926da63dd8
commit
bcf9334d78
3 changed files with 147 additions and 23 deletions
|
|
@ -269,6 +269,10 @@ def generate_legend_slice_paths(
|
||||||
legend_paths: list[NDArray[np.float64]] = []
|
legend_paths: list[NDArray[np.float64]] = []
|
||||||
label_meta: list[dict[str, Any]] = []
|
label_meta: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
p_up = plane.upper()
|
||||||
|
is_xz = (p_up == "XZ") or (draw_z and not draw_y)
|
||||||
|
is_yz = (p_up == "YZ") or (draw_z and not draw_x)
|
||||||
|
|
||||||
# 1. Local X Axis (extends in +X direction from x0)
|
# 1. Local X Axis (extends in +X direction from x0)
|
||||||
if draw_x:
|
if draw_x:
|
||||||
x_grid_end_mm = min(max_x_val, x0 + 10.0 * main_step) if max_x_val > x0 else x0 + 10.0 * main_step
|
x_grid_end_mm = min(max_x_val, x0 + 10.0 * main_step) if max_x_val > x0 else x0 + 10.0 * main_step
|
||||||
|
|
@ -278,23 +282,45 @@ def generate_legend_slice_paths(
|
||||||
x_arrow_tip = x_arrow_base + arrow_len_mm
|
x_arrow_tip = x_arrow_base + arrow_len_mm
|
||||||
|
|
||||||
legend_paths.append(np.array([[x0, y0, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
legend_paths.append(np.array([[x0, y0, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
||||||
legend_paths.append(np.array([[x_arrow_base, y0 + arrow_half_w_mm, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
|
||||||
legend_paths.append(np.array([[x_arrow_tip, y0, z0], [x_arrow_base, y0 - arrow_half_w_mm, z0]], dtype=np.float64))
|
|
||||||
|
|
||||||
for k in range(num_main_steps + 1):
|
if is_xz:
|
||||||
xk = x0 + k * main_step
|
# On XZ grid plane (constant y=y0, ticks & wings extend in Z direction)
|
||||||
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([[x_arrow_base, y0, z0 + arrow_half_w_mm], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
||||||
legend_paths.append(np.array([[xk, y0, z0], [xk, y0 - tick_h, z0]], dtype=np.float64))
|
legend_paths.append(np.array([[x_arrow_tip, y0, z0], [x_arrow_base, y0, z0 - arrow_half_w_mm]], dtype=np.float64))
|
||||||
|
|
||||||
if k > 0:
|
for k in range(num_main_steps + 1):
|
||||||
val_mm = k * main_step
|
xk = x0 + 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 "")
|
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
||||||
val_str = f"{val_mm/1000.0:g}{unit}" if main_step >= 1000.0 else f"{val_mm:g}{unit}"
|
legend_paths.append(np.array([[xk, y0, z0], [xk, y0, z0 + tick_h]], dtype=np.float64))
|
||||||
label_meta.append({
|
|
||||||
"type": "tick",
|
if k > 0:
|
||||||
"text": val_str,
|
val_mm = k * main_step
|
||||||
"local_pt": (xk, y0 - tick_h - label_margin_mm, z0),
|
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": (xk, y0, z0 + tick_h + label_margin_mm),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# On XY grid plane (constant z=z0, ticks & wings extend in -Y direction)
|
||||||
|
legend_paths.append(np.array([[x_arrow_base, y0 + arrow_half_w_mm, z0], [x_arrow_tip, y0, z0]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x_arrow_tip, y0, z0], [x_arrow_base, y0 - arrow_half_w_mm, z0]], dtype=np.float64))
|
||||||
|
|
||||||
|
for k in range(num_main_steps + 1):
|
||||||
|
xk = x0 + 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([[xk, y0, z0], [xk, y0 - tick_h, 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": (xk, y0 - tick_h - label_margin_mm, z0),
|
||||||
|
})
|
||||||
|
|
||||||
label_meta.append({
|
label_meta.append({
|
||||||
"type": "x_axis",
|
"type": "x_axis",
|
||||||
|
|
@ -311,14 +337,16 @@ def generate_legend_slice_paths(
|
||||||
y_arrow_tip = y_arrow_base - arrow_len_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, 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:
|
if is_yz:
|
||||||
|
# On YZ grid plane (constant x=x0, ticks & wings extend in Z direction)
|
||||||
|
legend_paths.append(np.array([[x0, y_arrow_base, z0 - arrow_half_w_mm], [x0, y_arrow_tip, z0]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0, y_arrow_tip, z0], [x0, y_arrow_base, z0 + arrow_half_w_mm]], dtype=np.float64))
|
||||||
|
|
||||||
for k in range(num_main_steps + 1):
|
for k in range(num_main_steps + 1):
|
||||||
yk = y0 - k * main_step
|
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
|
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))
|
legend_paths.append(np.array([[x0, yk, z0], [x0, yk, z0 + tick_h]], dtype=np.float64))
|
||||||
|
|
||||||
if k > 0:
|
if k > 0:
|
||||||
val_mm = k * main_step
|
val_mm = k * main_step
|
||||||
|
|
@ -327,8 +355,28 @@ def generate_legend_slice_paths(
|
||||||
label_meta.append({
|
label_meta.append({
|
||||||
"type": "tick",
|
"type": "tick",
|
||||||
"text": val_str,
|
"text": val_str,
|
||||||
"local_pt": (x0 + tick_h + label_margin_mm, yk, z0),
|
"local_pt": (x0, yk, z0 + tick_h + label_margin_mm),
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
# On XY grid plane (constant z=z0, ticks & wings extend in X direction)
|
||||||
|
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({
|
label_meta.append({
|
||||||
"type": "y_axis",
|
"type": "y_axis",
|
||||||
|
|
@ -345,10 +393,12 @@ def generate_legend_slice_paths(
|
||||||
z_arrow_tip = z_arrow_base + arrow_len_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, 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:
|
if is_xz or draw_x:
|
||||||
|
# On XZ grid plane (constant y=y0, ticks & wings extend in X direction)
|
||||||
|
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))
|
||||||
|
|
||||||
for k in range(num_main_steps + 1):
|
for k in range(num_main_steps + 1):
|
||||||
zk = z0 + k * main_step
|
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
|
tick_h = leg_sizing.major_tick_mm if k in (0, 5, num_main_steps) else leg_sizing.minor_tick_mm
|
||||||
|
|
@ -363,6 +413,25 @@ def generate_legend_slice_paths(
|
||||||
"text": val_str,
|
"text": val_str,
|
||||||
"local_pt": (x0 + tick_h + label_margin_mm, y0, zk),
|
"local_pt": (x0 + tick_h + label_margin_mm, y0, zk),
|
||||||
})
|
})
|
||||||
|
else:
|
||||||
|
# On YZ grid plane (constant x=x0, ticks & wings extend in Y direction)
|
||||||
|
legend_paths.append(np.array([[x0, y0 - arrow_half_w_mm, z_arrow_base], [x0, y0, z_arrow_tip]], dtype=np.float64))
|
||||||
|
legend_paths.append(np.array([[x0, y0, z_arrow_tip], [x0, y0 + arrow_half_w_mm, z_arrow_base]], dtype=np.float64))
|
||||||
|
|
||||||
|
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, y0 - tick_h, 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, y0 - tick_h - label_margin_mm, zk),
|
||||||
|
})
|
||||||
|
|
||||||
label_meta.append({
|
label_meta.append({
|
||||||
"type": "z_axis",
|
"type": "z_axis",
|
||||||
|
|
|
||||||
|
|
@ -586,6 +586,7 @@ class PDFLegendElement(PDFElement):
|
||||||
draw_z=getattr(exp, "draw_z_axis", False),
|
draw_z=getattr(exp, "draw_z_axis", False),
|
||||||
local_origin_3d=getattr(exp, "legend_origin_3d", None),
|
local_origin_3d=getattr(exp, "legend_origin_3d", None),
|
||||||
local_max_3d=getattr(exp, "legend_max_3d", None),
|
local_max_3d=getattr(exp, "legend_max_3d", None),
|
||||||
|
plane=getattr(exp, "projection_plane", "XY"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rotate legend SlicePaths with existing transform_slice_paths
|
# Rotate legend SlicePaths with existing transform_slice_paths
|
||||||
|
|
@ -629,6 +630,8 @@ class PDFLegendElement(PDFElement):
|
||||||
s.text_position(pt_pdf[0], pt_pdf[1] - (exp.font_size_pt * 0.35)).show(txt)
|
s.text_position(pt_pdf[0], pt_pdf[1] - (exp.font_size_pt * 0.35)).show(txt)
|
||||||
elif meta["type"] == "y_axis":
|
elif meta["type"] == "y_axis":
|
||||||
s.text_position(pt_pdf[0] - (exp.font_size_pt * 0.35), pt_pdf[1]).show(txt)
|
s.text_position(pt_pdf[0] - (exp.font_size_pt * 0.35), pt_pdf[1]).show(txt)
|
||||||
|
elif meta["type"] == "z_axis":
|
||||||
|
s.text_position(pt_pdf[0] - (exp.font_size_pt * 0.35), pt_pdf[1]).show(txt)
|
||||||
|
|
||||||
s.end_text()
|
s.end_text()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1877,6 +1877,58 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
self.assertTrue(generated_name.endswith(f"-{today_str}.pdf"))
|
self.assertTrue(generated_name.endswith(f"-{today_str}.pdf"))
|
||||||
self.assertIn("x", generated_name)
|
self.assertIn("x", generated_name)
|
||||||
|
|
||||||
|
def test_generate_legend_slice_paths_xz_plane(self):
|
||||||
|
from NodeSlicer.core.pdf_exporter.geometry import generate_legend_slice_paths
|
||||||
|
from NodeSlicer.core.slice import SlicePaths
|
||||||
|
|
||||||
|
legend_sp, label_meta = generate_legend_slice_paths(
|
||||||
|
grid_min_x=0.0,
|
||||||
|
grid_min_y=0.0,
|
||||||
|
grid_max_x=50.0,
|
||||||
|
main_step=10.0,
|
||||||
|
minor_step=1.0,
|
||||||
|
line_width_pt=0.5,
|
||||||
|
font_size_pt=3.0,
|
||||||
|
plane="XZ",
|
||||||
|
draw_x=True,
|
||||||
|
draw_y=False,
|
||||||
|
draw_z=True,
|
||||||
|
local_origin_3d=(0.0, 0.0, 0.0),
|
||||||
|
local_max_3d=(50.0, 0.0, 50.0),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsInstance(legend_sp, SlicePaths)
|
||||||
|
self.assertGreater(len(legend_sp), 0)
|
||||||
|
|
||||||
|
# Verify that all 3D line endpoints have constant y=0 (i.e. strictly in the XZ plane)
|
||||||
|
for path in legend_sp:
|
||||||
|
p_arr = np.asarray(path, dtype=np.float64)
|
||||||
|
np.testing.assert_allclose(p_arr[:, 1], 0.0, atol=1e-6, err_msg="Legend geometry for XZ plane must have Y=0")
|
||||||
|
|
||||||
|
# Verify that ticks on the X axis span in Z
|
||||||
|
x_ticks_in_z = [
|
||||||
|
path for path in legend_sp
|
||||||
|
if len(path) == 2 and abs(path[0, 0] - path[1, 0]) < 1e-6 and abs(path[0, 2] - path[1, 2]) > 0.1
|
||||||
|
]
|
||||||
|
self.assertGreater(len(x_ticks_in_z), 0, "X axis ticks must have height along Z on XZ plane")
|
||||||
|
|
||||||
|
# Verify that ticks on the Z axis span in X
|
||||||
|
z_ticks_in_x = [
|
||||||
|
path for path in legend_sp
|
||||||
|
if len(path) == 2 and abs(path[0, 2] - path[1, 2]) < 1e-6 and abs(path[0, 0] - path[1, 0]) > 0.1
|
||||||
|
]
|
||||||
|
self.assertGreater(len(z_ticks_in_x), 0, "Z axis ticks must have height along X on XZ plane")
|
||||||
|
|
||||||
|
# Verify labels
|
||||||
|
types = [m["type"] for m in label_meta]
|
||||||
|
self.assertIn("tick", types)
|
||||||
|
self.assertIn("x_axis", types)
|
||||||
|
self.assertIn("z_axis", types)
|
||||||
|
|
||||||
|
# Verify label positions are in XZ plane (Y=0)
|
||||||
|
for m in label_meta:
|
||||||
|
self.assertAlmostEqual(m["local_pt"][1], 0.0, places=5)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue