-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
339 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ Add-ons | |
iterdxf | ||
r12writer | ||
odafc | ||
text2path | ||
pycsg | ||
acadctb | ||
forms | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
.. module:: ezdxf.addons.text2path | ||
|
||
text2path | ||
========= | ||
|
||
.. versionadded:: 0.16 | ||
|
||
Tools to convert text strings and text based DXF entities into outer- and inner | ||
linear paths as :class:`~ezdxf.render.path.Path` objects. These tools depend on | ||
the optional `matplotlib`_ package. | ||
|
||
.. warning:: | ||
|
||
Conversion of TEXT entities into hatches does not work for spatial text not | ||
located in the xy-plane. Contour and hole detection is done in the xy-plane | ||
by 2D bounding boxes to be fast. | ||
|
||
.. autofunction:: make_paths_from_str(s: str, font: FontFace, halign: int = 0, valign: int = 0, m: Matrix44 = None) -> List[Path] | ||
|
||
.. autofunction:: make_hatches_from_str(s: str, font: FontFace, halign: int = 0, valign: int = 0, segments: int = 4, dxfattribs: Dict = None m: Matrix44 = None) -> List[Hatch] | ||
|
||
.. autofunction:: make_paths_from_entity(entity)-> List[Path] | ||
|
||
.. autofunction:: make_hatches_from_entity(entity) -> List[Hatch] | ||
|
||
.. autofunction:: group_contour_and_holes(Iterable[Path]) -> Iterable[Tuple[Path, List[Path]]] | ||
|
||
.. _matplotlib: https://matplotlib.org |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Copyright (c) 2021, Manfred Moitzi | ||
# License: MIT License | ||
# | ||
# Requires: matplotlib | ||
|
||
from pathlib import Path | ||
import ezdxf | ||
from ezdxf.lldxf import const | ||
from ezdxf.tools import fonts | ||
from ezdxf.addons import text2path | ||
|
||
DIR = Path('~/Desktop/Outbox').expanduser() | ||
fonts.load() | ||
|
||
doc = ezdxf.new() | ||
doc.layers.new('OUTLINE') | ||
doc.layers.new('FILLING') | ||
msp = doc.modelspace() | ||
|
||
attr = {'layer': 'OUTLINE', 'color': 1} | ||
ff = fonts.FontFace(family="Source Code Pro") | ||
s = "Source Code Pro 0123456789" | ||
halign = const.LEFT | ||
valign = const.BOTTOM | ||
segments = 8 | ||
|
||
for path in text2path.make_paths_from_str( | ||
s, ff, halign=halign, valign=valign): | ||
# The font geometry consist of many small quadratic bezier curves. | ||
# The distance argument for the flattening method has no big impact, but | ||
# the segments argument is very important, which defines the minimum count | ||
# of approximation lines for a single curve segment. | ||
# The default value is 16 which is much to much for these | ||
# short curve segments. | ||
# LWPOLYLINE: this works because I know the paths are in the xy-plane, | ||
# else an OCS transformation would be required or use add_polyline3d(). | ||
msp.add_lwpolyline(path.flattening(1, segments=segments), dxfattribs=attr) | ||
|
||
attr['layer'] = 'FILLING' | ||
attr['color'] = 2 | ||
for hatch in text2path.make_hatches_from_str( | ||
s, ff, halign=halign, valign=valign, | ||
dxfattribs=attr, segments=segments): | ||
msp.add_entity(hatch) | ||
|
||
doc.set_modelspace_vport(10, (7, 0)) | ||
doc.saveas(DIR / 'text2path.dxf') |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,194 @@ | ||
# Copyright (c) 2021, Manfred Moitzi | ||
# License: MIT License | ||
from typing import Union, List | ||
from typing import Union, List, Dict, Iterable, Tuple | ||
from matplotlib.textpath import TextPath | ||
from matplotlib.font_manager import FontProperties, findfont | ||
|
||
from ezdxf.entities import Text, Attrib | ||
from ezdxf.entities import Text, Attrib, Hatch | ||
from ezdxf.lldxf import const | ||
from ezdxf.render import Path | ||
from ezdxf.math import Matrix44, BoundingBox | ||
from ezdxf.render import path, nesting, Path | ||
from ezdxf.tools import text, fonts | ||
|
||
from matplotlib.path import Path as MPath | ||
|
||
AnyText = Union[Text, Attrib] | ||
|
||
# Each char consists of one or more paths! | ||
|
||
def make_paths_from_str(s: str, | ||
font: fonts.FontFace, | ||
halign: int = const.LEFT, | ||
valign: int = const.BASELINE, | ||
m: Matrix44 = None) -> List[Path]: | ||
""" Convert a single line string `s` into a list of | ||
:class:`~ezdxf.render.path.Path` objects. All paths are returned in a single | ||
list. The path objects are created for the text height of one drawing unit | ||
as cap height (height of uppercase letter "X") and the insertion point is | ||
(0, 0). | ||
The paths are aligned to this insertion point. | ||
BASELINE means the bottom of the letter "X". | ||
def make_paths_from_entities(entity: AnyText) -> List[Path]: | ||
""" Convert text content from DXF entities TEXT, ATTRIB and ATTDEF into a | ||
list of :class:`~ezdxf.render.Path` objects. All paths in a single list. | ||
Args: | ||
s: text to convert | ||
font: font face definition | ||
halign: horizontal alignment: LEFT=0, CENTER=1, RIGHT=2 | ||
valign: vertical alignment: BASELINE=0, BOTTOM=1, MIDDLE=2, TOP=3 | ||
m: transformation :class:`~ezdxf.math.Matrix44` | ||
""" | ||
return [] | ||
font_properties, font_measurements = _get_font_data(font) | ||
paths = _str_to_paths(s, font_properties) | ||
bbox = path.bbox(paths, precise=False) | ||
matrix = get_alignment_transformation( | ||
font_measurements, bbox, halign, valign) | ||
if m is not None: | ||
matrix *= m | ||
return list(path.transform_paths(paths, matrix)) | ||
|
||
|
||
def make_paths_from_str(s: str, | ||
font: fonts.FontFace, | ||
halign: int = const.LEFT, | ||
valign: int = const.BASELINE) -> List[Path]: | ||
""" Convert string `s` into a list of :class:`~ezdxf.render.Path` objects. | ||
All paths in a single list. The path objects | ||
are created for text height of one drawing unit as cap height (height of | ||
uppercase letter "X") and the insertion point is (0, 0). The paths are | ||
aligned to this insertion point. BASELINE means the bottom of the | ||
letter "X". | ||
def _get_font_data( | ||
font: fonts.FontFace) -> Tuple[FontProperties, fonts.FontMeasurements]: | ||
fp = FontProperties( | ||
family=font.family, | ||
style=font.style, | ||
stretch=font.stretch, | ||
weight=font.weight, | ||
) | ||
ttf_path = findfont(fp) | ||
fonts.load() # not expensive if already loaded | ||
# The ttf file path is the cache key for font measurements: | ||
fm = fonts.get_font_measurements(ttf_path) | ||
return fp, fm | ||
|
||
|
||
def _str_to_paths(s: str, fp: FontProperties) -> List[Path]: | ||
text_path = TextPath((0, 0), s, size=1, prop=fp, usetex=False) | ||
return list(path.from_matplotlib_path(text_path)) | ||
|
||
|
||
def get_alignment_transformation(fm: fonts.FontMeasurements, bbox: BoundingBox, | ||
halign: int, valign: int) -> Matrix44: | ||
if halign == const.LEFT: | ||
shift_x = 0 | ||
elif halign == const.RIGHT: | ||
shift_x = -bbox.extmax.x | ||
elif halign == const.CENTER: | ||
shift_x = -bbox.center.x | ||
else: | ||
raise ValueError(f'invalid halign argument: {halign}') | ||
cap_height = max(fm.cap_height, bbox.extmax.y) | ||
descender_height = max(fm.descender_height, abs(bbox.extmin.y)) | ||
if valign == const.BASELINE: | ||
shift_y = 0 | ||
elif valign == const.TOP: | ||
shift_y = -cap_height | ||
elif valign == const.MIDDLE: | ||
shift_y = -cap_height / 2 | ||
elif valign == const.BOTTOM: | ||
shift_y = descender_height | ||
else: | ||
raise ValueError(f'invalid valign argument: {valign}') | ||
return Matrix44.translate(shift_x, shift_y, 0) | ||
|
||
|
||
def group_contour_and_holes( | ||
paths: Iterable[Path]) -> Iterable[Tuple[Path, List[Path]]]: | ||
""" Group paths created from text strings or entities by their contour | ||
paths. e.g. "abc" yields 3 [contour, holes] structures:: | ||
ff = fonts.FontFace(family="Arial") | ||
paths = make_paths_from_str("abc", ff) | ||
for contour, holes in group_contour_and_holes(paths) | ||
for hole in holes: | ||
# hole is a Path() object | ||
pass | ||
Note: These paths as easy and fast to transform, | ||
see :func:`~ezdxf.render.path.transform_paths` | ||
This is the basic tool to create HATCH entities from paths. | ||
Warning: This function does not detect separated characters, e.g. "!" | ||
creates 2 contour paths. | ||
""" | ||
polygons = nesting.fast_bbox_detection(paths) | ||
for polygon in polygons: | ||
contour = polygon[0] | ||
if len(polygon) > 1: # are holes present? | ||
# holes can be recursive polygons, so flatten holes: | ||
holes = list(nesting.flatten_polygons(polygon[1])) | ||
else: | ||
holes = [] | ||
yield contour, holes | ||
|
||
|
||
def make_hatches_from_str(s: str, | ||
font: fonts.FontFace, | ||
halign: int = const.LEFT, | ||
valign: int = const.BASELINE, | ||
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 path objects are created for the text height of one drawing unit as cap | ||
height (height of uppercase letter "X") and the insertion point is (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 | ||
halign: horizontal alignment: LEFT=0, Center=1, RIGHT=2 | ||
halign: horizontal alignment: LEFT=0, CENTER=1, RIGHT=2 | ||
valign: vertical alignment: BASELINE=0, BOTTOM=1, MIDDLE=2, TOP=3 | ||
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) | ||
paths = _str_to_paths(s, font_properties) | ||
|
||
# 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) | ||
hatch.paths.add_polyline_path( | ||
contour.flattening(1, segments=segments), flags=1) # 1=external | ||
for hole in holes: | ||
hatch.paths.add_polyline_path( | ||
hole.flattening(1, segments=segments), flags=0) # 0=normal | ||
hatches.append(hatch) | ||
|
||
bbox = path.bbox(paths, precise=False) | ||
matrix = get_alignment_transformation( | ||
font_measurements, 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 make_paths_from_entity(entity: AnyText) -> List[Path]: | ||
""" Convert text content from DXF entities TEXT, ATTRIB and ATTDEF into a | ||
list of :class:`~ezdxf.render.Path` objects. All paths are returned in a | ||
single list. | ||
The paths are located at the location of the source entity, but don't expect | ||
a 100% match compared to CAD applications. | ||
""" | ||
return [] | ||
|
||
|
||
def make_hatches_from_entity(entity: AnyText) -> List[Hatch]: | ||
""" Convert text content from DXF entities TEXT, ATTRIB and ATTDEF into a | ||
list of virtual :class:`~ezdxf.entities.Hatch` entities. | ||
The hatches are located at the location of the source entity, but don't | ||
expect a 100% match compared to CAD applications. | ||
""" | ||
return [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
# Created: 28.12.2018 | ||
# Copyright (c) 2018-2020, Manfred Moitzi | ||
# Copyright (c) 2018-2021, Manfred Moitzi | ||
# License: MIT License | ||
from .arrows import ARROWS | ||
from .r12spline import R12Spline | ||
from .curves import Bezier, EulerSpiral, Spline, random_2d_path, random_3d_path | ||
from .mesh import MeshBuilder, MeshVertexMerger, MeshTransformer, MeshAverageVertexMerger | ||
from .trace import TraceBuilder | ||
from .path import Path, Command, has_path_support, make_path | ||
from .path import Path, Command, has_path_support, make_path, from_matplotlib_path |
Oops, something went wrong.