2686 lines
102 KiB
Python
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"}
|