[dxf] add roundtrip dxf test, enable refs and improve path handling
This commit is contained in:
parent
e261585894
commit
564ff10db3
2 changed files with 164 additions and 18 deletions
|
|
@ -16,7 +16,7 @@ import gzip
|
|||
import numpy
|
||||
import ezdxf
|
||||
from ezdxf.enums import TextEntityAlignment
|
||||
from ezdxf.entities import LWPolyline, Polyline, Text, Insert
|
||||
from ezdxf.entities import LWPolyline, Polyline, Text, Insert, Solid, Trace
|
||||
|
||||
from .utils import is_gzipped, tmpfile
|
||||
from .. import Pattern, Ref, PatternError, Label
|
||||
|
|
@ -217,9 +217,7 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) ->
|
|||
attr = element.dxfattribs()
|
||||
layer = attr.get('layer', DEFAULT_LAYER)
|
||||
|
||||
if points.shape[1] == 2:
|
||||
raise PatternError('Invalid or unimplemented polygon?')
|
||||
|
||||
width = 0
|
||||
if points.shape[1] > 2:
|
||||
if (points[0, 2] != points[:, 2]).any():
|
||||
raise PatternError('PolyLine has non-constant width (not yet representable in masque!)')
|
||||
|
|
@ -230,14 +228,35 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) ->
|
|||
if width == 0:
|
||||
width = attr.get('const_width', 0)
|
||||
|
||||
shape: Path | Polygon
|
||||
if width == 0 and len(points) > 2 and numpy.array_equal(points[0], points[-1]):
|
||||
shape = Polygon(vertices=points[:-1, :2])
|
||||
else:
|
||||
shape = Path(width=width, vertices=points[:, :2])
|
||||
is_closed = element.closed
|
||||
# If the last point is a repeat of the first, drop it.
|
||||
if len(points) > 1 and numpy.allclose(points[0, :2], points[-1, :2]):
|
||||
verts = points[:-1, :2]
|
||||
else:
|
||||
verts = points[:, :2]
|
||||
|
||||
shape: Path | Polygon
|
||||
if width == 0 and is_closed and len(verts) >= 3:
|
||||
shape = Polygon(vertices=verts)
|
||||
else:
|
||||
shape = Path(width=width, vertices=points[:, :2])
|
||||
|
||||
pat.shapes[layer].append(shape)
|
||||
|
||||
elif isinstance(element, Solid | Trace):
|
||||
attr = element.dxfattribs()
|
||||
layer = attr.get('layer', DEFAULT_LAYER)
|
||||
points = numpy.array([element.get_dxf_attrib(f'vtx{i}') for i in range(4)
|
||||
if element.has_dxf_attrib(f'vtx{i}')])
|
||||
if len(points) >= 3:
|
||||
# If vtx2 == vtx3, it's a triangle. ezdxf handles this.
|
||||
if len(points) == 4 and numpy.allclose(points[2], points[3]):
|
||||
verts = points[:3, :2]
|
||||
# DXF Solid/Trace uses 0-1-3-2 vertex order for quadrilaterals!
|
||||
elif len(points) == 4:
|
||||
verts = points[[0, 1, 3, 2], :2]
|
||||
else:
|
||||
verts = points[:, :2]
|
||||
pat.shapes[layer].append(Polygon(vertices=verts))
|
||||
elif isinstance(element, Text):
|
||||
args = dict(
|
||||
offset=numpy.asarray(element.get_placement()[1])[:2],
|
||||
|
|
@ -302,15 +321,23 @@ def _mrefs_to_drefs(
|
|||
elif isinstance(rep, Grid):
|
||||
a = rep.a_vector
|
||||
b = rep.b_vector if rep.b_vector is not None else numpy.zeros(2)
|
||||
rotated_a = rotation_matrix_2d(-ref.rotation) @ a
|
||||
rotated_b = rotation_matrix_2d(-ref.rotation) @ b
|
||||
if numpy.isclose(rotated_a[1], 0) and numpy.isclose(rotated_b[0], 0):
|
||||
# In masque, the grid basis vectors are NOT rotated by the reference's rotation.
|
||||
# In DXF, the grid basis vectors are [column_spacing, 0] and [0, row_spacing],
|
||||
# which ARE then rotated by the block reference's rotation.
|
||||
# Therefore, we can only use a DXF array if ref.rotation is 0 (or a multiple of 90)
|
||||
# AND the grid is already manhattan.
|
||||
|
||||
# Rotate basis vectors by the reference rotation to see where they end up in the DXF frame
|
||||
rotated_a = rotation_matrix_2d(ref.rotation) @ a
|
||||
rotated_b = rotation_matrix_2d(ref.rotation) @ b
|
||||
|
||||
if numpy.isclose(rotated_a[1], 0, atol=1e-8) and numpy.isclose(rotated_b[0], 0, atol=1e-8):
|
||||
attribs['column_count'] = rep.a_count
|
||||
attribs['row_count'] = rep.b_count
|
||||
attribs['column_spacing'] = rotated_a[0]
|
||||
attribs['row_spacing'] = rotated_b[1]
|
||||
block.add_blockref(encoded_name, ref.offset, dxfattribs=attribs)
|
||||
elif numpy.isclose(rotated_a[0], 0) and numpy.isclose(rotated_b[1], 0):
|
||||
elif numpy.isclose(rotated_a[0], 0, atol=1e-8) and numpy.isclose(rotated_b[1], 0, atol=1e-8):
|
||||
attribs['column_count'] = rep.b_count
|
||||
attribs['row_count'] = rep.a_count
|
||||
attribs['column_spacing'] = rotated_b[0]
|
||||
|
|
@ -348,10 +375,18 @@ def _shapes_to_elements(
|
|||
displacements = shape.repetition.displacements
|
||||
|
||||
for dd in displacements:
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = polygon.vertices + dd
|
||||
xy_closed = numpy.vstack((xy_open, xy_open[0, :]))
|
||||
block.add_lwpolyline(xy_closed, dxfattribs=attribs)
|
||||
if isinstance(shape, Path):
|
||||
# preserve path.
|
||||
# Note: DXF paths don't support endcaps well, so this is still a bit limited.
|
||||
xy = shape.vertices + dd
|
||||
attribs_path = {**attribs}
|
||||
if shape.width > 0:
|
||||
attribs_path['const_width'] = shape.width
|
||||
block.add_lwpolyline(xy, dxfattribs=attribs_path)
|
||||
else:
|
||||
for polygon in shape.to_polygons():
|
||||
xy_open = polygon.vertices + dd
|
||||
block.add_lwpolyline(xy_open, close=True, dxfattribs=attribs)
|
||||
|
||||
|
||||
def _labels_to_texts(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue