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 build_edge_path( boundaries: BoundaryPaths, path: Path, flags: int, distance: float, segments: int, g1_tol: float, ): if path.has_curves: # Edge path with LINE and SPLINE edges edge_path = boundaries.add_edge_path(flags) for edge in to_bsplines_and_vertices(path, g1_tol=g1_tol): if isinstance(edge, BSpline): edge_path.add_spline( control_points=edge.control_points, degree=edge.degree, knot_values=edge.knots(), ) else: # add LINE edges prev = edge[0] for p in edge[1:]: edge_path.add_line(prev, p) prev = p else: # Polyline boundary path boundaries.add_polyline_path(Vec2.generate( path.flattening(distance, segments)), flags=flags)
def extend(self, vertices: Iterable) -> None: """ Append multiple `vertices`. Args: vertices: iterable of vertices as :class:`Vec2` compatible objects """ self.vertices.extend(Vec2.generate(vertices))
def transform_2d(vertices: Iterable[Vertex], insert: Vertex = Vec3(0, 0, 0), shift: Tuple[float, float] = (0, 0), rotation: float = 0, scale: Tuple[float, float] = (1, 1), oblique: float = 0) -> List[Vec3]: """ Transform any vertices from the text line located at the base location at (0, 0) and alignment "LEFT". Args: vertices: iterable of vertices insert: insertion location shift: (shift-x, shift-y) as 2-tuple of float rotation: text rotation in radians scale: (scale-x, scale-y) as 2-tuple of float oblique: shear angle (slanting) in x-direction in radians """ # Building a transformation matrix vs. applying transformations in # individual steps: # Most text is horizontal, because people like to read horizontal text! # Operating in 2D is faster than building a full 3D transformation # matrix and a pure 2D transformation matrix is not implemented! # This function doesn't transform many vertices at the same time, # mostly only 4 vertices, therefore the matrix multiplication overhead # does not pay off. # The most expensive rotation transformation is the least frequently # used transformation. # IMPORTANT: this assumptions are not verified by profiling! # Use 2D vectors: vertices = Vec2.generate(vertices) # 1. slanting at the original location (very rare): if oblique: slant_x = math.tan(oblique) vertices = (Vec2(v.x + v.y * slant_x, v.y) for v in vertices) # 2. apply alignment shifting (frequently): shift_vector = Vec2(shift) if shift_vector: vertices = (v + shift_vector for v in vertices) # 3. scale (and mirror) at the aligned location (more often): scale_x, scale_y = scale if scale_x != 1 or scale_y != 1: vertices = (Vec2(v.x * scale_x, v.y * scale_y) for v in vertices) # 4. apply rotation (rare): if rotation: vertices = (v.rotate(rotation) for v in vertices) # 5. move to insert location in OCS/3D! (every time) insert = Vec3(insert) return [insert + v for v in vertices]
def build_poly_path( boundaries: BoundaryPaths, path: Path, flags: int, distance: float, segments: int, ): boundaries.add_polyline_path( # Vec2 removes the z-axis, which would be interpreted as bulge value! Vec2.generate(path.flattening(distance, segments)), flags=flags, )
def convex_hull_2d(points: Iterable['Vertex']) -> List['Vertex']: """ Returns 2D convex hull for `points`. Args: points: iterable of points as :class:`Vec3` compatible objects, z-axis is ignored """ def _convexhull(hull): while len(hull) > 2: # the last three points start_point, check_point, destination_point = hull[-3:] # curve not turns right if not is_point_left_of_line(check_point, start_point, destination_point): # remove the penultimate point del hull[-2] else: break return hull points = sorted(set(Vec2.generate(points))) # remove duplicate points if len(points) < 3: raise ValueError( "Convex hull calculation requires 3 or more unique points.") upper_hull = points[:2] # first two points for next_point in points[2:]: upper_hull.append(next_point) upper_hull = _convexhull(upper_hull) lower_hull = [points[-1], points[-2]] # last two points for next_point in reversed(points[:-2]): lower_hull.append(next_point) lower_hull = _convexhull(lower_hull) upper_hull.extend(lower_hull[1:-1]) return upper_hull
def test_2d_tangent_computation(): dbcurve = Bezier4P(DEFPOINTS2D) for index, chk in enumerate(Vec2.generate(TANGENTS2D)): assert dbcurve.tangent(index * .1).isclose(chk)
def test_accepts_2d_points(): curve = Bezier4P(DEFPOINTS2D) for index, chk in enumerate(Vec2.generate(POINTS2D)): assert curve.point(index * .1).isclose(chk)
def test_first_derivative(bezier): dbcurve = bezier(DEFPOINTS2D) for index, chk in enumerate(Vec2.generate(TANGENTS2D)): assert dbcurve.tangent(index * .1).isclose(chk)
def build_poly_path(hatch: Hatch, path: Path, flags: int): hatch.paths.add_polyline_path( # Vec2 removes the z-axis, which would be interpreted as bulge value! Vec2.generate(path.flattening(distance, segments)), flags=flags)