Files
stepanalyser/.venv/lib/python3.12/site-packages/ezdxf/graphicsfactory.py
Christian Anetzberger a197de9456 initial
2026-01-22 20:23:51 +01:00

2686 lines
102 KiB
Python

# Copyright (c) 2013-2023, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import (
TYPE_CHECKING,
Iterable,
Sequence,
cast,
Optional,
)
import math
import logging
import warnings
from ezdxf.lldxf import const
from ezdxf.lldxf.const import DXFValueError, DXFVersionError, DXF2000, DXF2013
from ezdxf.math import (
Vec3,
UVec,
UCS,
global_bspline_interpolation,
fit_points_to_cad_cv,
arc_angle_span_deg,
ConstructionArc,
NULLVEC,
)
from ezdxf.render.arrows import ARROWS
from ezdxf.entities import factory, Point, Spline, Body, Surface, Line, Circle
from ezdxf.entities.mtext_columns import *
from ezdxf.entities.dimstyleoverride import DimStyleOverride
from ezdxf.render.dim_linear import multi_point_linear_dimension
from ezdxf.tools import guid
logger = logging.getLogger("ezdxf")
if TYPE_CHECKING:
from ezdxf.document import Drawing
from ezdxf.eztypes import GenericLayoutType
from ezdxf.entities import (
Arc,
AttDef,
Dimension,
ArcDimension,
DXFGraphic,
Ellipse,
ExtrudedSurface,
Face3d,
Hatch,
Image,
ImageDef,
Insert,
LWPolyline,
Leader,
LoftedSurface,
MLine,
MText,
MPolygon,
Mesh,
Polyface,
Polyline,
Polymesh,
Ray,
Region,
RevolvedSurface,
Shape,
Solid,
Solid3d,
SweptSurface,
Text,
Trace,
Underlay,
UnderlayDefinition,
Wipeout,
XLine,
MultiLeader,
Helix,
)
from ezdxf.render.mleader import (
MultiLeaderMTextBuilder,
MultiLeaderBlockBuilder,
)
class CreatorInterface:
def __init__(self, doc: Drawing):
self.doc = doc
@property
def dxfversion(self) -> str:
return self.doc.dxfversion
@property
def is_active_paperspace(self):
return False
def new_entity(self, type_: str, dxfattribs: dict) -> DXFGraphic:
"""
Create entity in drawing database and add entity to the entity space.
Args:
type_ : DXF type string, like "LINE", "CIRCLE" or "LWPOLYLINE"
dxfattribs: DXF attributes for the new entity
"""
entity = factory.create_db_entry(type_, dxfattribs, self.doc)
self.add_entity(entity) # type: ignore
return entity # type: ignore
def add_entity(self, entity: DXFGraphic) -> None:
pass
def add_point(self, location: UVec, dxfattribs=None) -> Point:
"""
Add a :class:`~ezdxf.entities.Point` entity at `location`.
Args:
location: 2D/3D point in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["location"] = Vec3(location)
return self.new_entity("POINT", dxfattribs) # type: ignore
def add_line(self, start: UVec, end: UVec, dxfattribs=None) -> Line:
"""
Add a :class:`~ezdxf.entities.Line` entity from `start` to `end`.
Args:
start: 2D/3D point in :ref:`WCS`
end: 2D/3D point in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["start"] = Vec3(start)
dxfattribs["end"] = Vec3(end)
return self.new_entity("LINE", dxfattribs) # type: ignore
def add_circle(
self, center: UVec, radius: float, dxfattribs=None
) -> Circle:
"""
Add a :class:`~ezdxf.entities.Circle` entity. This is an 2D element,
which can be placed in space by using :ref:`OCS`.
Args:
center: 2D/3D point in :ref:`WCS`
radius: circle radius
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["center"] = Vec3(center)
dxfattribs["radius"] = float(radius)
return self.new_entity("CIRCLE", dxfattribs) # type: ignore
def add_ellipse(
self,
center: UVec,
major_axis: UVec = (1, 0, 0),
ratio: float = 1,
start_param: float = 0,
end_param: float = math.tau,
dxfattribs=None,
) -> Ellipse:
"""
Add an :class:`~ezdxf.entities.Ellipse` entity, `ratio` is the ratio of
minor axis to major axis, `start_param` and `end_param` defines start
and end point of the ellipse, a full ellipse goes from 0 to 2π.
The ellipse goes from start to end param in `counter-clockwise`
direction.
Args:
center: center of ellipse as 2D/3D point in :ref:`WCS`
major_axis: major axis as vector (x, y, z)
ratio: ratio of minor axis to major axis in range +/-[1e-6, 1.0]
start_param: start of ellipse curve
end_param: end param of ellipse curve
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("ELLIPSE requires DXF R2000")
ratio = float(ratio)
if abs(ratio) > 1.0: # not valid for AutoCAD
raise DXFValueError("invalid axis ratio > 1.0")
_major_axis = Vec3(major_axis)
if _major_axis.is_null: # not valid for AutoCAD
raise DXFValueError("invalid major axis: (0, 0, 0)")
dxfattribs = dict(dxfattribs or {})
dxfattribs["center"] = Vec3(center)
dxfattribs["major_axis"] = _major_axis
dxfattribs["ratio"] = ratio
dxfattribs["start_param"] = float(start_param)
dxfattribs["end_param"] = float(end_param)
return self.new_entity("ELLIPSE", dxfattribs) # type: ignore
def add_arc(
self,
center: UVec,
radius: float,
start_angle: float,
end_angle: float,
is_counter_clockwise: bool = True,
dxfattribs=None,
) -> Arc:
"""
Add an :class:`~ezdxf.entities.Arc` entity. The arc goes from
`start_angle` to `end_angle` in counter-clockwise direction by default,
set parameter `is_counter_clockwise` to ``False`` for clockwise
orientation.
Args:
center: center of arc as 2D/3D point in :ref:`WCS`
radius: arc radius
start_angle: start angle in degrees
end_angle: end angle in degrees
is_counter_clockwise: ``False`` for clockwise orientation
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["center"] = Vec3(center)
dxfattribs["radius"] = float(radius)
if is_counter_clockwise:
dxfattribs["start_angle"] = float(start_angle)
dxfattribs["end_angle"] = float(end_angle)
else:
dxfattribs["start_angle"] = float(end_angle)
dxfattribs["end_angle"] = float(start_angle)
return self.new_entity("ARC", dxfattribs) # type: ignore
def add_solid(self, points: Iterable[UVec], dxfattribs=None) -> Solid:
"""Add a :class:`~ezdxf.entities.Solid` entity, `points` is an iterable
of 3 or 4 points.
.. hint::
The last two vertices are in reversed order: a square has the
vertex order 0-1-3-2
Args:
points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
return self._add_quadrilateral("SOLID", points, dxfattribs) # type: ignore
def add_trace(self, points: Iterable[UVec], dxfattribs=None) -> Trace:
"""Add a :class:`~ezdxf.entities.Trace` entity, `points` is an iterable
of 3 or 4 points.
.. hint::
The last two vertices are in reversed order: a square has the
vertex order 0-1-3-2
Args:
points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
return self._add_quadrilateral("TRACE", points, dxfattribs) # type: ignore
def add_3dface(self, points: Iterable[UVec], dxfattribs=None) -> Face3d:
"""
Add a :class:`~ezdxf.entities.3DFace` entity, `points` is an iterable
3 or 4 2D/3D points.
.. hint::
In contrast to SOLID and TRACE, the last two vertices are in
regular order: a square has the vertex order 0-1-2-3
Args:
points: iterable of 3 or 4 2D/3D points in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
return self._add_quadrilateral("3DFACE", points, dxfattribs) # type: ignore
def add_text(
self,
text: str,
*,
height: Optional[float] = None,
rotation: Optional[float] = None,
dxfattribs=None,
) -> Text:
"""
Add a :class:`~ezdxf.entities.Text` entity, see also
:class:`~ezdxf.entities.Textstyle`.
Args:
text: content string
height: text height in drawing units
rotation: text rotation in degrees
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["text"] = str(text)
if height is not None:
dxfattribs["height"] = float(height)
if rotation is not None:
dxfattribs["rotation"] = float(rotation)
dxfattribs.setdefault("insert", Vec3())
return self.new_entity("TEXT", dxfattribs) # type: ignore
def add_blockref(self, name: str, insert: UVec, dxfattribs=None) -> Insert:
"""
Add an :class:`~ezdxf.entities.Insert` entity.
When inserting a block reference into the modelspace or another block
layout with different units, the scaling factor between these units
should be applied as scaling attributes (:attr:`xscale`, ...) e.g.
modelspace in meters and block in centimeters, :attr:`xscale` has to
be 0.01.
Args:
name: block name as str
insert: insert location as 2D/3D point in :ref:`WCS`
dxfattribs: additional DXF attributes
"""
if not isinstance(name, str):
raise DXFValueError("Block name as string required.")
dxfattribs = dict(dxfattribs or {})
dxfattribs["name"] = name
dxfattribs["insert"] = Vec3(insert)
return self.new_entity("INSERT", dxfattribs) # type: ignore
def add_auto_blockref(
self,
name: str,
insert: UVec,
values: dict[str, str],
dxfattribs=None,
) -> Insert:
"""
Add an :class:`~ezdxf.entities.Insert` entity. This method adds for each
:class:`~ezdxf.entities.Attdef` entity, defined in the block definition,
automatically an :class:`Attrib` entity to the block reference and set
(tag, value) DXF attributes of the ATTRIB entities by the (key, value)
pairs (both as strings) of the `values` dict.
The Attrib entities are placed relative to the insert point, which is
equal to the block base point.
This method wraps the INSERT and all the ATTRIB entities into an
anonymous block, which produces the best visual results, especially for
non-uniform scaled block references, because the transformation and
scaling is done by the CAD application. But this makes evaluation of
block references with attributes more complicated, if you prefer INSERT
and ATTRIB entities without a wrapper block use the
:meth:`add_blockref_with_attribs` method.
Args:
name: block name
insert: insert location as 2D/3D point in :ref:`WCS`
values: :class:`~ezdxf.entities.Attrib` tag values as (tag, value) pairs
dxfattribs: additional DXF attributes
"""
if not isinstance(name, str):
raise DXFValueError("Block name as string required.")
def unpack(dxfattribs) -> tuple[str, str, UVec]:
tag = dxfattribs.pop("tag")
text = values.get(tag, "")
location = dxfattribs.pop("insert")
return tag, text, location
def autofill() -> None:
# ATTRIBs are placed relative to the base point
for attdef in blockdef.attdefs():
dxfattribs = attdef.dxfattribs(drop={"prompt", "handle"})
tag, text, location = unpack(dxfattribs)
blockref.add_attrib(tag, text, location, dxfattribs)
dxfattribs = dict(dxfattribs or {})
autoblock = self.doc.blocks.new_anonymous_block()
blockref = autoblock.add_blockref(name, (0, 0))
blockdef = self.doc.blocks[name]
autofill()
return self.add_blockref(autoblock.name, insert, dxfattribs)
def add_attdef(
self,
tag: str,
insert: UVec = (0, 0),
text: str = "",
*,
height: Optional[float] = None,
rotation: Optional[float] = None,
dxfattribs=None,
) -> AttDef:
"""
Add an :class:`~ezdxf.entities.AttDef` as stand alone DXF entity.
Set position and alignment by the idiom::
layout.add_attdef("NAME").set_placement(
(2, 3), align=TextEntityAlignment.MIDDLE_CENTER
)
Args:
tag: tag name as string
insert: insert location as 2D/3D point in :ref:`WCS`
text: tag value as string
height: text height in drawing units
rotation: text rotation in degrees
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["tag"] = str(tag)
dxfattribs["insert"] = Vec3(insert)
dxfattribs["text"] = str(text)
if height is not None:
dxfattribs["height"] = float(height)
if rotation is not None:
dxfattribs["rotation"] = float(rotation)
return self.new_entity("ATTDEF", dxfattribs) # type: ignore
def add_polyline2d(
self,
points: Iterable[UVec],
format: Optional[str] = None,
*,
close: bool = False,
dxfattribs=None,
) -> Polyline:
"""
Add a 2D :class:`~ezdxf.entities.Polyline` entity.
Args:
points: iterable of 2D points in :ref:`WCS`
close: ``True`` for a closed polyline
format: user defined point format like :meth:`add_lwpolyline`,
default is ``None``
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
if "closed" in dxfattribs:
warnings.warn(
'dxfattribs key "closed" is deprecated, '
'use keyword argument "close"',
DeprecationWarning,
)
close = dxfattribs.pop("closed", close)
polyline: Polyline = self.new_entity("POLYLINE", dxfattribs) # type: ignore
polyline.close(close)
if format is not None:
polyline.append_formatted_vertices(points, format=format)
else:
polyline.append_vertices(points)
if self.doc:
polyline.add_sub_entities_to_entitydb(self.doc.entitydb)
return polyline
def add_polyline3d(
self,
points: Iterable[UVec],
*,
close: bool = False,
dxfattribs=None,
) -> Polyline:
"""Add a 3D :class:`~ezdxf.entities.Polyline` entity.
Args:
points: iterable of 3D points in :ref:`WCS`
close: ``True`` for a closed polyline
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["flags"] = (
dxfattribs.get("flags", 0) | const.POLYLINE_3D_POLYLINE
)
return self.add_polyline2d(points, close=close, dxfattribs=dxfattribs)
def add_polymesh(
self, size: tuple[int, int] = (3, 3), dxfattribs=None
) -> Polymesh:
"""
Add a :class:`~ezdxf.entities.Polymesh` entity, which is a wrapper class
for the POLYLINE entity. A polymesh is a grid of `mcount` x `ncount`
vertices and every vertex has its own (x, y, z)-coordinates.
Args:
size: 2-tuple (`mcount`, `ncount`)
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["flags"] = (
dxfattribs.get("flags", 0) | const.POLYLINE_3D_POLYMESH
)
m_size = max(size[0], 2)
n_size = max(size[1], 2)
dxfattribs["m_count"] = m_size
dxfattribs["n_count"] = n_size
m_close = dxfattribs.pop("m_close", False)
n_close = dxfattribs.pop("n_close", False)
polymesh: Polymesh = self.new_entity("POLYLINE", dxfattribs) # type: ignore
points = [(0, 0, 0)] * (m_size * n_size)
polymesh.append_vertices(points) # init mesh vertices
polymesh.close(m_close, n_close)
if self.doc:
polymesh.add_sub_entities_to_entitydb(self.doc.entitydb)
return polymesh
def add_polyface(self, dxfattribs=None) -> Polyface:
"""Add a :class:`~ezdxf.entities.Polyface` entity, which is a wrapper
class for the POLYLINE entity.
Args:
dxfattribs: additional DXF attributes for
:class:`~ezdxf.entities.Polyline` entity
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["flags"] = (
dxfattribs.get("flags", 0) | const.POLYLINE_POLYFACE
)
m_close = dxfattribs.pop("m_close", False)
n_close = dxfattribs.pop("n_close", False)
polyface: Polyface = self.new_entity("POLYLINE", dxfattribs) # type: ignore
polyface.close(m_close, n_close)
if self.doc:
polyface.add_sub_entities_to_entitydb(self.doc.entitydb)
return polyface
def _add_quadrilateral(
self, type_: str, points: Iterable[UVec], dxfattribs=None
) -> DXFGraphic:
dxfattribs = dict(dxfattribs or {})
entity = self.new_entity(type_, dxfattribs)
for x, point in enumerate(self._four_points(points)):
entity[x] = Vec3(point) # type: ignore
return entity
@staticmethod
def _four_points(points: Iterable[UVec]) -> Iterable[UVec]:
vertices = list(points)
if len(vertices) not in (3, 4):
raise DXFValueError("3 or 4 points required.")
for vertex in vertices:
yield vertex
if len(vertices) == 3:
yield vertices[-1] # last again
def add_shape(
self,
name: str,
insert: UVec = (0, 0),
size: float = 1.0,
dxfattribs=None,
) -> Shape:
"""
Add a :class:`~ezdxf.entities.Shape` reference to an external stored shape.
Args:
name: shape name as string
insert: insert location as 2D/3D point in :ref:`WCS`
size: size factor
dxfattribs: additional DXF attributes
"""
dxfattribs = dict(dxfattribs or {})
dxfattribs["name"] = str(name)
dxfattribs["insert"] = Vec3(insert)
dxfattribs["size"] = float(size)
return self.new_entity("SHAPE", dxfattribs) # type: ignore
# new entities in DXF AC1015 (R2000)
def add_lwpolyline(
self,
points: Iterable[UVec],
format: str = "xyseb",
*,
close: bool = False,
dxfattribs=None,
) -> LWPolyline:
"""
Add a 2D polyline as :class:`~ezdxf.entities.LWPolyline` entity.
A points are defined as (x, y, [start_width, [end_width, [bulge]]])
tuples, but order can be redefined by the `format` argument. Set
`start_width`, `end_width` to 0 to be ignored like
(x, y, 0, 0, bulge).
The :class:`~ezdxf.entities.LWPolyline` is defined as a single DXF
entity and needs less disk space than a
:class:`~ezdxf.entities.Polyline` entity. (requires DXF R2000)
Format codes:
- ``x`` = x-coordinate
- ``y`` = y-coordinate
- ``s`` = start width
- ``e`` = end width
- ``b`` = bulge value
- ``v`` = (x, y [,z]) tuple (z-axis is ignored)
Args:
points: iterable of (x, y, [start_width, [end_width, [bulge]]]) tuples
format: user defined point format, default is "xyseb"
close: ``True`` for a closed polyline
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("LWPOLYLINE requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
if "closed" in dxfattribs:
warnings.warn(
'dxfattribs key "closed" is deprecated, '
'use keyword argument "close"',
DeprecationWarning,
)
close = dxfattribs.pop("closed", close)
lwpolyline: LWPolyline = self.new_entity("LWPOLYLINE", dxfattribs) # type: ignore
lwpolyline.set_points(points, format=format)
lwpolyline.closed = close
return lwpolyline
def add_mtext(self, text: str, dxfattribs=None) -> MText:
"""
Add a multiline text entity with automatic text wrapping at boundaries
as :class:`~ezdxf.entities.MText` entity.
(requires DXF R2000)
Args:
text: content string
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("MTEXT requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
mtext: MText = self.new_entity("MTEXT", dxfattribs) # type: ignore
mtext.text = str(text)
return mtext
def add_mtext_static_columns(
self,
content: Iterable[str],
width: float,
gutter_width: float,
height: float,
dxfattribs=None,
) -> MText:
"""Add a multiline text entity with static columns as
:class:`~ezdxf.entities.MText` entity. The content is spread
across the columns, the count of content strings determine the count
of columns.
This factory method adds automatically a column break ``"\\N"`` at the
end of each column text to force a new column.
The `height` attribute should be big enough to reserve enough space for
the tallest column. Too small values produce valid DXF files, but the
visual result will not be as expected. The `height` attribute also
defines the total height of the MTEXT entity.
(requires DXF R2000)
Args:
content: iterable of column content
width: column width
gutter_width: distance between columns
height: max. column height
dxfattribs: additional DXF attributes
"""
dxfversion = self.dxfversion
if dxfversion < DXF2000:
raise DXFVersionError("MTEXT requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
content = list(content)
if dxfversion < const.DXF2018:
mtext = make_static_columns_r2000(
content, width, gutter_width, height, dxfattribs
)
else:
mtext = make_static_columns_r2018(
content, width, gutter_width, height, dxfattribs
)
if self.doc:
self.doc.entitydb.add(mtext)
self.add_entity(mtext)
return mtext
def add_mtext_dynamic_auto_height_columns(
self,
content: str,
width: float,
gutter_width: float,
height: float,
count: int,
dxfattribs=None,
) -> MText:
"""Add a multiline text entity with as many columns as needed for the
given common fixed `height`. The content is spread across the columns
automatically by the CAD application. The `height` argument also defines
the total height of the MTEXT entity. To get the correct column `count`
requires an **exact** MTEXT rendering like AutoCAD, which is
not done by `ezdxf`, therefore passing the expected column `count`
is required to calculate the correct total width.
This current implementation works best for DXF R2018, because the
content is stored as a continuous text in a single MTEXT entity. For
DXF versions prior to R2018 the content should be distributed across
multiple MTEXT entities (one entity per column), which is not done by
`ezdxf`, but the result is correct for advanced DXF viewers and CAD
application, which do the MTEXT content distribution completely by
itself.
Because of the current limitations the use of this method is not
recommend. This situation may improve in future releases, but the exact
rendering of the content will also slow down the processing speed
dramatically.
(requires DXF R2000)
Args:
content: column content as a single string
width: column width
gutter_width: distance between columns
height: max. column height
count: expected column count
dxfattribs: additional DXF attributes
"""
dxfversion = self.dxfversion
if dxfversion < DXF2000:
raise DXFVersionError("MTEXT requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
if dxfversion < const.DXF2018:
mtext = make_dynamic_auto_height_columns_r2000(
content, width, gutter_width, height, count, dxfattribs
)
else:
mtext = make_dynamic_auto_height_columns_r2018(
content, width, gutter_width, height, count, dxfattribs
)
if self.doc:
self.doc.entitydb.add(mtext)
self.add_entity(mtext)
return mtext
def add_mtext_dynamic_manual_height_columns(
self,
content: str,
width: float,
gutter_width: float,
heights: Sequence[float],
dxfattribs=None,
) -> MText:
"""Add a multiline text entity with dynamic columns as
:class:`~ezdxf.entities.MText` entity. The content is spread
across the columns automatically by the CAD application.
The `heights` sequence determine the height of the columns, except for
the last column, which always takes the remaining content. The height
value for the last column is required but can be 0, because the value
is ignored. The count of `heights` also determines the count of columns,
and :code:`max(heights)` defines the total height of the MTEXT entity,
which may be wrong if the last column requires more space.
This current implementation works best for DXF R2018, because the
content is stored as a continuous text in a single MTEXT entity. For
DXF versions prior to R2018 the content should be distributed across
multiple MTEXT entities (one entity per column), which is not done by
`ezdxf`, but the result is correct for advanced DXF viewers and CAD
application, which do the MTEXT content distribution completely by
itself.
(requires DXF R2000)
Args:
content: column content as a single string
width: column width
gutter_width: distance between columns
heights: column height for each column
dxfattribs: additional DXF attributes
"""
# The current implementation work well for R2018.
#
# For the prior DXF versions the current implementation puts the whole
# content into the first (main) column.
# This works for AutoCAD and BricsCAD, both collect the content from
# the linked MTEXT columns and the main column and do their own MTEXT
# rendering - DO trust the DXF content!
# The drawing add-on will fail at this until a usable MTEXT renderer
# is implemented. For now the drawing add-on renders the main MTEXT and
# the linked MTEXT columns as standalone MTEXT entities, and all content
# is stored in the main MTEXT entity.
# For the same reason the R2018 MTEXT rendering is not correct.
# In DXF R2018 the MTEXT entity is a single entity without linked
# MTEXT columns and the content is a single string, which has to be
# parsed, rendered and distributed across the columns by the
# CAD application itself.
dxfversion = self.dxfversion
if dxfversion < DXF2000:
raise DXFVersionError("MTEXT requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
if dxfversion < const.DXF2018:
mtext = make_dynamic_manual_height_columns_r2000(
content, width, gutter_width, heights, dxfattribs
)
else:
mtext = make_dynamic_manual_height_columns_r2018(
content, width, gutter_width, heights, dxfattribs
)
if self.doc:
self.doc.entitydb.add(mtext)
self.add_entity(mtext)
return mtext
def add_ray(self, start: UVec, unit_vector: UVec, dxfattribs=None) -> Ray:
"""
Add a :class:`~ezdxf.entities.Ray` that begins at `start` point and
continues to infinity (construction line). (requires DXF R2000)
Args:
start: location 3D point in :ref:`WCS`
unit_vector: 3D vector (x, y, z)
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("RAY requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["start"] = Vec3(start)
dxfattribs["unit_vector"] = Vec3(unit_vector).normalize()
return self.new_entity("RAY", dxfattribs) # type: ignore
def add_xline(
self, start: UVec, unit_vector: UVec, dxfattribs=None
) -> XLine:
"""Add an infinity :class:`~ezdxf.entities.XLine` (construction line).
(requires DXF R2000)
Args:
start: location 3D point in :ref:`WCS`
unit_vector: 3D vector (x, y, z)
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("XLINE requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["start"] = Vec3(start)
dxfattribs["unit_vector"] = Vec3(unit_vector).normalize()
return self.new_entity("XLINE", dxfattribs) # type: ignore
def add_spline(
self,
fit_points: Optional[Iterable[UVec]] = None,
degree: int = 3,
dxfattribs=None,
) -> Spline:
"""Add a B-spline (:class:`~ezdxf.entities.Spline` entity) defined by
the given `fit_points` - the control points and knot values are created
by the CAD application, therefore it is not predictable how the rendered
spline will look like, because for every set of fit points exists an
infinite set of B-splines.
If `fit_points` is ``None``, an "empty" spline will be created, all data
has to be set by the user.
The SPLINE entity requires DXF R2000.
AutoCAD creates a spline through fit points by a global curve
interpolation and an unknown method to estimate the direction of the
start- and end tangent.
.. seealso::
- :ref:`tut_spline`
- :func:`ezdxf.math.fit_points_to_cad_cv`
Args:
fit_points: iterable of fit points as ``(x, y[, z])`` in :ref:`WCS`,
creates an empty :class:`~ezdxf.entities.Spline` if ``None``
degree: degree of B-spline, max. degree supported by AutoCAD is 11
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("SPLINE requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["degree"] = int(degree)
spline: Spline = self.new_entity("SPLINE", dxfattribs) # type: ignore
if fit_points is not None:
spline.fit_points = Vec3.generate(fit_points)
return spline
def add_spline_control_frame(
self,
fit_points: Iterable[UVec],
degree: int = 3,
method: str = "chord",
dxfattribs=None,
) -> Spline:
"""Add a :class:`~ezdxf.entities.Spline` entity passing through the
given `fit_points`, the control points are calculated by a global curve
interpolation without start- and end tangent constrains.
The new SPLINE entity is defined by control points and not by the fit
points, therefore the SPLINE looks always the same, no matter which CAD
application renders the SPLINE.
- "uniform": creates a uniform t vector, from 0 to 1 evenly spaced, see
`uniform`_ method
- "distance", "chord": creates a t vector with values proportional to
the fit point distances, see `chord length`_ method
- "centripetal", "sqrt_chord": creates a t vector with values
proportional to the fit point sqrt(distances), see `centripetal`_
method
- "arc": creates a t vector with values proportional to the arc length
between fit points.
Use function :meth:`add_cad_spline_control_frame` to create
SPLINE entities from fit points similar to CAD application including
start- and end tangent constraints.
Args:
fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS`
degree: degree of B-spline, max. degree supported by AutoCAD is 11
method: calculation method for parameter vector t
dxfattribs: additional DXF attributes
"""
bspline = global_bspline_interpolation(
fit_points, degree=degree, method=method
)
return self.add_open_spline(
control_points=bspline.control_points,
degree=bspline.degree,
knots=bspline.knots(),
dxfattribs=dxfattribs,
)
def add_cad_spline_control_frame(
self,
fit_points: Iterable[UVec],
tangents: Optional[Iterable[UVec]] = None,
dxfattribs=None,
) -> Spline:
"""Add a :class:`~ezdxf.entities.Spline` entity passing through the
given fit points. This method creates the same control points as CAD
applications.
Args:
fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS`
tangents: start- and end tangent, default is autodetect
dxfattribs: additional DXF attributes
"""
s = fit_points_to_cad_cv(fit_points, tangents=tangents)
spline = self.add_spline(dxfattribs=dxfattribs)
spline.apply_construction_tool(s)
return spline
def add_open_spline(
self,
control_points: Iterable[UVec],
degree: int = 3,
knots: Optional[Iterable[float]] = None,
dxfattribs=None,
) -> Spline:
"""
Add an open uniform :class:`~ezdxf.entities.Spline` defined by
`control_points`. (requires DXF R2000)
Open uniform B-splines start and end at your first and last control point.
Args:
control_points: iterable of 3D points in :ref:`WCS`
degree: degree of B-spline, max. degree supported by AutoCAD is 11
knots: knot values as iterable of floats
dxfattribs: additional DXF attributes
"""
spline = self.add_spline(dxfattribs=dxfattribs)
spline.set_open_uniform(list(control_points), degree)
if knots is not None:
spline.knots = knots # type: ignore
return spline
def add_rational_spline(
self,
control_points: Iterable[UVec],
weights: Sequence[float],
degree: int = 3,
knots: Optional[Iterable[float]] = None,
dxfattribs=None,
) -> Spline:
"""
Add an open rational uniform :class:`~ezdxf.entities.Spline` defined by
`control_points`. (requires DXF R2000)
`weights` has to be an iterable of floats, which defines the influence
of the associated control point to the shape of the B-spline, therefore
for each control point is one weight value required.
Open rational uniform B-splines start and end at the first and last
control point.
Args:
control_points: iterable of 3D points in :ref:`WCS`
weights: weight values as iterable of floats
degree: degree of B-spline, max. degree supported by AutoCAD is 11
knots: knot values as iterable of floats
dxfattribs: additional DXF attributes
"""
spline = self.add_spline(dxfattribs=dxfattribs)
spline.set_open_rational(list(control_points), weights, degree)
if knots is not None:
spline.knots = knots # type: ignore
return spline
def add_body(self, dxfattribs=None) -> Body:
"""Add a :class:`~ezdxf.entities.Body` entity.
(requires DXF R2000 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
return self._add_acis_entity("BODY", dxfattribs)
def add_region(self, dxfattribs=None) -> Region:
"""Add a :class:`~ezdxf.entities.Region` entity.
(requires DXF R2000 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
return self._add_acis_entity("REGION", dxfattribs) # type: ignore
def add_3dsolid(self, dxfattribs=None) -> Solid3d:
"""Add a 3DSOLID entity (:class:`~ezdxf.entities.Solid3d`).
(requires DXF R2000 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
return self._add_acis_entity("3DSOLID", dxfattribs) # type: ignore
def add_surface(self, dxfattribs=None) -> Surface:
"""Add a :class:`~ezdxf.entities.Surface` entity.
(requires DXF R2007 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
if self.dxfversion < const.DXF2007:
raise DXFVersionError("SURFACE requires DXF R2007 or later")
return self._add_acis_entity("SURFACE", dxfattribs) # type: ignore
def add_extruded_surface(self, dxfattribs=None) -> ExtrudedSurface:
"""Add a :class:`~ezdxf.entities.ExtrudedSurface` entity.
(requires DXF R2007 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
if self.dxfversion < const.DXF2007:
raise DXFVersionError("EXTRUDEDSURFACE requires DXF R2007 or later")
return self._add_acis_entity("EXTRUDEDSURFACE", dxfattribs) # type: ignore
def add_lofted_surface(self, dxfattribs=None) -> LoftedSurface:
"""Add a :class:`~ezdxf.entities.LoftedSurface` entity.
(requires DXF R2007 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
if self.dxfversion < const.DXF2007:
raise DXFVersionError("LOFTEDSURFACE requires DXF R2007 or later")
return self._add_acis_entity("LOFTEDSURFACE", dxfattribs) # type: ignore
def add_revolved_surface(self, dxfattribs=None) -> RevolvedSurface:
"""
Add a :class:`~ezdxf.entities.RevolvedSurface` entity.
(requires DXF R2007 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
if self.dxfversion < const.DXF2007:
raise DXFVersionError("REVOLVEDSURFACE requires DXF R2007 or later")
return self._add_acis_entity("REVOLVEDSURFACE", dxfattribs) # type: ignore
def add_swept_surface(self, dxfattribs=None) -> SweptSurface:
"""
Add a :class:`~ezdxf.entities.SweptSurface` entity.
(requires DXF R2007 or later)
The ACIS data has to be set as :term:`SAT` or :term:`SAB`.
"""
if self.dxfversion < const.DXF2007:
raise DXFVersionError("SWEPTSURFACE requires DXF R2007 or later")
return self._add_acis_entity("SWEPTSURFACE", dxfattribs) # type: ignore
def _add_acis_entity(self, name, dxfattribs) -> Body:
if self.dxfversion < const.DXF2000:
raise DXFVersionError(f"{name} requires DXF R2000 or later")
dxfattribs = dict(dxfattribs or {})
if self.dxfversion >= DXF2013:
dxfattribs.setdefault("flags", 1)
dxfattribs.setdefault("uid", guid())
return self.new_entity(name, dxfattribs) # type: ignore
def add_hatch(self, color: int = 7, dxfattribs=None) -> Hatch:
"""Add a :class:`~ezdxf.entities.Hatch` entity. (requires DXF R2000)
Args:
color: fill color as :ref`ACI`, default is 7 (black/white).
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("HATCH requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["solid_fill"] = 1
dxfattribs["color"] = int(color)
dxfattribs["pattern_name"] = "SOLID"
return self.new_entity("HATCH", dxfattribs) # type: ignore
def add_mpolygon(
self,
color: int = const.BYLAYER,
fill_color: Optional[int] = None,
dxfattribs=None,
) -> MPolygon:
"""Add a :class:`~ezdxf.entities.MPolygon` entity. (requires DXF R2000)
The MPOLYGON entity is not a core DXF entity and is not supported by
every CAD application or DXF library.
DXF version R2004+ is required to use a fill color different from
BYLAYER. For R2000 the fill color is always BYLAYER, set any ACI value
to create a filled MPOLYGON entity.
Args:
color: boundary color as :ref:`ACI`, default is BYLAYER.
fill_color: fill color as :ref:`ACI`, default is ``None``
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("MPOLYGON requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
if fill_color is None:
dxfattribs["solid_fill"] = 0
else:
dxfattribs["solid_fill"] = 1
dxfattribs["fill_color"] = fill_color
dxfattribs["pattern_name"] = "SOLID"
dxfattribs["pattern_type"] = const.HATCH_TYPE_PREDEFINED
dxfattribs["color"] = int(color)
return self.new_entity("MPOLYGON", dxfattribs) # type: ignore
def add_mesh(self, dxfattribs=None) -> Mesh:
"""
Add a :class:`~ezdxf.entities.Mesh` entity. (requires DXF R2007)
Args:
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("MESH requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
return self.new_entity("MESH", dxfattribs) # type: ignore
def add_image(
self,
image_def: ImageDef,
insert: UVec,
size_in_units: tuple[float, float],
rotation: float = 0.0,
dxfattribs=None,
) -> Image:
"""
Add an :class:`~ezdxf.entities.Image` entity, requires a
:class:`~ezdxf.entities.ImageDef` entity, see :ref:`tut_image`.
(requires DXF R2000)
Args:
image_def: required image definition as :class:`~ezdxf.entities.ImageDef`
insert: insertion point as 3D point in :ref:`WCS`
size_in_units: size as (x, y) tuple in drawing units
rotation: rotation angle around the extrusion axis, default is the
z-axis, in degrees
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("IMAGE requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
x_pixels, y_pixels = image_def.dxf.image_size.vec2
x_units, y_units = size_in_units
x_units_per_pixel = x_units / x_pixels
y_units_per_pixel = y_units / y_pixels
x_angle_rad = math.radians(rotation)
y_angle_rad = x_angle_rad + (math.pi / 2.0)
dxfattribs["insert"] = Vec3(insert)
dxfattribs["u_pixel"] = Vec3.from_angle(x_angle_rad, x_units_per_pixel)
dxfattribs["v_pixel"] = Vec3.from_angle(y_angle_rad, y_units_per_pixel)
dxfattribs["image_def"] = image_def # is not a real DXF attrib
return self.new_entity("IMAGE", dxfattribs) # type: ignore
def add_wipeout(self, vertices: Iterable[UVec], dxfattribs=None) -> Wipeout:
"""Add a :class:`ezdxf.entities.Wipeout` entity, the masking area is
defined by WCS `vertices`.
This method creates only a 2D entity in the xy-plane of the layout,
the z-axis of the input vertices are ignored.
"""
dxfattribs = dict(dxfattribs or {})
wipeout: Wipeout = self.new_entity("WIPEOUT", dxfattribs=dxfattribs) # type: ignore
wipeout.set_masking_area(vertices)
doc = self.doc
if doc and ("ACAD_WIPEOUT_VARS" not in doc.rootdict):
doc.set_wipeout_variables(frame=0)
return wipeout
def add_underlay(
self,
underlay_def: UnderlayDefinition,
insert: UVec = (0, 0, 0),
scale=(1, 1, 1),
rotation: float = 0.0,
dxfattribs=None,
) -> Underlay:
"""
Add an :class:`~ezdxf.entities.Underlay` entity, requires a
:class:`~ezdxf.entities.UnderlayDefinition` entity,
see :ref:`tut_underlay`. (requires DXF R2000)
Args:
underlay_def: required underlay definition as :class:`~ezdxf.entities.UnderlayDefinition`
insert: insertion point as 3D point in :ref:`WCS`
scale: underlay scaling factor as (x, y, z) tuple or as single
value for uniform scaling for x, y and z
rotation: rotation angle around the extrusion axis, default is the
z-axis, in degrees
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("UNDERLAY requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["insert"] = Vec3(insert)
dxfattribs["underlay_def_handle"] = underlay_def.dxf.handle
dxfattribs["rotation"] = float(rotation)
underlay: Underlay = self.new_entity( # type: ignore
underlay_def.entity_name, dxfattribs
)
underlay.scaling = scale
underlay.set_underlay_def(underlay_def)
return underlay
def _safe_dimstyle(self, name: str) -> str:
if self.doc and self.doc.dimstyles.has_entry(name):
return name
logger.debug(
f'Replacing undefined DIMSTYLE "{name}" by "Standard" DIMSTYLE.'
)
return "Standard"
def add_linear_dim(
self,
base: UVec,
p1: UVec,
p2: UVec,
location: Optional[UVec] = None,
text: str = "<>",
angle: float = 0,
# 0=horizontal, 90=vertical, else=rotated
text_rotation: Optional[float] = None,
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add horizontal, vertical and rotated :class:`~ezdxf.entities.Dimension`
line. If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
See also: :ref:`tut_linear_dimension`
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.Dimension` entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
base: location of dimension line, any point on the dimension line or
its extension will do (in UCS)
p1: measurement point 1 and start point of extension line 1 (in UCS)
p2: measurement point 2 and start point of extension line 2 (in UCS)
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text, everything
else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZDXF"
angle: angle from ucs/wcs x-axis to dimension line in degrees
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
type_ = {"dimtype": const.DIM_LINEAR | const.DIM_BLOCK_EXCLUSIVE}
dimline: Dimension = self.new_entity("DIMENSION", dxfattribs=type_) # type: ignore
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["defpoint"] = Vec3(base) # group code 10
dxfattribs["text"] = str(text)
dxfattribs["defpoint2"] = Vec3(p1) # group code 13
dxfattribs["defpoint3"] = Vec3(p2) # group code 14
dxfattribs["angle"] = float(angle)
# text_rotation ALWAYS overrides implicit angles as absolute angle
# (x-axis=0, y-axis=90)!
if text_rotation is not None:
dxfattribs["text_rotation"] = float(text_rotation)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
# special version, just for linear dimension
style.set_location(location, leader=False, relative=False)
return style
def add_multi_point_linear_dim(
self,
base: UVec,
points: Iterable[UVec],
angle: float = 0,
ucs: Optional[UCS] = None,
avoid_double_rendering: bool = True,
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
discard=False,
) -> None:
"""
Add multiple linear dimensions for iterable `points`. If an
:class:`~ezdxf.math.UCS` is used for dimension line rendering, all point
definitions in UCS coordinates, translation into :ref:`WCS` and
:ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default. See also:
:ref:`tut_linear_dimension`
This method sets many design decisions by itself, the necessary geometry
will be generated automatically, no required nor possible
:meth:`~ezdxf.entities.DimStyleOverride.render` call.
This method is easy to use, but you get what you get.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
base: location of dimension line, any point on the dimension line
or its extension will do (in UCS)
points: iterable of measurement points (in UCS)
angle: angle from ucs/wcs x-axis to dimension line in degrees
(0 = horizontal, 90 = vertical)
ucs: user defined coordinate system
avoid_double_rendering: suppresses the first extension line and the
first arrow if possible for continued dimension entities
dimstyle: dimension style name (DimStyle table entry),
default is "EZDXF"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
discard: discard rendering result for friendly CAD applications like
BricsCAD to get a native and likely better rendering result.
(does not work with AutoCAD)
"""
dxfattribs = dict(dxfattribs or {})
multi_point_linear_dimension(
cast("GenericLayoutType", self),
base=base,
points=points,
angle=angle,
ucs=ucs,
avoid_double_rendering=avoid_double_rendering,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
discard=discard,
)
def add_aligned_dim(
self,
p1: UVec,
p2: UVec,
distance: float,
text: str = "<>",
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add linear dimension aligned with measurement points `p1` and `p2`. If
an :class:`~ezdxf.math.UCS` is used for dimension line rendering, all
point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector
is defined by UCS or (0, 0, 1) by default.
See also: :ref:`tut_linear_dimension`
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object,
to create the necessary dimension geometry, you have to call
:meth:`DimStyleOverride.render` manually, this two-step process allows
additional processing steps on the :class:`~ezdxf.entities.Dimension`
entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
p1: measurement point 1 and start point of extension line 1 (in UCS)
p2: measurement point 2 and start point of extension line 2 (in UCS)
distance: distance of dimension line from measurement points
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZDXF"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
_p1 = Vec3(p1)
_p2 = Vec3(p2)
direction = _p2 - _p1
angle = direction.angle_deg
base = direction.orthogonal().normalize(distance) + _p1
return self.add_linear_dim(
base=base,
p1=_p1,
p2=_p2,
dimstyle=dimstyle,
text=text,
angle=angle,
override=override,
dxfattribs=dxfattribs,
)
def add_angular_dim_2l(
self,
base: UVec,
line1: tuple[UVec, UVec],
line2: tuple[UVec, UVec],
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add angular :class:`~ezdxf.entities.Dimension` from two lines. The
measurement is always done from `line1` to `line2` in counter-clockwise
orientation. This does not always match the result in CAD applications!
If an :class:`~ezdxf.math.UCS` is used for angular dimension rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.Dimension` entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
base: location of dimension line, any point on the dimension line or
its extension is valid (in UCS)
line1: specifies start leg of the angle (start point, end point) and
determines extension line 1 (in UCS)
line2: specifies end leg of the angle (start point, end point) and
determines extension line 2 (in UCS)
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
type_ = {"dimtype": const.DIM_ANGULAR | const.DIM_BLOCK_EXCLUSIVE}
dimline: Dimension = self.new_entity("DIMENSION", dxfattribs=type_) # type: ignore
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["text"] = str(text)
dxfattribs["defpoint2"] = Vec3(line1[0]) # group code 13
dxfattribs["defpoint3"] = Vec3(line1[1]) # group code 14
dxfattribs["defpoint4"] = Vec3(line2[0]) # group code 15
dxfattribs["defpoint"] = Vec3(line2[1]) # group code 10
dxfattribs["defpoint5"] = Vec3(base) # group code 16
# text_rotation ALWAYS overrides implicit angles as absolute angle (x-axis=0, y-axis=90)!
if text_rotation is not None:
dxfattribs["text_rotation"] = float(text_rotation)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
style.user_location_override(location)
return style
def add_angular_dim_3p(
self,
base: UVec,
center: UVec,
p1: UVec,
p2: UVec,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add angular :class:`~ezdxf.entities.Dimension` from three points
(center, p1, p2). The measurement is always done from `p1` to `p2` in
counter-clockwise orientation. This does not always match the result in
CAD applications!
If an :class:`~ezdxf.math.UCS` is used for angular dimension rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.Dimension` entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
base: location of dimension line, any point on the dimension line
or its extension is valid (in UCS)
center: specifies the vertex of the angle
p1: specifies start leg of the angle (center -> p1) and end-point
of extension line 1 (in UCS)
p2: specifies end leg of the angle (center -> p2) and end-point
of extension line 2 (in UCS)
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
type_ = {"dimtype": const.DIM_ANGULAR_3P | const.DIM_BLOCK_EXCLUSIVE}
dimline = cast(
"Dimension", self.new_entity("DIMENSION", dxfattribs=type_)
)
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["text"] = str(text)
dxfattribs["defpoint"] = Vec3(base)
dxfattribs["defpoint2"] = Vec3(p1)
dxfattribs["defpoint3"] = Vec3(p2)
dxfattribs["defpoint4"] = Vec3(center)
# text_rotation ALWAYS overrides implicit angles as absolute angle
# (x-axis=0, y-axis=90)!
if text_rotation is not None:
dxfattribs["text_rotation"] = float(text_rotation)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
style.user_location_override(location)
return style
def add_angular_dim_cra(
self,
center: UVec,
radius: float,
start_angle: float,
end_angle: float,
distance: float,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create an angular dimension by (c)enter point,
(r)adius and start- and end (a)ngles, the measurement text is placed at
the default location defined by the associated `dimstyle`.
The measurement is always done from `start_angle` to `end_angle` in
counter-clockwise orientation. This does not always match the result in
CAD applications!
For further information see the more generic factory method
:func:`add_angular_dim_3p`.
Args:
center: center point of the angle (in UCS)
radius: the distance from `center` to the start of the extension
lines in drawing units
start_angle: start angle in degrees (in UCS)
end_angle: end angle in degrees (in UCS)
distance: distance from start of the extension lines to the
dimension line in drawing units
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
sa = float(start_angle)
ea = float(end_angle)
ext_line_start = float(radius)
dim_line_offset = float(distance)
center_ = Vec3(center)
center_angle = sa + arc_angle_span_deg(sa, ea) / 2.0
# ca = (sa + ea) / 2 is not correct: e.g. 30, -30 is 0 but should be 180
base = center_ + Vec3.from_deg_angle(center_angle) * (
ext_line_start + dim_line_offset
)
p1 = center_ + Vec3.from_deg_angle(sa) * ext_line_start
p2 = center_ + Vec3.from_deg_angle(ea) * ext_line_start
return self.add_angular_dim_3p(
base=base,
center=center_,
p1=p1,
p2=p2,
location=location,
text=text,
text_rotation=text_rotation,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_angular_dim_arc(
self,
arc: ConstructionArc,
distance: float,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create an angular dimension from a
:class:`~ezdxf.math.ConstructionArc`. This construction tool can
be created from ARC entities and the tool itself provides various
construction class methods.
The measurement text is placed at the default location defined by the
associated `dimstyle`.
The measurement is always done from `start_angle` to `end_angle` of the
arc in counter-clockwise orientation.
This does not always match the result in CAD applications!
For further information see the more generic factory method
:func:`add_angular_dim_3p`.
Args:
arc: :class:`~ezdxf.math.ConstructionArc`
distance: distance from start of the extension lines to the
dimension line in drawing units
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
return self.add_angular_dim_cra(
center=arc.center,
radius=arc.radius,
start_angle=arc.start_angle,
end_angle=arc.end_angle,
distance=distance,
location=location,
text=text,
text_rotation=text_rotation,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_arc_dim_3p(
self,
base: UVec,
center: UVec,
p1: UVec,
p2: UVec,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add :class:`~ezdxf.entities.ArcDimension` from three points
(center, p1, p2). Point `p1` defines the radius and the start-angle of
the arc, point `p2` only defines the end-angle of the arc.
If an :class:`~ezdxf.math.UCS` is used for arc dimension rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.ArcDimension` entity between creation and
rendering.
.. note::
`Ezdxf` does not render the arc dimension like CAD applications and
does not consider all DIMSTYLE variables, so the rendering results
are **very** different from CAD applications.
Args:
base: location of dimension line, any point on the dimension line
or its extension is valid (in UCS)
center: specifies the vertex of the angle
p1: specifies the radius (center -> p1) and the star angle of the
arc, this is also the start point for the 1st extension line (in UCS)
p2: specifies the end angle of the arc. The start 2nd extension line
is defined by this angle and the radius defined by p1 (in UCS)
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
# always set dimtype to 8 for DXF R2013+, the DXF export handles the
# version dependent dimtype
type_ = {"dimtype": const.DIM_ARC | const.DIM_BLOCK_EXCLUSIVE}
dimline: ArcDimension = self.new_entity( # type: ignore
"ARC_DIMENSION", dxfattribs=type_
)
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["text"] = str(text)
dxfattribs["defpoint"] = Vec3(base)
dxfattribs["defpoint2"] = Vec3(p1)
dxfattribs["defpoint3"] = Vec3(p2)
dxfattribs["defpoint4"] = Vec3(center)
dxfattribs["start_angle"] = 0.0 # unknown meaning
dxfattribs["end_angle"] = 0.0 # unknown meaning
dxfattribs["is_partial"] = 0 # unknown meaning
dxfattribs["has_leader"] = 0 # ignored by ezdxf
dxfattribs["leader_point1"] = NULLVEC # ignored by ezdxf
dxfattribs["leader_point2"] = NULLVEC # ignored by ezdxf
# text_rotation ALWAYS overrides implicit angles as absolute angle
# (x-axis=0, y-axis=90)!
if text_rotation is not None:
dxfattribs["text_rotation"] = float(text_rotation)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
style.user_location_override(location)
return style
def add_arc_dim_cra(
self,
center: UVec,
radius: float,
start_angle: float,
end_angle: float,
distance: float,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create an arc dimension by (c)enter point,
(r)adius and start- and end (a)ngles, the measurement text is placed at
the default location defined by the associated `dimstyle`.
.. note::
`Ezdxf` does not render the arc dimension like CAD applications and
does not consider all DIMSTYLE variables, so the rendering results
are **very** different from CAD applications.
Args:
center: center point of the angle (in UCS)
radius: the distance from `center` to the start of the extension
lines in drawing units
start_angle: start-angle in degrees (in UCS)
end_angle: end-angle in degrees (in UCS)
distance: distance from start of the extension lines to the
dimension line in drawing units
location: user defined location for text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
sa = float(start_angle)
ea = float(end_angle)
ext_line_start = float(radius)
dim_line_offset = float(distance)
center_ = Vec3(center)
center_angle = sa + arc_angle_span_deg(sa, ea) / 2.0
# ca = (sa + ea) / 2 is not correct: e.g. 30, -30 is 0 but should be 180
base = center_ + Vec3.from_deg_angle(center_angle) * (
ext_line_start + dim_line_offset
)
p1 = center_ + Vec3.from_deg_angle(sa) * ext_line_start
p2 = center_ + Vec3.from_deg_angle(ea) * ext_line_start
return self.add_arc_dim_3p(
base=base,
center=center_,
p1=p1,
p2=p2,
location=location,
text=text,
text_rotation=text_rotation,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_arc_dim_arc(
self,
arc: ConstructionArc,
distance: float,
*,
location: Optional[UVec] = None,
text: str = "<>",
text_rotation: Optional[float] = None,
dimstyle: str = "EZ_CURVED",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create an arc dimension from a
:class:`~ezdxf.math.ConstructionArc`. This construction tool can
be created from ARC entities and the tool itself provides various
construction class methods.
The measurement text is placed at the default location defined by the
associated `dimstyle`.
.. note::
`Ezdxf` does not render the arc dimension like CAD applications and
does not consider all DIMSTYLE variables, so the rendering results
are **very** different from CAD applications.
Args:
arc: :class:`~ezdxf.math.ConstructionArc`
distance: distance from start of the extension lines to the
dimension line in drawing units
location: user defined location for the text midpoint (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
text_rotation: rotation angle of the dimension text as absolute
angle (x-axis=0, y-axis=90) in degrees
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_CURVED"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
return self.add_arc_dim_cra(
center=arc.center,
radius=arc.radius,
start_angle=arc.start_angle,
end_angle=arc.end_angle,
distance=distance,
location=location,
text=text,
text_rotation=text_rotation,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_diameter_dim(
self,
center: UVec,
mpoint: Optional[UVec] = None,
radius: Optional[float] = None,
angle: Optional[float] = None,
*,
location: Optional[UVec] = None,
text: str = "<>",
dimstyle: str = "EZ_RADIUS",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add a diameter :class:`~ezdxf.entities.Dimension` line. The diameter
dimension line requires a `center` point and a point `mpoint` on the
circle or as an alternative a `radius` and a dimension line `angle` in
degrees.
If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the :class:`~ezdxf.entities.Dimension`
entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
center: specifies the center of the circle (in UCS)
mpoint: specifies the measurement point on the circle (in UCS)
radius: specify radius, requires argument `angle`, overrides `p1` argument
angle: specify angle of dimension line in degrees, requires argument
`radius`, overrides `p1` argument
location: user defined location for the text midpoint (in UCS)
text: ``None`` or ``"<>"`` the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_RADIUS"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
type_ = {"dimtype": const.DIM_DIAMETER | const.DIM_BLOCK_EXCLUSIVE}
dimline = cast(
"Dimension", self.new_entity("DIMENSION", dxfattribs=type_)
)
center = Vec3(center)
if location is not None:
if radius is None:
raise ValueError("Argument radius is required.")
location = Vec3(location)
# (center - location) just works as expected, but in my
# understanding it should be: (location - center)
radius_vec = (center - location).normalize(length=radius)
else: # defined by mpoint = measurement point on circle
if mpoint is None: # defined by radius and angle
if angle is None:
raise ValueError("Argument angle or mpoint required.")
if radius is None:
raise ValueError("Argument radius or mpoint required.")
radius_vec = Vec3.from_deg_angle(angle, radius)
else:
radius_vec = Vec3(mpoint) - center
p1 = center + radius_vec
p2 = center - radius_vec
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["defpoint"] = Vec3(p1) # group code 10
dxfattribs["defpoint4"] = Vec3(p2) # group code 15
dxfattribs["text"] = str(text)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
style.user_location_override(location)
return style
def add_diameter_dim_2p(
self,
p1: UVec,
p2: UVec,
text: str = "<>",
dimstyle: str = "EZ_RADIUS",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create a diameter dimension by two points on the
circle and the measurement text at the default location defined by the
associated `dimstyle`, for further information see general method
:func:`add_diameter_dim`. Center point of the virtual circle is the
midpoint between `p1` and `p2`.
- dimstyle "EZ_RADIUS": places the dimension text outside
- dimstyle "EZ_RADIUS_INSIDE": places the dimension text inside
Args:
p1: first point of the circle (in UCS)
p2: second point on the opposite side of the center point of the
circle (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_RADIUS"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
mpoint = Vec3(p1)
center = mpoint.lerp(p2)
return self.add_diameter_dim(
center,
mpoint,
text=text,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_radius_dim(
self,
center: UVec,
mpoint: Optional[UVec] = None,
radius: Optional[float] = None,
angle: Optional[float] = None,
*,
location: Optional[UVec] = None,
text: str = "<>",
dimstyle: str = "EZ_RADIUS",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add a radius :class:`~ezdxf.entities.Dimension` line. The radius
dimension line requires a `center` point and a point `mpoint` on the
circle or as an alternative a `radius` and a dimension line `angle` in
degrees. See also: :ref:`tut_radius_dimension`
If a :class:`~ezdxf.math.UCS` is used for dimension line rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.Dimension` entity between creation and rendering.
Following render types are supported:
- Default text location outside: text aligned with dimension line;
dimension style: "EZ_RADIUS"
- Default text location outside horizontal: "EZ_RADIUS" + dimtoh=1
- Default text location inside: text aligned with dimension line;
dimension style: "EZ_RADIUS_INSIDE"
- Default text location inside horizontal: "EZ_RADIUS_INSIDE" + dimtih=1
- User defined text location: argument `location` != ``None``, text
aligned with dimension line; dimension style: "EZ_RADIUS"
- User defined text location horizontal: argument `location` != ``None``,
"EZ_RADIUS" + dimtoh=1 for text outside horizontal, "EZ_RADIUS"
+ dimtih=1 for text inside horizontal
Placing the dimension text at a user defined `location`, overrides the
`mpoint` and the `angle` argument, but requires a given `radius`
argument. The `location` argument does not define the exact text
location, instead it defines the dimension line starting at `center`
and the measurement text midpoint projected on this dimension line
going through `location`, if text is aligned to the dimension line.
If text is horizontal, `location` is the kink point of the dimension
line from radial to horizontal direction.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
center: center point of the circle (in UCS)
mpoint: measurement point on the circle, overrides `angle` and
`radius` (in UCS)
radius: radius in drawing units, requires argument `angle`
angle: specify angle of dimension line in degrees, requires
argument `radius`
location: user defined dimension text location, overrides `mpoint`
and `angle`, but requires `radius` (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_RADIUS"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
type_ = {"dimtype": const.DIM_RADIUS | const.DIM_BLOCK_EXCLUSIVE}
dimline = cast(
"Dimension", self.new_entity("DIMENSION", dxfattribs=type_)
)
center = Vec3(center)
if location is not None:
if radius is None:
raise ValueError("Argument radius is required.")
location = Vec3(location)
radius_vec = (location - center).normalize(length=radius)
mpoint = center + radius_vec
else: # defined by mpoint = measurement point on circle
if mpoint is None: # defined by radius and angle
if angle is None:
raise ValueError("Argument angle or mpoint required.")
if radius is None:
raise ValueError("Argument radius or mpoint required.")
mpoint = center + Vec3.from_deg_angle(angle, radius)
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["defpoint4"] = Vec3(mpoint) # group code 15
dxfattribs["defpoint"] = Vec3(center) # group code 10
dxfattribs["text"] = str(text)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
if location is not None:
style.user_location_override(location)
return style
def add_radius_dim_2p(
self,
center: UVec,
mpoint: UVec,
*,
text: str = "<>",
dimstyle: str = "EZ_RADIUS",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create a radius dimension by center point,
measurement point on the circle and the measurement text at the default
location defined by the associated `dimstyle`, for further information
see general method :func:`add_radius_dim`.
- dimstyle "EZ_RADIUS": places the dimension text outside
- dimstyle "EZ_RADIUS_INSIDE": places the dimension text inside
Args:
center: center point of the circle (in UCS)
mpoint: measurement point on the circle (in UCS)
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_RADIUS"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
return self.add_radius_dim(
center,
mpoint,
text=text,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_radius_dim_cra(
self,
center: UVec,
radius: float,
angle: float,
*,
text: str = "<>",
dimstyle: str = "EZ_RADIUS",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Shortcut method to create a radius dimension by (c)enter point,
(r)adius and (a)ngle, the measurement text is placed at the default
location defined by the associated `dimstyle`, for further information
see general method :func:`add_radius_dim`.
- dimstyle "EZ_RADIUS": places the dimension text outside
- dimstyle "EZ_RADIUS_INSIDE": places the dimension text inside
Args:
center: center point of the circle (in UCS)
radius: radius in drawing units
angle: angle of dimension line in degrees
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZ_RADIUS"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
return self.add_radius_dim(
center,
radius=radius,
angle=angle,
text=text,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_ordinate_dim(
self,
feature_location: UVec,
offset: UVec,
dtype: int,
*,
origin: UVec = NULLVEC,
rotation: float = 0.0,
text: str = "<>",
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""
Add an ordinate type :class:`~ezdxf.entities.Dimension` line. The
feature location is defined in the global coordinate system, which is
set as render UCS, which is the :ref:`WCS` by default.
If an :class:`~ezdxf.math.UCS` is used for dimension line rendering,
all point definitions in UCS coordinates, translation into :ref:`WCS`
and :ref:`OCS` is done by the rendering function. Extrusion vector is
defined by UCS or (0, 0, 1) by default.
This method returns a :class:`~ezdxf.entities.DimStyleOverride` object -
to create the necessary dimension geometry, you have to call
:meth:`~ezdxf.entities.DimStyleOverride.render` manually, this two-step
process allows additional processing steps on the
:class:`~ezdxf.entities.Dimension` entity between creation and rendering.
.. note::
`Ezdxf` does not consider all DIMSTYLE variables, so the
rendering results are different from CAD applications.
Args:
feature_location: feature location in the global coordinate system (UCS)
offset: offset vector of leader end point from the feature location
in the local coordinate system
dtype: 1 = x-type, 0 = y-type
origin: specifies the origin (0, 0) of the local coordinate
system in UCS
rotation: rotation angle of the local coordinate system in degrees
text: ``None`` or "<>" the measurement is drawn as text,
" " (a single space) suppresses the dimension text,
everything else `text` is drawn as dimension text
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZDXF"
override: :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes for the DIMENSION entity
Returns: :class:`~ezdxf.entities.DimStyleOverride`
"""
dtype = int(dtype)
if dtype not in (0, 1):
raise DXFValueError("invalid dtype (0, 1)")
type_ = {
"dimtype": const.DIM_ORDINATE
| const.DIM_BLOCK_EXCLUSIVE
| (const.DIM_ORDINATE_TYPE * dtype)
}
dimline = cast(
"Dimension", self.new_entity("DIMENSION", dxfattribs=type_)
)
origin_ = Vec3(origin)
feature_location_ = Vec3(feature_location)
end_point_ = feature_location_ + Vec3(offset)
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs["defpoint"] = origin_ # group code 10
rotation = float(rotation)
if rotation:
# Horizontal direction in clockwise orientation, see DXF reference
# for group code 51:
dxfattribs["horizontal_direction"] = -rotation
relative_feature_location = feature_location_ - origin_
dxfattribs[
"defpoint2"
] = origin_ + relative_feature_location.rotate_deg(rotation)
dxfattribs["defpoint3"] = end_point_.rotate_deg(rotation)
dxfattribs["text"] = str(text)
dimline.update_dxf_attribs(dxfattribs)
style = DimStyleOverride(dimline, override=override)
return style
def add_ordinate_x_dim(
self,
feature_location: UVec,
offset: UVec,
*,
origin: UVec = NULLVEC,
rotation: float = 0.0,
text: str = "<>",
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""Shortcut to add an x-type feature ordinate DIMENSION, for more
information see :meth:`add_ordinate_dim`.
"""
return self.add_ordinate_dim(
feature_location=feature_location,
offset=offset,
dtype=1,
origin=origin,
rotation=rotation,
text=text,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_ordinate_y_dim(
self,
feature_location: UVec,
offset: UVec,
*,
origin: UVec = NULLVEC,
rotation: float = 0.0,
text: str = "<>",
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> DimStyleOverride:
"""Shortcut to add a y-type feature ordinate DIMENSION, for more
information see :meth:`add_ordinate_dim`.
"""
return self.add_ordinate_dim(
feature_location=feature_location,
offset=offset,
dtype=0,
origin=origin,
rotation=rotation,
text=text,
dimstyle=dimstyle,
override=override,
dxfattribs=dxfattribs,
)
def add_arrow(
self,
name: str,
insert: UVec,
size: float = 1.0,
rotation: float = 0,
dxfattribs=None,
) -> Vec3:
return ARROWS.render_arrow(
self, # type: ignore
name=name,
insert=insert,
size=size,
rotation=rotation,
dxfattribs=dxfattribs,
).vec3
def add_arrow_blockref(
self,
name: str,
insert: UVec,
size: float = 1.0,
rotation: float = 0,
dxfattribs=None,
) -> Vec3:
return ARROWS.insert_arrow(
self, # type: ignore
name=name,
insert=insert,
size=size,
rotation=rotation,
dxfattribs=dxfattribs,
).vec3
def add_leader(
self,
vertices: Iterable[UVec],
dimstyle: str = "EZDXF",
override: Optional[dict] = None,
dxfattribs=None,
) -> Leader:
"""
The :class:`~ezdxf.entities.Leader` entity represents an arrow, made up
of one or more vertices (or spline fit points) and an arrowhead.
The label or other content to which the :class:`~ezdxf.entities.Leader`
is attached is stored as a separate entity, and is not part of the
:class:`~ezdxf.entities.Leader` itself. (requires DXF R2000)
:class:`~ezdxf.entities.Leader` shares its styling infrastructure with
:class:`~ezdxf.entities.Dimension`.
By default a :class:`~ezdxf.entities.Leader` without any annotation is
created. For creating more fancy leaders and annotations see
documentation provided by Autodesk or `Demystifying DXF: LEADER and MULTILEADER
implementation notes <https://atlight.github.io/formats/dxf-leader.html>`_ .
Args:
vertices: leader vertices (in :ref:`WCS`)
dimstyle: dimension style name (:class:`~ezdxf.entities.DimStyle`
table entry), default is "EZDXF"
override: override :class:`~ezdxf.entities.DimStyleOverride` attributes
dxfattribs: additional DXF attributes
"""
def filter_unsupported_dimstyle_attributes(attribs: dict) -> dict:
return {
k: v
for k, v in attribs.items()
if k not in LEADER_UNSUPPORTED_DIMSTYLE_ATTRIBS
}
if self.dxfversion < DXF2000:
raise DXFVersionError("LEADER requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
dxfattribs["dimstyle"] = self._safe_dimstyle(dimstyle)
dxfattribs.setdefault("annotation_type", 3)
leader = cast("Leader", self.new_entity("LEADER", dxfattribs))
leader.set_vertices(vertices)
if override:
override = filter_unsupported_dimstyle_attributes(override)
if "dimldrblk" in override:
self.doc.acquire_arrow(override["dimldrblk"])
# Class Leader() supports the required OverrideMixin() interface
DimStyleOverride(
cast("Dimension", leader), override=override
).commit()
return leader
def add_multileader_mtext(
self,
style: str = "Standard",
dxfattribs=None,
) -> MultiLeaderMTextBuilder:
"""Add a :class:`~ezdxf.entities.MultiLeader` entity but returns
a :class:`~ezdxf.render.MultiLeaderMTextBuilder`.
"""
from ezdxf.render.mleader import MultiLeaderMTextBuilder
multileader = self._make_multileader(style, dxfattribs)
return MultiLeaderMTextBuilder(multileader)
def add_multileader_block(
self,
style: str = "Standard",
dxfattribs=None,
) -> MultiLeaderBlockBuilder:
"""Add a :class:`~ezdxf.entities.MultiLeader` entity but returns
a :class:`~ezdxf.render.MultiLeaderBlockBuilder`.
"""
from ezdxf.render.mleader import MultiLeaderBlockBuilder
multileader = self._make_multileader(style, dxfattribs)
return MultiLeaderBlockBuilder(multileader)
def _make_multileader(
self,
style: str,
dxfattribs=None,
) -> MultiLeader:
if self.dxfversion < DXF2000:
raise DXFVersionError("MULTILEADER requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
mleader_style = self.doc.mleader_styles.get(style)
if mleader_style is None:
raise DXFValueError(f"MLEADERSTYLE '{style}' does not exist")
dxfattribs["style_handle"] = mleader_style.dxf.handle
return self.new_entity("MULTILEADER", dxfattribs=dxfattribs) # type: ignore
def add_mline(
self,
vertices: Optional[Iterable[UVec]] = None,
*,
close: bool = False,
dxfattribs=None,
) -> MLine:
"""Add a :class:`~ezdxf.entities.MLine` entity
Args:
vertices: MLINE vertices (in :ref:`WCS`)
close: ``True`` to add a closed MLINE
dxfattribs: additional DXF attributes
"""
if self.dxfversion < DXF2000:
raise DXFVersionError("MLine requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
style_name = dxfattribs.pop("style_name", "Standard")
mline: MLine = self.new_entity("MLINE", dxfattribs) # type: ignore
# close() method regenerates geometry!
mline.set_flag_state(mline.CLOSED, close)
mline.set_style(style_name)
if vertices:
mline.extend(vertices)
return mline
def add_helix(
self,
radius: float,
pitch: float,
turns: float,
ccw=True,
dxfattribs=None,
) -> Helix:
"""
Add a :class:`~ezdxf.entities.Helix` entity.
The center of the helix is always (0, 0, 0) and the helix axis direction
is the +z-axis.
Transform the new HELIX by the :meth:`~ezdxf.entities.DXFGraphic.transform`
method to your needs.
Args:
radius: helix radius
pitch: the height of one complete helix turn
turns: count of turns
ccw: creates a counter-clockwise turning (right-handed) helix if ``True``
dxfattribs: additional DXF attributes
"""
from ezdxf import path
if self.dxfversion < DXF2000:
raise DXFVersionError("Helix requires DXF R2000")
dxfattribs = dict(dxfattribs or {})
helix: Helix = self.new_entity("HELIX", dxfattribs) # type: ignore
base = Vec3(0, 0, 0)
helix.dxf.axis_base_point = base
helix.dxf.radius = float(radius)
helix.dxf.start_point = base + (radius, 0, 0)
helix.dxf.axis_vector = Vec3(0, 0, 1 if pitch > 0 else -1)
helix.dxf.turns = turns
helix.dxf.turn_height = pitch
helix.dxf.handedness = int(ccw)
helix.dxf.constrain = 1 # turns
p = path.helix(radius, pitch, turns, ccw)
splines = list(path.to_bsplines_and_vertices(p))
if splines:
helix.apply_construction_tool(splines[0])
return helix
LEADER_UNSUPPORTED_DIMSTYLE_ATTRIBS = {"dimblk", "dimblk1", "dimblk2"}