524 lines
19 KiB
Python
524 lines
19 KiB
Python
import os
|
|
import tempfile
|
|
import numpy as np
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add workspace directory to sys.path
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.append(str(PROJECT_ROOT.parent))
|
|
|
|
# Mock bpy and dependencies for unit testing
|
|
class DummyNodeSocket: pass
|
|
class DummyNode: pass
|
|
class DummyObject: pass
|
|
class DummyMesh: pass
|
|
|
|
mock_types = MagicMock()
|
|
mock_types.NodeSocket = DummyNodeSocket
|
|
mock_types.Node = DummyNode
|
|
mock_types.Object = DummyObject
|
|
mock_types.Mesh = DummyMesh
|
|
|
|
mock_bpy = MagicMock()
|
|
mock_bpy.context.scene = MagicMock()
|
|
mock_bpy.context.scene.unit_settings.scale_length = 0.001
|
|
mock_bpy.types = mock_types
|
|
mock_bpy.path.abspath = lambda path: path.replace("//", "/abs/")
|
|
|
|
sys.modules['bpy'] = mock_bpy
|
|
sys.modules['bpy.types'] = mock_types
|
|
sys.modules['bpy.props'] = MagicMock()
|
|
sys.modules['bpy.utils'] = MagicMock()
|
|
|
|
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingGroup, SlicingCollection, SlicingObject
|
|
from NodeSlicer.core.pdf_exporter import (
|
|
extract_unique_top_view_edges,
|
|
extract_lineart_edges,
|
|
export_wireframe_to_pdf,
|
|
export_mesh_to_pdf,
|
|
MM_TO_PT
|
|
)
|
|
|
|
|
|
class TestPdfExporter(unittest.TestCase):
|
|
|
|
def test_extract_edges_from_slicing_object(self):
|
|
so = SlicingObject()
|
|
# Create a simple triangle in 3D: (0,0,0), (10,0,0), (0,10,0) with normal (0,0,1)
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[10.0, 0.0, 0.0],
|
|
[0.0, 10.0, 0.0],
|
|
[0.0, 0.0, 1.0] # Normal
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges = extract_unique_top_view_edges(so)
|
|
self.assertEqual(len(edges), 3)
|
|
|
|
def test_extract_edges_deduplication(self):
|
|
so = SlicingObject()
|
|
# Two triangles sharing an edge (10,0,0) - (0,10,0)
|
|
tri1 = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[10.0, 0.0, 0.0],
|
|
[0.0, 10.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
tri2 = np.array([
|
|
[10.0, 0.0, 0.0],
|
|
[10.0, 10.0, 0.0],
|
|
[0.0, 10.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri1, tri2])
|
|
|
|
edges = extract_unique_top_view_edges(so)
|
|
# Total edges: 3 + 3 - 1 shared = 5 unique edges
|
|
self.assertEqual(len(edges), 5)
|
|
|
|
def test_extract_edges_filters_vertical_lines(self):
|
|
so = SlicingObject()
|
|
# Vertical wall in top view (x, y remain constant, z changes) -> degenerate 2D line segment
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[0.0, 0.0, 10.0],
|
|
[10.0, 0.0, 0.0],
|
|
[0.0, 1.0, 0.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges = extract_unique_top_view_edges(so)
|
|
# Vertical edge (0,0,0)-(0,0,10) becomes 2D length 0 and should be filtered out
|
|
self.assertTrue(len(edges) < 3)
|
|
|
|
def test_extract_lineart_edges(self):
|
|
so = SlicingObject()
|
|
# Create 2 triangles sharing an edge at 90 degree angle (crease)
|
|
tri1 = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[10.0, 0.0, 0.0],
|
|
[0.0, 10.0, 0.0],
|
|
[0.0, 0.0, 1.0] # Normal up
|
|
], dtype=np.float64)
|
|
tri2 = np.array([
|
|
[10.0, 0.0, 0.0],
|
|
[10.0, 0.0, 10.0],
|
|
[0.0, 10.0, 0.0],
|
|
[1.0, 0.0, 0.0] # Normal right (90 deg to tri1)
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri1, tri2])
|
|
|
|
# At max_angle_deg=30, the 90 deg crease edge should be marked
|
|
lineart_30 = extract_lineart_edges(so, max_angle_deg=30.0)
|
|
self.assertTrue(len(lineart_30) > 0)
|
|
|
|
# At max_angle_deg=100, crease edge (90 deg) is below threshold, but boundary edges remain
|
|
lineart_100 = extract_lineart_edges(so, max_angle_deg=100.0)
|
|
self.assertTrue(len(lineart_100) > 0)
|
|
|
|
def test_export_pdf_true_1_to_1_scale(self):
|
|
edges_2d = np.array([
|
|
[[0.0, 0.0], [100.0, 0.0]],
|
|
[[100.0, 0.0], [100.0, 100.0]],
|
|
[[100.0, 100.0], [0.0, 100.0]],
|
|
[[0.0, 100.0], [0.0, 0.0]]
|
|
], dtype=np.float64)
|
|
|
|
margin_mm = 10.0
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_true_scale.pdf")
|
|
export_wireframe_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
margin_mm=margin_mm,
|
|
stroke_color=(0.1, 0.2, 0.3),
|
|
bg_color=(0.9, 0.95, 1.0)
|
|
)
|
|
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read()
|
|
|
|
minor_step = 1.0 # main_step 10.0 / 10
|
|
total_margin_mm = margin_mm + 2.0 * minor_step
|
|
expected_w = (100.0 + 2 * total_margin_mm) * MM_TO_PT
|
|
expected_h = (100.0 + 2 * total_margin_mm) * MM_TO_PT
|
|
mediabox_str = f"/MediaBox [0 0 {expected_w:.2f} {expected_h:.2f}]".encode("utf-8")
|
|
|
|
self.assertTrue(content.startswith(b"%PDF-1.4"))
|
|
self.assertIn(mediabox_str, content)
|
|
self.assertIn(b"0.900 0.950 1.000 rg", content) # Background color
|
|
self.assertIn(b"0.100 0.200 0.300 RG", content) # Wireframe color
|
|
self.assertIn(b"/GS25 gs", content)
|
|
self.assertIn(b"/GS50 gs", content)
|
|
self.assertIn(b"/GS100 gs", content)
|
|
self.assertTrue(content.endswith(b"%%EOF\n"))
|
|
|
|
def test_export_pdf_line_layers(self):
|
|
so = SlicingObject()
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[100.0, 0.0, 0.0],
|
|
[0.0, 100.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
configs = [
|
|
{"draw_wireframe": True, "draw_contour": False, "draw_crease": False},
|
|
{"draw_wireframe": False, "draw_contour": True, "draw_crease": False},
|
|
{"draw_wireframe": False, "draw_contour": False, "draw_crease": True},
|
|
{"draw_wireframe": True, "draw_contour": True, "draw_crease": True},
|
|
]
|
|
|
|
for i, cfg in enumerate(configs):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, f"test_layers_{i}.pdf")
|
|
export_wireframe_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
crease_angle=30.0,
|
|
raw_geometry=so,
|
|
**cfg
|
|
)
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read()
|
|
self.assertTrue(content.startswith(b"%PDF-1.4"))
|
|
self.assertTrue(content.endswith(b"%%EOF\n"))
|
|
self.assertIn(b"(X) Tj", content)
|
|
self.assertIn(b"(Y) Tj", content)
|
|
|
|
def test_export_pdf_show_name(self):
|
|
so = SlicingObject()
|
|
so.name = "MyTestPart"
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[50.0, 0.0, 0.0],
|
|
[0.0, 50.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_show_name.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
show_name=True,
|
|
raw_geometry=so
|
|
)
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read()
|
|
self.assertIn(b"(MyTestPart) Tj", content)
|
|
|
|
def test_export_pdf_show_bbox_size(self):
|
|
so = SlicingObject()
|
|
so.name = "GearPart"
|
|
tri = np.array([
|
|
[10.0, 20.0, 0.0],
|
|
[130.0, 20.0, 0.0],
|
|
[10.0, 250.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_show_bbox.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
show_name=True,
|
|
show_bbox_size=True,
|
|
raw_geometry=so
|
|
)
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read()
|
|
# Width = 120mm, Height = 230mm -> Line 1: (GearPart) Tj, Line 2: (\(X: 120mm Y: 230mm\)) Tj
|
|
self.assertIn(b"(GearPart) Tj", content)
|
|
self.assertIn(b"(\\(X: 120mm Y: 230mm\\)) Tj", content)
|
|
|
|
def test_export_pdf_draw_bbox(self):
|
|
so = SlicingObject()
|
|
so.name = "BBoxPart"
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[100.0, 0.0, 0.0],
|
|
[0.0, 50.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_draw_bbox.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
draw_bbox=True,
|
|
bbox_color=(0.0, 0.0, 1.0),
|
|
raw_geometry=so
|
|
)
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read()
|
|
# BBox color blue: 0.000 0.000 1.000 RG
|
|
self.assertIn(b"0.000 0.000 1.000 RG", content)
|
|
|
|
def test_pdf_object_physical_size_matches_3d_mesh(self):
|
|
"""
|
|
Verifies that the physical size of an object exported to PDF (in PostScript points pt)
|
|
matches its 3D mesh dimensions (in mm) * (72.0 / 25.4) with 100% mathematical precision.
|
|
"""
|
|
import re
|
|
|
|
mesh_w_mm = 120.0
|
|
mesh_h_mm = 80.0
|
|
min_x_3d, min_y_3d = 25.0, -15.0
|
|
max_x_3d, max_y_3d = min_x_3d + mesh_w_mm, min_y_3d + mesh_h_mm
|
|
|
|
so = SlicingObject()
|
|
so.name = "ScaledPart"
|
|
tri1 = np.array([
|
|
[min_x_3d, min_y_3d, 0.0],
|
|
[max_x_3d, min_y_3d, 0.0],
|
|
[min_x_3d, max_y_3d, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
tri2 = np.array([
|
|
[max_x_3d, min_y_3d, 0.0],
|
|
[max_x_3d, max_y_3d, 0.0],
|
|
[min_x_3d, max_y_3d, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri1, tri2])
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_physical_scale.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
draw_wireframe=True,
|
|
margin_mm=15.0,
|
|
line_width_mm=0.5,
|
|
raw_geometry=so
|
|
)
|
|
|
|
with open(pdf_path, "rb") as f:
|
|
pdf_text = f.read().decode("utf-8", errors="ignore")
|
|
|
|
mediabox_match = re.search(r"/MediaBox\s+\[0\s+0\s+(\d+\.\d+)\s+(\d+\.\d+)\]", pdf_text)
|
|
self.assertIsNotNone(mediabox_match)
|
|
page_w_pt = float(mediabox_match.group(1))
|
|
page_h_pt = float(mediabox_match.group(2))
|
|
|
|
# Verify MediaBox exact conversion: 1 pt = 1/72 inch = 25.4/72 mm
|
|
page_w_mm = page_w_pt / MM_TO_PT
|
|
page_h_mm = page_h_pt / MM_TO_PT
|
|
|
|
self.assertAlmostEqual(page_w_mm * MM_TO_PT, page_w_pt, places=4)
|
|
self.assertAlmostEqual(page_h_mm * MM_TO_PT, page_h_pt, places=4)
|
|
|
|
# Object physical size in PDF points:
|
|
object_w_pt = mesh_w_mm * MM_TO_PT
|
|
object_h_pt = mesh_h_mm * MM_TO_PT
|
|
|
|
# 120 mm -> 340.15748 pt, 80 mm -> 226.77165 pt
|
|
self.assertAlmostEqual(object_w_pt, 120.0 * (72.0 / 25.4), places=5)
|
|
self.assertAlmostEqual(object_h_pt, 80.0 * (72.0 / 25.4), places=5)
|
|
|
|
# Extract drawn object line coordinates under /GS100 gs from the PDF stream
|
|
gs100_idx = pdf_text.find("/GS100 gs")
|
|
self.assertNotEqual(gs100_idx, -1)
|
|
gs100_section = pdf_text[gs100_idx:]
|
|
|
|
line_matches = re.findall(r"(\d+\.\d+)\s+(\d+\.\d+)\s+m\s+(\d+\.\d+)\s+(\d+\.\d+)\s+l", gs100_section)
|
|
self.assertTrue(len(line_matches) >= 4)
|
|
|
|
obj_x, obj_y = [], []
|
|
for x0, y0, x1, y1 in line_matches[:4]:
|
|
obj_x.extend([float(x0), float(x1)])
|
|
obj_y.extend([float(y0), float(y1)])
|
|
|
|
drawn_obj_w_pt = max(obj_x) - min(obj_x)
|
|
drawn_obj_h_pt = max(obj_y) - min(obj_y)
|
|
|
|
# Assert that actual drawn line span in PDF points EXACTLY equals 3D mesh dimensions
|
|
self.assertAlmostEqual(drawn_obj_w_pt, 120.0 * (72.0 / 25.4), places=2)
|
|
self.assertAlmostEqual(drawn_obj_h_pt, 80.0 * (72.0 / 25.4), places=2)
|
|
|
|
def test_complex_slicing_group_pdf_exporter_end_to_end(self):
|
|
"""
|
|
End-to-end test on a complex multi-object SlicingGroup containing 3D mechanical
|
|
components (chamfered block + hollow prism), testing all PDF layers simultaneously.
|
|
"""
|
|
import re
|
|
|
|
# Object 1: 3D Chamfered Block
|
|
so1 = SlicingObject()
|
|
so1.name = "BaseBracket"
|
|
# Triangles forming a 3D block with a chamfered face
|
|
tris1 = [
|
|
# Bottom face
|
|
[[10.0, 20.0, 0.0], [110.0, 20.0, 0.0], [10.0, 70.0, 0.0], [0.0, 0.0, -1.0]],
|
|
[[110.0, 20.0, 0.0], [110.0, 70.0, 0.0], [10.0, 70.0, 0.0], [0.0, 0.0, -1.0]],
|
|
# Top face
|
|
[[10.0, 20.0, 30.0], [10.0, 70.0, 30.0], [90.0, 20.0, 30.0], [0.0, 0.0, 1.0]],
|
|
[[90.0, 20.0, 30.0], [10.0, 70.0, 30.0], [90.0, 70.0, 30.0], [0.0, 0.0, 1.0]],
|
|
# Chamfer face (45 deg angle)
|
|
[[90.0, 20.0, 30.0], [90.0, 70.0, 30.0], [110.0, 20.0, 0.0], [0.707, 0.0, 0.707]],
|
|
[[110.0, 20.0, 0.0], [90.0, 70.0, 30.0], [110.0, 70.0, 0.0], [0.707, 0.0, 0.707]],
|
|
]
|
|
so1.mesh = np.array(tris1, dtype=np.float64)
|
|
|
|
# Object 2: 3D Hollow Octagonal Prism
|
|
so2 = SlicingObject()
|
|
so2.name = "HollowPipe"
|
|
# Construct an 8-sided hollow tube in 3D
|
|
angles = np.linspace(0, 2 * np.pi, 8, endpoint=False)
|
|
r_out = 30.0
|
|
r_in = 15.0
|
|
cx, cy = 180.0, 45.0
|
|
|
|
tube_tris = []
|
|
for i in range(8):
|
|
a1, a2 = angles[i], angles[(i + 1) % 8]
|
|
x1_o, y1_o = cx + r_out * np.cos(a1), cy + r_out * np.sin(a1)
|
|
x2_o, y2_o = cx + r_out * np.cos(a2), cy + r_out * np.sin(a2)
|
|
x1_i, y1_i = cx + r_in * np.cos(a1), cy + r_in * np.sin(a1)
|
|
x2_i, y2_i = cx + r_in * np.cos(a2), cy + r_in * np.sin(a2)
|
|
|
|
# Top ring face
|
|
tube_tris.append([[x1_o, y1_o, 50.0], [x2_o, y2_o, 50.0], [x1_i, y1_i, 50.0], [0.0, 0.0, 1.0]])
|
|
tube_tris.append([[x2_o, y2_o, 50.0], [x2_i, y2_i, 50.0], [x1_i, y1_i, 50.0], [0.0, 0.0, 1.0]])
|
|
# Outer wall face
|
|
nx, ny = np.cos((a1 + a2) / 2), np.sin((a1 + a2) / 2)
|
|
tube_tris.append([[x1_o, y1_o, 0.0], [x2_o, y2_o, 0.0], [x1_o, y1_o, 50.0], [nx, ny, 0.0]])
|
|
tube_tris.append([[x2_o, y2_o, 0.0], [x2_o, y2_o, 50.0], [x1_o, y1_o, 50.0], [nx, ny, 0.0]])
|
|
|
|
so2.mesh = np.array(tube_tris, dtype=np.float64)
|
|
|
|
# Assemble into SlicingGroup with 2 collections
|
|
sc1 = SlicingCollection([], "BracketColl")
|
|
sc1.objects = [so1]
|
|
|
|
sc2 = SlicingCollection([], "PipeColl")
|
|
sc2.objects = [so2]
|
|
|
|
sg = SlicingGroup([])
|
|
sg.collections = {"BracketColl": sc1, "PipeColl": sc2}
|
|
|
|
# Extract 2D wireframe edges from SlicingGroup
|
|
edges_2d = extract_unique_top_view_edges(sg)
|
|
self.assertTrue(len(edges_2d) > 0)
|
|
|
|
# Export to PDF with ALL checkboxes enabled
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "complex_slicing_group.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
draw_wireframe=True,
|
|
draw_boundary=True,
|
|
draw_contour=True,
|
|
draw_crease=True,
|
|
show_name=True,
|
|
crease_angle=30.0,
|
|
margin_mm=12.5,
|
|
line_width_mm=0.8,
|
|
stroke_color=(0.1, 0.2, 0.4),
|
|
bg_color=(0.95, 0.98, 1.0),
|
|
raw_geometry=sg
|
|
)
|
|
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
pdf_bytes = f.read()
|
|
|
|
pdf_text = pdf_bytes.decode("utf-8", errors="ignore")
|
|
|
|
# 1. Check PDF header & footer
|
|
self.assertTrue(pdf_bytes.startswith(b"%PDF-1.4"))
|
|
self.assertIn(b"%%EOF\n", pdf_bytes)
|
|
|
|
# 2. Check MediaBox
|
|
mediabox_match = re.search(r"/MediaBox\s+\[0\s+0\s+(\d+\.\d+)\s+(\d+\.\d+)\]", pdf_text)
|
|
self.assertIsNotNone(mediabox_match)
|
|
w_pt, h_pt = float(mediabox_match.group(1)), float(mediabox_match.group(2))
|
|
self.assertTrue(w_pt > 0 and h_pt > 0)
|
|
|
|
# 3. Check ExtGState opacities (/GS25, /GS50, /GS100)
|
|
self.assertIn("/GS25 5 0 R", pdf_text)
|
|
self.assertIn("/GS50 6 0 R", pdf_text)
|
|
self.assertIn("/GS100 7 0 R", pdf_text)
|
|
|
|
# 4. Check colors
|
|
self.assertIn("0.950 0.980 1.000 rg", pdf_text) # BG color
|
|
self.assertIn("0.100 0.200 0.400 RG", pdf_text) # Stroke color
|
|
|
|
# 5. Check object name rendering for both objects
|
|
self.assertIn("(BaseBracket) Tj", pdf_text)
|
|
self.assertIn("(HollowPipe) Tj", pdf_text)
|
|
|
|
# 6. Check size legend tick labels
|
|
self.assertIn("(10) Tj", pdf_text)
|
|
self.assertIn("(100 mm) Tj", pdf_text)
|
|
|
|
def test_export_pdf_with_settings_attributes(self):
|
|
so = SlicingObject()
|
|
so.name = "AttributedPart"
|
|
tri = np.array([
|
|
[0.0, 0.0, 0.0],
|
|
[10.0, 0.0, 0.0],
|
|
[0.0, 10.0, 0.0],
|
|
[0.0, 0.0, 1.0]
|
|
], dtype=np.float64)
|
|
so.mesh = np.array([tri])
|
|
so.attributes = {
|
|
"infill_density": {"domain": "face", "type": "float", "values": np.array([20.0, 20.0])},
|
|
"layer_height": {"domain": "face", "type": "float", "values": np.array([0.15, 0.15])},
|
|
"material": ["PLA", "PLA"]
|
|
}
|
|
|
|
edges_2d = extract_unique_top_view_edges(so)
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
pdf_path = os.path.join(tmpdir, "test_settings_attr.pdf")
|
|
export_mesh_to_pdf(
|
|
edges_2d,
|
|
pdf_path,
|
|
show_name=True,
|
|
show_bbox_size=True,
|
|
raw_geometry=so,
|
|
settings={"infill_density": "%", "layer_height": " mm", "material": ""}
|
|
)
|
|
self.assertTrue(os.path.exists(pdf_path))
|
|
with open(pdf_path, "rb") as f:
|
|
content = f.read().decode("utf-8", errors="ignore")
|
|
|
|
self.assertIn("(AttributedPart) Tj", content)
|
|
self.assertIn("(infill_density: 20%) Tj", content)
|
|
self.assertIn("(layer_height: 0.15 mm) Tj", content)
|
|
self.assertIn("(material: PLA) Tj", content)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|
|
|