161 lines
5.8 KiB
Python
161 lines
5.8 KiB
Python
import io
|
|
import numpy
|
|
import ezdxf
|
|
from numpy.testing import assert_allclose
|
|
from pathlib import Path
|
|
|
|
from ..pattern import Pattern
|
|
from ..library import Library
|
|
from ..shapes import Path as MPath, Polygon
|
|
from ..repetition import Grid
|
|
from ..file import dxf
|
|
|
|
def test_dxf_roundtrip(tmp_path: Path):
|
|
lib = Library()
|
|
pat = Pattern()
|
|
|
|
# 1. Polygon (closed)
|
|
poly_verts = numpy.array([[0, 0], [10, 0], [10, 10], [0, 10]])
|
|
pat.polygon("1", vertices=poly_verts)
|
|
|
|
# 2. Path (open, 3 points)
|
|
path_verts = numpy.array([[20, 0], [30, 0], [30, 10]])
|
|
pat.path("2", vertices=path_verts, width=2)
|
|
|
|
# 3. Path (open, 2 points) - Testing the fix for 2-point polylines
|
|
path2_verts = numpy.array([[40, 0], [50, 10]])
|
|
pat.path("3", vertices=path2_verts, width=0) # width 0 to be sure it's not a polygonized path if we're not careful
|
|
|
|
# 4. Ref with Grid repetition (Manhattan)
|
|
subpat = Pattern()
|
|
subpat.polygon("sub", vertices=[[0, 0], [1, 0], [1, 1]])
|
|
lib["sub"] = subpat
|
|
|
|
pat.ref("sub", offset=(100, 100), repetition=Grid(a_vector=(10, 0), a_count=2, b_vector=(0, 10), b_count=3))
|
|
|
|
lib["top"] = pat
|
|
|
|
dxf_file = tmp_path / "test.dxf"
|
|
dxf.writefile(lib, "top", dxf_file)
|
|
|
|
read_lib, _ = dxf.readfile(dxf_file)
|
|
|
|
# In DXF read, the top level is usually called "Model"
|
|
top_pat = read_lib.get("Model") or read_lib.get("top") or list(read_lib.values())[0]
|
|
|
|
# Verify Polygon
|
|
polys = [s for s in top_pat.shapes["1"] if isinstance(s, Polygon)]
|
|
assert len(polys) >= 1
|
|
poly_read = polys[0]
|
|
# DXF polyline might be shifted or vertices reordered, but here they should be simple
|
|
assert_allclose(poly_read.vertices, poly_verts)
|
|
|
|
# Verify 3-point Path
|
|
paths = [s for s in top_pat.shapes["2"] if isinstance(s, MPath)]
|
|
assert len(paths) >= 1
|
|
path_read = paths[0]
|
|
assert_allclose(path_read.vertices, path_verts)
|
|
assert path_read.width == 2
|
|
|
|
# Verify 2-point Path
|
|
paths2 = [s for s in top_pat.shapes["3"] if isinstance(s, MPath)]
|
|
assert len(paths2) >= 1
|
|
path2_read = paths2[0]
|
|
assert_allclose(path2_read.vertices, path2_verts)
|
|
assert path2_read.width == 0
|
|
|
|
# Verify Ref with Grid
|
|
# Finding the sub pattern name might be tricky because of how DXF stores blocks
|
|
# but "sub" should be in read_lib
|
|
assert "sub" in read_lib
|
|
|
|
# Check refs in the top pattern
|
|
found_grid = False
|
|
for target, reflist in top_pat.refs.items():
|
|
# DXF names might be case-insensitive or modified, but ezdxf usually preserves them
|
|
if target.upper() == "SUB":
|
|
for ref in reflist:
|
|
if isinstance(ref.repetition, Grid):
|
|
assert ref.repetition.a_count == 2
|
|
assert ref.repetition.b_count == 3
|
|
assert_allclose(ref.repetition.a_vector, (10, 0))
|
|
assert_allclose(ref.repetition.b_vector, (0, 10))
|
|
found_grid = True
|
|
assert found_grid, f"Manhattan Grid repetition should have been preserved. Targets: {list(top_pat.refs.keys())}"
|
|
|
|
def test_dxf_manhattan_precision(tmp_path: Path):
|
|
# Test that float precision doesn't break Manhattan grid detection
|
|
lib = Library()
|
|
sub = Pattern()
|
|
sub.polygon("1", vertices=[[0, 0], [1, 0], [1, 1]])
|
|
lib["sub"] = sub
|
|
|
|
top = Pattern()
|
|
# 90 degree rotation: in masque the grid is NOT rotated, so it stays [[10,0],[0,10]]
|
|
# In DXF, an array with rotation 90 has basis vectors [[0,10],[-10,0]].
|
|
# So a masque grid [[10,0],[0,10]] with ref rotation 90 matches a DXF array.
|
|
angle = numpy.pi / 2 # 90 degrees
|
|
top.ref("sub", offset=(0, 0), rotation=angle,
|
|
repetition=Grid(a_vector=(10, 0), a_count=2, b_vector=(0, 10), b_count=2))
|
|
|
|
lib["top"] = top
|
|
|
|
dxf_file = tmp_path / "precision.dxf"
|
|
dxf.writefile(lib, "top", dxf_file)
|
|
|
|
# If the isclose() fix works, this should still be a Grid when read back
|
|
read_lib, _ = dxf.readfile(dxf_file)
|
|
read_top = read_lib.get("Model") or read_lib.get("top") or list(read_lib.values())[0]
|
|
|
|
target_name = next(k for k in read_top.refs if k.upper() == "SUB")
|
|
ref = read_top.refs[target_name][0]
|
|
assert isinstance(ref.repetition, Grid), "Grid should be preserved for 90-degree rotation"
|
|
|
|
|
|
def test_dxf_rotated_grid_roundtrip_preserves_basis_and_counts(tmp_path: Path):
|
|
lib = Library()
|
|
sub = Pattern()
|
|
sub.polygon("1", vertices=[[0, 0], [1, 0], [1, 1]])
|
|
lib["sub"] = sub
|
|
|
|
top = Pattern()
|
|
top.ref(
|
|
"sub",
|
|
offset=(0, 0),
|
|
rotation=numpy.pi / 2,
|
|
repetition=Grid(a_vector=(10, 0), a_count=3, b_vector=(0, 20), b_count=2),
|
|
)
|
|
lib["top"] = top
|
|
|
|
dxf_file = tmp_path / "rotated_grid.dxf"
|
|
dxf.writefile(lib, "top", dxf_file)
|
|
|
|
read_lib, _ = dxf.readfile(dxf_file)
|
|
read_top = read_lib.get("Model") or read_lib.get("top") or list(read_lib.values())[0]
|
|
|
|
target_name = next(k for k in read_top.refs if k.upper() == "SUB")
|
|
ref = read_top.refs[target_name][0]
|
|
assert isinstance(ref.repetition, Grid)
|
|
actual = ref.repetition.displacements
|
|
expected = Grid(a_vector=(10, 0), a_count=3, b_vector=(0, 20), b_count=2).displacements
|
|
assert_allclose(
|
|
actual[numpy.lexsort((actual[:, 1], actual[:, 0]))],
|
|
expected[numpy.lexsort((expected[:, 1], expected[:, 0]))],
|
|
)
|
|
|
|
|
|
def test_dxf_read_legacy_polyline() -> None:
|
|
doc = ezdxf.new()
|
|
msp = doc.modelspace()
|
|
msp.add_polyline2d([(0, 0), (10, 0), (10, 10)], dxfattribs={"layer": "legacy"}).close(True)
|
|
|
|
stream = io.StringIO()
|
|
doc.write(stream)
|
|
stream.seek(0)
|
|
|
|
read_lib, _ = dxf.read(stream)
|
|
top_pat = read_lib.get("Model") or list(read_lib.values())[0]
|
|
|
|
polys = [shape for shape in top_pat.shapes["legacy"] if isinstance(shape, Polygon)]
|
|
assert len(polys) == 1
|
|
assert_allclose(polys[0].vertices, [[0, 0], [10, 0], [10, 10]])
|