def test_polygon_with_holes_to_dxf_entity(): res = cast(Hatch, list(geo.dxf_entities(POLYGON_2))[0]) assert len(res.paths) == 3 p = res.paths[1] assert p.PATH_TYPE == 'PolylinePath' assert p.vertices == Vector.list(HOLE1) p = res.paths[2] assert p.PATH_TYPE == 'PolylinePath' assert p.vertices == Vector.list(HOLE2)
def _parse_polygon(coordinates: Sequence) -> Sequence: """ Returns polygon definition as tuple (exterior, [holes]). """ if _is_coordinate_sequence(coordinates): exterior = coordinates holes = [] else: exterior = coordinates[0] holes = coordinates[1:] return Vector.list(exterior), [Vector.list(h) for h in holes]
def parse(geo_mapping: Dict) -> Dict: """ Parse ``__geo_interface__`` convert all coordinates into :class:`Vector` objects, Polygon['coordinates'] is always a tuple (exterior, holes), holes maybe an empty list. """ geo_mapping = copy.deepcopy(geo_mapping) type_ = geo_mapping.get(TYPE) if type_ is None: raise ValueError(f'Required key "{TYPE}" not found.') if type_ == FEATURE_COLLECTION: # It is possible for this array to be empty. features = geo_mapping.get(FEATURES) if features: geo_mapping[FEATURES] = [parse(f) for f in features] else: raise ValueError(f'Missing key "{FEATURES}" in FeatureCollection.') elif type_ == GEOMETRY_COLLECTION: # It is possible for this array to be empty. geometries = geo_mapping.get(GEOMETRIES) if geometries: geo_mapping[GEOMETRIES] = [parse(g) for g in geometries] else: raise ValueError( f'Missing key "{GEOMETRIES}" in GeometryCollection.') elif type_ == FEATURE: # The value of the geometry member SHALL be either a Geometry object # or, in the case that the Feature is unlocated, a JSON null value. if GEOMETRY in geo_mapping: geometry = geo_mapping.get(GEOMETRY) geo_mapping[GEOMETRY] = parse(geometry) if geometry else None else: raise ValueError(f'Missing key "{GEOMETRY}" in Feature.') elif type_ in { POINT, LINE_STRING, POLYGON, MULTI_POINT, MULTI_LINE_STRING, MULTI_POLYGON }: coordinates = geo_mapping.get(COORDINATES) if coordinates is None: raise ValueError(f'Missing key "{COORDINATES}" in {type_}.') if type_ == POINT: coordinates = Vector(coordinates) elif type_ in (LINE_STRING, MULTI_POINT): coordinates = Vector.list(coordinates) elif type_ == POLYGON: coordinates = _parse_polygon(coordinates) elif type_ == MULTI_LINE_STRING: coordinates = [Vector.list(v) for v in coordinates] elif type_ == MULTI_POLYGON: coordinates = [_parse_polygon(v) for v in coordinates] geo_mapping[COORDINATES] = coordinates else: raise TypeError(f'Invalid type "{type_}".') return geo_mapping
def test_polygon_without_holes_to_dxf_entity(): res = cast(Hatch, list(geo.dxf_entities(POLYGON_0))[0]) assert res.dxftype() == 'HATCH' assert len(res.paths) == 1 p = res.paths[0] assert p.PATH_TYPE == 'PolylinePath' assert p.vertices == Vector.list(EXTERIOR)
def spline_insert_knot(): dwg = ezdxf.new('R2000', setup=True) msp = dwg.modelspace() def add_spline(control_points, color=3, knots=None): msp.add_polyline2d(control_points, dxfattribs={ 'color': color, 'linetype': 'DASHED' }) msp.add_open_spline(control_points, degree=3, knots=knots, dxfattribs={'color': color}) control_points = Vector.list([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70)]) add_spline(control_points, color=3, knots=None) bspline = BSpline(control_points, order=4) bspline.insert_knot(bspline.max_t / 2) add_spline(bspline.control_points, color=4, knots=bspline.knot_values()) if dwg.validate(): dwg.saveas("Spline_R2000_spline_insert_knot.dxf")
def add_mesh(self, vertices: List[Vector] = None, faces: List[Sequence[int]] = None, edges: List[Tuple[int, int]] = None, mesh=None) -> None: """ Add another mesh to this mesh. A `mesh` can be a :class:`MeshBuilder`, :class:`MeshVertexMerger` or :class:`~ezdxf.entities.Mesh` object or requires the attributes :attr:`vertices`, :attr:`edges` and :attr:`faces`. Args: vertices: list of vertices, a vertex is a ``(x, y, z)`` tuple or :class:`~ezdxf.math.Vector` object faces: list of faces, a face is a list of vertex indices edges: list of edges, an edge is a list of vertex indices mesh: another mesh entity """ if mesh is not None: vertices = Vector.list(mesh.vertices) faces = mesh.faces edges = mesh.edges if vertices is None: raise ValueError("Requires vertices or another mesh.") faces = faces or [] edges = edges or [] indices = self.add_vertices(vertices) for v1, v2 in edges: self.edges.append((indices[v1], indices[v2])) for face_vertices in faces: self.faces.append(tuple(indices[vi] for vi in face_vertices))
def test_control_vertices(p1): vertices = list(p1.control_vertices()) assert vertices == Vector.list([(0, 0), (2, 0), (2, 1), (4, 1), (4, 0)]) path = Path() assert len(list(path.control_vertices())) == 0 path = Path.from_vertices([(0, 0), (1, 0)]) assert len(list(path.control_vertices())) == 2
def test_knot_generation(p, method): fit_points = Vector.list([(0, 0), (0, 10), (10, 10), (20, 10), (20, 0), (30, 0), (30, 10), (40, 10), (40, 0)]) count = len(fit_points) n = count - 1 order = p + 1 t_vector = distance_t_vector(fit_points) knots = list(knots_from_parametrization(n, p, t_vector, method)) check_knots(n + 1, p + 1, knots)
def from_vertices(cls, vertices: Iterable['Vertex'], close=False) -> 'Path': """ Returns a :class:`Path` from vertices. """ vertices = Vector.list(vertices) path = cls(start=vertices[0]) for vertex in vertices[1:]: path.line_to(vertex) if close: path.close() return path
def cubic_bezier_interpolation( points: Iterable['Vertex']) -> Iterable[Bezier4P]: """ Returns an interpolation curve for given data `points` as multiple cubic Bézier-curves. Returns n-1 cubic Bézier-curves for n given data points, curve i goes from point[i] to point[i+1]. Args: points: data points .. versionadded:: 0.13 """ # Source: https://towardsdatascience.com/b%C3%A9zier-interpolation-8033e9a262c2 points = Vector.list(points) if len(points) < 3: raise ValueError('At least 3 points required.') num = len(points) - 1 # setup tri-diagonal matrix (a, b, c) b = [4.0] * num a = [1.0] * num c = [1.0] * num b[0] = 2.0 b[num - 1] = 7.0 a[num - 1] = 2.0 # setup right-hand side quantities points_vector = [points[0] + 2.0 * points[1]] points_vector.extend(2.0 * (2.0 * points[i] + points[i + 1]) for i in range(1, num - 1)) points_vector.append(8.0 * points[num - 1] + points[num]) # solve tri-diagonal linear equation system solution = tridiagonal_matrix_solver((a, b, c), points_vector) control_points_1 = Vector.list(solution.rows()) control_points_2 = [ p * 2.0 - cp for p, cp in zip(points[1:], control_points_1[1:]) ] control_points_2.append((control_points_1[num - 1] + points[num]) / 2.0) for defpoints in zip(points, control_points_1, control_points_2, points[1:]): yield Bezier4P(defpoints)
def test_arc_distances(): p = Vector.list([(0, 0), (2, 2), (4, 0), (6, -2), (8, 0)]) # p[1]..p[3] are a straight line, radius calculation fails and # a straight line from p[1] to p[2] is used as replacement # for the second arc radius = 2.0 arc_length = math.pi * 0.5 * radius diagonal = math.sqrt(2.0) * radius distances = list(arc_distances(p)) assert len(distances) == 4 assert isclose(distances[0], arc_length) assert isclose(distances[1], diagonal) # replacement for arc assert isclose(distances[2], arc_length) assert isclose(distances[3], arc_length)
def extend(self, vertices: Iterable['Vertex']) -> None: """ Append multiple vertices to the reference line. It is possible to work with 3D vertices, but all vertices have to be in the same plane and the normal vector of this plan is stored as extrusion vector in the MLINE entity. """ vertices = Vector.list(vertices) if not vertices: return all_vertices = [] if len(self): all_vertices.extend(self.get_locations()) all_vertices.extend(vertices) self.generate_geometry(all_vertices)
def test_basis_vector_N_ip(): degree = 3 fit_points = Vector.list(POINTS2) # data points D n = len(fit_points) - 1 t_vector = list(uniform_t_vector(fit_points)) knots = list(control_frame_knots(n, degree, t_vector)) should_count = len(fit_points) - 2 # target control point count h = should_count - 1 spline = Basis(knots, order=degree + 1, count=len(fit_points)) matrix_N = [spline.basis(t) for t in t_vector] for k in range(1, n): basis_vector = bspline_basis_vector(u=t_vector[k], count=len(fit_points), degree=degree, knots=knots) for i in range(1, h): assert isclose(matrix_N[k][i], basis_vector[i])
def spline_control_frame_approximation(): doc = new('R2000', setup=True) fit_points = Vector.list([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70), (65, 75)]) msp = doc.modelspace() msp.add_polyline2d(fit_points, dxfattribs={'color': 2, 'linetype': 'DOT2'}) spline = bspline_control_frame_approx(fit_points, count=7, degree=3, method='uniform') msp.add_polyline2d(spline.control_points, dxfattribs={ 'color': 3, 'linetype': 'DASHED' }) msp.add_open_spline(spline.control_points, degree=spline.degree, dxfattribs={'color': 3}) msp.add_spline(fit_points, degree=3, dxfattribs={'color': 1}) doc.saveas("Spline_R2000_spline_control_frame_approximation.dxf")
# Copyright (c) 2020, Manfred Moitzi # License: MIT License from pathlib import Path import math import ezdxf from ezdxf.math import (Vector, estimate_tangents, estimate_end_tangent_magnitude, global_bspline_interpolation, linspace) DIR = Path('~/Desktop/Outbox').expanduser() points = Vector.list([(0, 0), (0, 10), (10, 10), (20, 10), (20, 0)]) def sine_wave(count: int, scale: float = 1.0): for t in linspace(0, math.tau, count): yield Vector(t * scale, math.sin(t) * scale) def setup(): doc = ezdxf.new() msp = doc.modelspace() msp.add_lwpolyline(points, dxfattribs={'color': 5, 'layer': 'frame'}) for p in points: msp.add_circle(p, radius=0.1, dxfattribs={ 'color': 1, 'layer': 'frame' }) doc.set_modelspace_vport(20, center=(10, 5)) return doc, msp
def fit_points_2(): return Vector.list([(0, 0), (0, 10), (10, 10), (20, 10), (20, 0), (30, 0), (30, 10), (40, 10), (40, 0)])
def test_local_cubic_bspline_interpolation_from_tangents(): points = Vector.list(POINTS1) tangents = estimate_tangents(points) control_points, knots = local_cubic_bspline_interpolation_from_tangents(points, tangents) assert len(control_points) == 8 assert len(knots) == 8 + 4 # count + order
def test_line_string_to_dxf_entity(): res = cast(LWPolyline, list(geo.dxf_entities(LINE_STRING))[0]) assert res.dxftype() == 'LWPOLYLINE' assert list(res.vertices()) == Vector.list(EXTERIOR)
next_frame = Matrix44.translate(0, 5, 0) right_frame = Matrix44.translate(10, 0, 0) NAME = 'spline.dxf' dwg = ezdxf.new('R2000') msp = dwg.modelspace() def draw(points): for point in points: msp.add_circle(radius=0.1, center=point, dxfattribs={'color': 1}) spline_points = Vector.list([(1., 1.), (2.5, 3.), (4.5, 2.), (6.5, 4.)]) # fit points draw(spline_points) Spline(spline_points).render_as_fit_points(msp, method='distance', dxfattribs={'color': 2}) # curve with definition points as fit points Spline(spline_points).render_as_fit_points(msp, method='uniform', dxfattribs={'color': 3}) Spline(spline_points).render_as_fit_points(msp, method='centripetal', dxfattribs={'color': 4}) # distance ^ 1/2 Spline(spline_points).render_as_fit_points(msp, method='centripetal', power=1./3., dxfattribs={'color': 6}) # distance ^ 1/3 msp.add_spline(fit_points=spline_points, dxfattribs={'color': 1}) msp.add_text("Spline.render_as_fit_points() differs from AutoCAD fit point rendering", dxfattribs={'height': .1}).set_pos(spline_points[0]) # open uniform b-spline spline_points = next_frame.transform_vectors(spline_points) draw(spline_points) msp.add_text("Spline.render_open_bspline() matches AutoCAD", dxfattribs={'height': .1}).set_pos(spline_points[0])
def virtual_leader_entities(leader: 'Leader') -> Iterable['DXFGraphic']: # Source: https://atlight.github.io/formats/dxf-leader.html # GDAL: DXF LEADER implementation: # https://github.com/OSGeo/gdal/blob/master/gdal/ogr/ogrsf_frmts/dxf/ogrdxf_leader.cpp # LEADER DXF Reference: # http://help.autodesk.com/view/OARX/2018/ENU/?guid=GUID-396B2369-F89F-47D7-8223-8B7FB794F9F3 from ezdxf.entities import DimStyleOverride assert leader.dxftype() == 'LEADER' vertices = Vector.list(leader.vertices) # WCS if len(vertices) < 2: # This LEADER entities should be removed by the auditor if loaded or # ignored at exporting, if created by an ezdxf-user (log). raise ValueError('More than 1 vertex required.') dxf = leader.dxf doc = leader.doc # Some default values depend on the measurement system # 0/1 = imperial/metric if doc: measurement = doc.header.get('$MEASUREMENT', 0) else: measurement = 0 # Set default styling attributes values: dimtad = 1 dimgap = 0.625 if measurement else 0.0625 dimscale = 1.0 dimclrd = dxf.color dimltype = dxf.linetype dimlwd = dxf.lineweight override = None if doc: # get styling attributes from associated DIMSTYLE and/or XDATA override override = DimStyleOverride(cast('Dimension', leader)) dimtad = override.get('dimtad', dimtad) dimgap = override.get('dimgap', dimgap) dimscale = override.get('dimscale', dimscale) if dimscale == 0.0: # special but unknown meaning dimscale = 1.0 dimclrd = override.get('dimclrd', dimclrd) dimltype = override.get('dimltype', dimltype) dimlwd = override.get('dimlwd', dimlwd) text_width = dxf.text_width hook_line_vector = Vector(dxf.horizontal_direction) has_text_annotation = dxf.annotation_type == 0 if has_text_annotation and dxf.has_hookline: if dxf.hookline_direction == 1: hook_line_vector = -hook_line_vector if dimtad != 0 and text_width > 0: vertices.append(vertices[-1] + hook_line_vector * (dimgap * dimscale + text_width)) dxfattribs = leader.graphic_properties() dxfattribs['color'] = dimclrd dxfattribs['linetype'] = dimltype dxfattribs['lineweight'] = dimlwd if dxfattribs.get('color') == BYBLOCK: dxfattribs['color'] = dxf.block_color if dxf.path_type == 1: # Spline start_tangent = (vertices[1] - vertices[0]) end_tangent = (vertices[-1] - vertices[-2]) bspline = fit_points_to_cad_cv(vertices, degree=3, tangents=[start_tangent, end_tangent]) # noinspection PyUnresolvedReferences spline = factory.new('SPLINE', doc=doc).apply_construction_tool(bspline) yield spline else: attribs = dict(dxfattribs) prev = vertices[0] for vertex in vertices[1:]: attribs['start'] = prev attribs['end'] = vertex yield factory.new(dxftype='LINE', dxfattribs=attribs, doc=doc) prev = vertex if dxf.has_arrowhead and override: arrow_name = override.get('dimldrblk', '') if arrow_name is None: return size = override.get('dimasz', 2.5 if measurement else 0.1875) * dimscale rotation = (vertices[0] - vertices[1]).angle_deg if doc and arrow_name in doc.blocks: dxfattribs.update({ 'name': arrow_name, 'insert': vertices[0], 'rotation': rotation, 'xscale': size, 'yscale': size, 'zscale': size, }) # create a virtual block reference insert = factory.new('INSERT', dxfattribs=dxfattribs, doc=doc) yield from insert.virtual_entities() else: # render standard arrows yield from ARROWS.virtual_entities( name=arrow_name, insert=vertices[0], size=size, rotation=rotation, dxfattribs=dxfattribs, )
def test_subdivide_triangle(): t = Vector.list([(0, 0), (2, 0), (1, 2)]) assert len(list(subdivide_face(t, quads=True))) == 3 assert len(list(subdivide_face(t, quads=False))) == 6
def test_polygon_mapping_vertex_count_error(points): with pytest.raises(ValueError): geo.polygon_mapping(Vector.list(points), [])
from ezdxf.math import Vector from ezdxf import units for unit in (1, 6): unit_name = units.decode(unit) doc = ezdxf.new('R2000', setup=True, units=unit) msp = doc.modelspace() for y, ltype in enumerate(standards.linetypes()): msp.add_line((0, y), (20, y), dxfattribs={'linetype': ltype[0]}) doc.set_modelspace_vport(25, center=(10, 10)) doc.saveas(f'linetypes_{unit_name}_lines.dxf') doc = ezdxf.new('R2000', setup=True, units=unit) msp = doc.modelspace() for r, ltype in enumerate(standards.linetypes()): msp.add_circle((0, 0), radius=4 + r, dxfattribs={'linetype': ltype[0]}) doc.set_modelspace_vport(50) doc.saveas(f'linetypes_{unit_name}_circle.dxf') doc = ezdxf.new('R2000', setup=True, units=unit) msp = doc.modelspace() points = Vector.list([(0, 0), (4, 9), (6, 9), (11, 0), (16, 9)]) for y, ltype in enumerate(standards.linetypes()): fitpoints = [p + (0, y) for p in points] msp.add_spline(fitpoints, dxfattribs={'linetype': ltype[0]}) doc.set_modelspace_vport(35, center=(8, 12)) doc.saveas(f'linetypes_{unit_name}_spline.dxf')
msp = doc.modelspace() def draw(points, extrusion=None): dxfattribs = {'color': 1} if extrusion is not None: ocs = OCS(extrusion) points = ocs.points_from_wcs(points) dxfattribs['extrusion'] = extrusion for point in points: msp.add_circle(radius=0.1, center=point, dxfattribs=dxfattribs) spline_points = Vector.list([ (8.55, 2.96), (8.55, -.03), (2.75, -.03), (2.76, 3.05), (4.29, 1.78), (6.79, 3.05) ]) # open quadratic b-spline draw(spline_points) msp.add_text("Open Quadratic R12Spline", dxfattribs={'height': .1}).set_pos( spline_points[0]) R12Spline(spline_points, degree=2, closed=False).render( msp, segments=SEGMENTS, dxfattribs={'color': 3}) if doc.dxfversion > 'AC1009': msp.add_open_spline(control_points=spline_points, degree=2, dxfattribs={'color': 4}) # open cubic b-spline spline_points = list(next_frame.transform_vertices(spline_points)) draw(spline_points)
# Copyright (c) 2020, Manfred Moitzi # License: MIT License import pytest from ezdxf.math import is_planar_face, Vector, Vec2, subdivide_face, intersection_ray_ray_3d, normal_vector_3p from ezdxf.math import X_AXIS, Y_AXIS, Z_AXIS, subdivide_ngons from ezdxf.render.forms import square, circle REGULAR_FACE = Vector.list([(0, 0, 0), (1, 0, 1), (1, 1, 1), (0, 1, 0)]) IRREGULAR_FACE = Vector.list([(0, 0, 0), (1, 0, 1), (1, 1, 0), (0, 1, 0)]) REGULAR_FACE_WRONG_ORDER = Vector.list([(0, 0, 0), (1, 1, 1), (1, 0, 1), (0, 1, 0)]) def test_face_count(): assert is_planar_face(REGULAR_FACE[:3]) is True assert is_planar_face(REGULAR_FACE[:2]) is False def test_regular_face(): assert is_planar_face(REGULAR_FACE) is True def test_irregular_face(): assert is_planar_face(IRREGULAR_FACE) is False def test_does_not_detect_wrong_order(): assert is_planar_face(REGULAR_FACE_WRONG_ORDER) is True
mesh.add_mesh(vertices=vertices, faces=faces) assert len(mesh.vertices) == 514 assert len(mesh.faces) == 1024 def test_average_vertex_merger(): pyramid = SierpinskyPyramid(level=4, sides=3) faces = pyramid.faces() mesh = MeshAverageVertexMerger() for vertices in pyramid: mesh.add_mesh(vertices=vertices, faces=faces) assert len(mesh.vertices) == 514 assert len(mesh.faces) == 1024 REGULAR_FACE = Vector.list([(0, 0, 0), (1, 0, 1), (1, 1, 1), (0, 1, 0)]) IRREGULAR_FACE = Vector.list([(0, 0, 0), (1, 0, 1), (1, 1, 0), (0, 1, 0)]) def test_has_none_planar_faces(): mesh = MeshBuilder() mesh.add_face(REGULAR_FACE) assert mesh.has_none_planar_faces() is False mesh.add_face(IRREGULAR_FACE) assert mesh.has_none_planar_faces() is True def test_scale_mesh(): mesh = cube(center=False) mesh.scale(2, 3, 4) bbox = BoundingBox(mesh.vertices)
import math from ezdxf.math import Vector from ezdxf.math.bspline import global_bspline_interpolation from ezdxf.math.parametrize import uniform_t_vector, distance_t_vector, centripetal_t_vector, arc_t_vector, \ arc_distances, estimate_tangents from ezdxf.math.bspline import ( knots_from_parametrization, required_knot_values, averaged_knots_unconstrained, natural_knots_constrained, averaged_knots_constrained, natural_knots_unconstrained, double_knots, ) POINTS1 = Vector.list([(1, 1), (2, 4), (4, 1), (7, 6)]) POINTS2 = Vector.list([(1, 1), (2, 4), (4, 1), (7, 6), (5, 8), (3, 3), (1, 7)]) @pytest.fixture(params=[POINTS1, POINTS2]) def fit_points(request): return request.param def test_uniform_t_array(fit_points): t_vector = list(uniform_t_vector(len(fit_points))) assert len(t_vector) == len(fit_points) assert t_vector[0] == 0. assert t_vector[-1] == 1. for t1, t2 in zip(t_vector, t_vector[1:]): assert t1 <= t2
def test_estimate_tangents_5p(): tangents = estimate_tangents(Vector.list(POINTS1), method='5-points') assert len(tangents) == 4