def test_wcs_mirror_transformations_of_clockwise_oriented_curves(sx, sy, kind): hatch = Hatch() edge_path = hatch.paths.add_edge_path() # A closed loop is required to get a path! edge_path.add_line((15, 5), (5, 5)) if kind == "arc": edge_path.add_arc((10, 5), 5, start_angle=0, end_angle=180, ccw=False) elif kind == "ellipse": edge_path.add_ellipse( (10, 5), (5, 0), ratio=0.7, start_angle=0, end_angle=180, ccw=False ) else: pytest.fail(f"unknown kind: {kind}") src_path = make_path(hatch) assert len(src_path) > 1, "expected non empty path" m = Matrix44.scale(sx, sy, 1) transformed_hatch = transformed_copy(hatch, m) expected_path = src_path.transform(m) path_of_transformed_hatch = make_path(transformed_hatch) assert ( have_close_control_vertices(path_of_transformed_hatch, expected_path) is True )
def make_hatches_from_str(s: str, font: fonts.FontFace, size: float = 1.0, align: str = 'LEFT', length: float = 0, segments: int = 4, dxfattribs: Dict = None, m: Matrix44 = None) -> List[Hatch]: """ Convert a single line string `s` into a list of virtual :class:`~ezdxf.entities.Hatch` entities. The text `size` is the height of the uppercase letter "X" (cap height). The paths are aligned about the insertion point at (0, 0). The HATCH entities are aligned to this insertion point. BASELINE means the bottom of the letter "X". Args: s: text to convert font: font face definition size: text size (cap height) in drawing units align: alignment as string, default is "LEFT" length: target length for the "ALIGNED" and "FIT" alignments segments: minimal segment count per Bézier curve dxfattribs: additional DXF attributes m: transformation :class:`~ezdxf.math.Matrix44` """ font_properties, font_measurements = _get_font_data(font) # scale cap_height for 1 drawing unit! scaled_size = size / font_measurements.cap_height scaled_fm = font_measurements.scale_from_baseline(scaled_size) paths = _str_to_paths(s, font_properties, scaled_size) # HATCH is an OCS entity, transforming just the polyline paths # is not correct! The Hatch has to be created in the xy-plane! hatches = [] dxfattribs = dxfattribs or dict() dxfattribs.setdefault('solid_fill', 1) dxfattribs.setdefault('pattern_name', 'SOLID') dxfattribs.setdefault('color', 7) for contour, holes in group_contour_and_holes(paths): hatch = Hatch.new(dxfattribs=dxfattribs) # Vec2 removes the z-axis, which would be interpreted as bulge value! hatch.paths.add_polyline_path( Vec2.generate(contour.flattening(1, segments=segments)), flags=1) for hole in holes: hatch.paths.add_polyline_path( Vec2.generate(hole.flattening(1, segments=segments)), flags=0) hatches.append(hatch) halign, valign = const.TEXT_ALIGN_FLAGS[align.upper()] bbox = path.bbox(paths, precise=False) matrix = get_alignment_transformation(scaled_fm, bbox, halign, valign) if m is not None: matrix *= m # Transform HATCH entities as a unit: return [hatch.transform(matrix) for hatch in hatches]
def test_polyline_path_transform_interface(m44): hatch = Hatch.new() vertices = list(box(1.0, 2.0)) path = hatch.paths.add_polyline_path(vertices) hatch.transform(m44) chk = m44.transform_vertices(vertices) for v, c in zip(path.vertices, chk): assert c.isclose(v)
def closed_edge_hatch(request): _hatch = Hatch.new() _path = _hatch.paths.add_edge_path() if request.param == "arc": _path.add_arc((0, 0), radius=1, start_angle=0, end_angle=360, ccw=1) elif request.param == "ellipse": _path.add_ellipse( (0, 0), major_axis=(5, 0), ratio=0.2, start_angle=0, end_angle=360 ) return _hatch
def from_hatch(hatch: Hatch) -> Iterable[Path]: """ Yield all HATCH boundary paths as separated :class:`Path` objects. .. versionadded:: 0.16 """ ocs = hatch.ocs() elevation = hatch.dxf.elevation.z for boundary in hatch.paths: yield from_hatch_boundary_path(boundary, ocs, elevation)
def _hatch_as_polygon(hatch: Hatch, distance: float, force_line_string: bool) -> Dict: def boundary_to_vertices(boundary) -> List[Vector]: path = Path.from_hatch_boundary_path(boundary, ocs, elevation) return path_to_vertices(path) def path_to_vertices(path) -> List[Vector]: path.close() return list(path.flattening(distance)) # Path vertex winding order can be ignored here, validation and # correction is done in polygon_mapping(). elevation = hatch.dxf.elevation.z ocs = hatch.ocs() hatch_style = hatch.dxf.hatch_style # Returns boundaries in EXTERNAL, OUTERMOST and DEFAULT order and filters # unused boundaries according the hatch style: boundaries = list(hatch.paths.rendering_paths(hatch_style)) count = len(boundaries) if count == 0: raise ValueError('HATCH without any boundary path.') # Take first path as exterior path, multiple EXTERNAL paths are possible exterior = boundaries[0] if count == 1 or hatch_style == const.HATCH_STYLE_IGNORE: points = boundary_to_vertices(exterior) return _line_string_or_polygon_mapping(points, force_line_string) else: if force_line_string: # Build a MultiString collection: points = boundary_to_vertices(exterior) geometries = [ _line_string_or_polygon_mapping(points, force_line_string) ] # All other boundary paths are treated as holes for hole in boundaries[1:]: points = boundary_to_vertices(hole) geometries.append( _line_string_or_polygon_mapping(points, force_line_string)) return join_multi_single_type_mappings(geometries) else: # Multiple separated polygons are possible in one HATCH entity: polygons = [] for exterior, holes in _boundaries_to_polygons( boundaries, ocs, elevation): points = path_to_vertices(exterior) polygons.append( polygon_mapping(points, [path_to_vertices(hole) for hole in holes])) if len(polygons) > 1: return join_multi_single_type_mappings(polygons) return polygons[0]
def _from_hatch(hatch: Hatch, **kwargs) -> Path: ocs = hatch.ocs() elevation = hatch.dxf.elevation.z offset = NULLVEC if isinstance(hatch, MPolygon): offset = hatch.dxf.get("offset_vector", NULLVEC) paths = [ from_hatch_boundary_path(boundary, ocs, elevation, offset=offset) for boundary in hatch.paths ] # looses the boundary path state: return tools.to_multi_path(paths)
def test_upright_hatch_with_polyline_path(): hatch = Hatch.new(dxfattribs={ "elevation": (0, 0, 4), "extrusion": (0, 0, -1), }) hatch.paths.add_polyline_path([(x, y, b) for x, y, s, e, b in POLYLINE_POINTS]) p0 = path.make_path(hatch) assert p0.has_curves is True upright(hatch) assert hatch.dxf.extrusion.isclose(Z_AXIS) p1 = path.make_path(hatch) assert path.have_close_control_vertices(p0, p1)
def test_edge_path_transform_interface(m44): hatch = Hatch.new() path = hatch.paths.add_edge_path() path.add_line((0, 0), (10, 0)) path.add_arc((10, 5), radius=5, start_angle=270, end_angle=450, ccw=1) path.add_ellipse( (5, 10), major_axis=(5, 0), ratio=0.2, start_angle=0, end_angle=180 ) spline = path.add_spline( [(1, 1), (2, 2), (3, 3), (4, 4)], degree=3, periodic=1 ) # the following values do not represent a mathematically valid spline spline.control_points = [(1, 1), (2, 2), (3, 3), (4, 4)] spline.knot_values = [1, 2, 3, 4, 5, 6] spline.weights = [4, 3, 2, 1] spline.start_tangent = (10, 1) spline.end_tangent = (2, 20) chk = list( m44.transform_vertices( [ Vec3(0, 0), Vec3(10, 0), Vec3(10, 5), Vec3(5, 10), Vec3(1, 1), Vec3(2, 2), Vec3(3, 3), Vec3(4, 4), ] ) ) hatch.transform(m44) line = path.edges[0] assert chk[0].isclose(line.start) assert chk[1].isclose(line.end) arc = path.edges[1] assert chk[2].isclose(arc.center) ellipse = path.edges[2] assert chk[3].isclose(ellipse.center) spline = path.edges[3] for c, v in zip(chk[4:], spline.control_points): assert c.isclose(v) for c, v in zip(chk[4:], spline.fit_points): assert c.isclose(v) assert m44.transform_direction((10, 1, 0)).isclose(spline.start_tangent) assert m44.transform_direction((2, 20, 0)).isclose(spline.end_tangent)
def from_hatch(hatch: Hatch) -> Iterable[Path]: """Yield all HATCH boundary paths as separated :class:`Path` objects. .. versionadded:: 0.16 .. versionchanged:: 17.1 Attaches the boundary state to each path as :class:`ezdxf.lldxf.const.BoundaryPathState`. """ ocs = hatch.ocs() elevation = hatch.dxf.elevation.z for boundary in hatch.paths: p = from_hatch_boundary_path(boundary, ocs, elevation) if p.has_sub_paths: yield from p.sub_paths() else: yield p
def test_ocs_mirror_transformations_of_clockwise_oriented_curves( sx, sy, extrusion, kind, ): hatch = Hatch() edge_path = hatch.paths.add_edge_path() if kind == "arc": edge_path.add_arc((7, 0), 5, start_angle=0, end_angle=180, ccw=False) elif kind == "ellipse": edge_path.add_ellipse( (7, 0), (5, 0), ratio=0.7, start_angle=0, end_angle=180, ccw=False ) else: pytest.fail(f"unknown kind: {kind}") transformed_hatch = transformed_copy(hatch, Matrix44.scale(sx, sy, 1)) # This tests the current implementation of OCS transformations! assert transformed_hatch.dxf.extrusion.isclose(extrusion) assert ( transformed_hatch.paths[0].edges[0].ccw is False ), "ccw flag should not change"
def _hatch_converter(paths: Iterable[Path], add_boundary: Callable[[Hatch, Path, int], None], extrusion: 'Vertex' = Z_AXIS, dxfattribs: Optional[Dict] = None) -> Iterable[Hatch]: if isinstance(paths, Path): paths = [paths] else: paths = list(paths) if len(paths) == 0: return [] extrusion = Vec3(extrusion) reference_point = paths[0].start dxfattribs = dxfattribs or dict() if not extrusion.isclose(Z_AXIS): ocs, elevation = _get_ocs(extrusion, reference_point) paths = tools.transform_paths_to_ocs(paths, ocs) dxfattribs['elevation'] = Vec3(0, 0, elevation) dxfattribs['extrusion'] = extrusion elif reference_point.z != 0: dxfattribs['elevation'] = Vec3(0, 0, reference_point.z) dxfattribs.setdefault('solid_fill', 1) dxfattribs.setdefault('pattern_name', 'SOLID') dxfattribs.setdefault('color', const.BYLAYER) for group in group_paths(paths): if len(group) == 0: continue hatch = Hatch.new(dxfattribs=dxfattribs) external = group[0] external.close() add_boundary(hatch, external, 1) for hole in group[1:]: hole.close() add_boundary(hatch, hole, 0) yield hatch
def path_hatch(): return Hatch.from_text(PATH_HATCH)
def hatch(): return Hatch.new()
def hatch(self): return Hatch.new(dxfattribs={ "elevation": (0, 0, 4), "extrusion": (0, 0, -1), })
def edge_hatch(): return Hatch.from_text(EDGE_HATCH)
def spline_edge_hatch(): return Hatch.from_text(EDGE_HATCH_WITH_SPLINE)
def test_check_entity_type(): with pytest.raises(TypeError): text2path.check_entity_type(None) with pytest.raises(TypeError): text2path.check_entity_type(Hatch())
def hatch_pattern(): return Hatch.from_text(HATCH_PATTERN)
def all_edge_types_hatch(elevation, extrusion): hatch = Hatch.new(dxfattribs={ "layer": "original", "color": 2, "elevation": (0.0, 0.0, elevation), "extrusion": extrusion, "pattern_name": "SOLID", "solid_fill": 1, "associative": 0, "hatch_style": 0, "pattern_type": 1, }, ) # edge-path contains all supported edge types: ep = hatch.paths.add_edge_path(flags=1) ep.add_arc( # clockwise oriented ARC center=(0.0, 13.0), radius=3.0, start_angle=-90.0, end_angle=90.0, ccw=False, ) ep.add_ellipse( # clockwise oriented ELLIPSE center=(0.0, 5.0), major_axis=(0.0, 5.0), ratio=0.6, start_angle=180.0, end_angle=360.0, ccw=False, ) ep.add_line((0.0, 0.0), (10.0, 0.0)) # LINE ep.add_ellipse( # counter-clockwise oriented ELLIPSE center=(10.0, 5.0), major_axis=(0.0, -5.0), ratio=0.6, start_angle=0.0, end_angle=180.0, ccw=True, ) ep.add_arc( # counter-clockwise oriented ARC center=(10.0, 13.0), radius=3.0, start_angle=270.0, end_angle=450.0, ccw=True, ) ep.add_spline( # SPLINE control_points=[ Vec2(10.0, 16.0), Vec2(9.028174684192452, 16.0), Vec2(6.824943218065775, 12.14285714285714), Vec2(3.175056781934232, 19.85714285714287), Vec2(0.9718253158075516, 16.0), Vec2(0, 16.0), ], knot_values=[ 0.0, 0.0, 0.0, 0.0, 2.91547594742265, 8.746427842267952, 11.6619037896906, 11.6619037896906, 11.6619037896906, 11.6619037896906, ], degree=3, periodic=0, ) return hatch
def test_full_circle_edge_scaling(): _hatch = Hatch.new() _path = _hatch.paths.add_edge_path() _arc = _path.add_arc((0, 0), radius=1, start_angle=0, end_angle=360, ccw=1) _hatch.transform(Matrix44.scale(0.5, 0.5, 0.5)) assert _arc.radius == pytest.approx(0.5)