# Created: 10.03.2013 # Copyright (c) 2013-2018, Manfred Moitzi # License: MIT License from typing import TYPE_CHECKING, Iterable, Sequence, Union, Dict, Tuple, cast import math import logging from ezdxf.lldxf import const from ezdxf.lldxf.const import DXFValueError, DXFVersionError from ezdxf.math import Vector from ezdxf.math import bspline_control_frame, bspline_control_frame_approx from ezdxf.render.arrows import ARROWS from ezdxf.dimstyleoverride import DimStyleOverride from ezdxf.render.dimension import multi_point_linear_dimension logger = logging.getLogger('ezdxf') if TYPE_CHECKING: # import forward references from ezdxf.eztypes import DXFFactoryType, DXFEntity, Spline, Text, ImageDef, Image, Line, Point, Circle, Arc, Shape from ezdxf.eztypes import Solid, Trace, Face, Insert, Attrib, Polyline, Polyface, Polymesh, UnderlayDef, Underlay from ezdxf.eztypes import Hatch, Mesh, LWPolyline, Ellipse, MText, Ray, XLine, Dimension, DimStyleOverride from ezdxf.eztypes import Solid3d, Region, Body, Surface, RevolvedSurface, ExtrudedSurface, SweptSurface, \ LoftedSurface from ezdxf.eztypes import UCS, GenericLayoutType, Vertex def copy_attribs(dxfattribs: dict = None) -> dict: return dict(dxfattribs or {}) class GraphicsFactory: """ Abstract base class for BaseLayout() """ def __init__(self, dxffactory: 'DXFFactoryType'): self._dxffactory = dxffactory @property def dxfversion(self) -> str: return self._dxffactory.dxfversion def build_and_add_entity(self, type_: str, dxfattribs: dict): raise NotImplementedError("Abstract method call.") def add_point(self, location: 'Vertex', dxfattribs: dict = None) -> 'Point': """ Add a :class:`Point` element at `location`. Args: location: 2D/3D point in :ref:`WCS` dxfattribs (dist): additional DXF attributes for :class:`Point` entity Returns: :class:`Point` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['location'] = location return self.build_and_add_entity('POINT', dxfattribs) def add_line(self, start: 'Vertex', end: 'Vertex', dxfattribs: dict = None) -> 'Line': """ Add a :class:`Line` element from `start` to `end`. Args: start: 2D/3D point in :ref:`WCS` end: 2D/3D point in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Line` entity Returns: :class:`Line` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['start'] = start dxfattribs['end'] = end return self.build_and_add_entity('LINE', dxfattribs) def add_circle(self, center: 'Vertex', radius: float, dxfattribs: dict = None) -> 'Circle': """ Add a :class:`Circle` element. 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 (dcit): additional DXF attributes for :class:`Circle` entity Returns: :class:`Circle` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['center'] = center dxfattribs['radius'] = radius return self.build_and_add_entity('CIRCLE', dxfattribs) def add_ellipse(self, center: 'Vertex', major_axis: 'Vertex' = (1, 0, 0), ratio: float = 1, start_param: float = 0, end_param: float = 2*math.pi, dxfattribs: dict = None) -> 'Ellipse': """ Add an :class:`Ellipse` element, `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*pi. 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 start_param: start of ellipse curve end_param: end param of ellipse curve dxfattribs (dict): additional DXF attributes for :class:`Ellipse` entity Returns: :class:`Ellipse` """ if self.dxfversion < 'AC1015': raise DXFVersionError('ELLIPSE requires DXF version R2000+') if ratio > 1.: raise DXFValueError("Parameter 'ratio' has to be <= 1.0") dxfattribs = copy_attribs(dxfattribs) dxfattribs['center'] = center dxfattribs['major_axis'] = major_axis dxfattribs['ratio'] = ratio dxfattribs['start_param'] = start_param dxfattribs['end_param'] = end_param return self.build_and_add_entity('ELLIPSE', dxfattribs) def add_arc(self, center: 'Vertex', radius: float, start_angle: float, end_angle: float, is_counter_clockwise: bool = True, dxfattribs: dict = None) -> 'Arc': """ Add an :class:`Arc` element. 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 (dict): additional DXF attributes for :class:`Arc` entity Returns: :class:`Arc` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['center'] = center dxfattribs['radius'] = radius if is_counter_clockwise: dxfattribs['start_angle'] = start_angle dxfattribs['end_angle'] = end_angle else: dxfattribs['start_angle'] = end_angle dxfattribs['end_angle'] = start_angle return self.build_and_add_entity('ARC', dxfattribs) def add_solid(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'Solid': """ Add a :class:`Solid` element, `points` is an iterable of 3 or 4 points. Args: points: iterable of 3 or 4 2D/3D points in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Solid` entity Returns: :class:`Solid` """ return cast('Solid', self._add_quadrilateral('SOLID', points, dxfattribs)) def add_trace(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'Trace': """ Add a :class:`Trace` element, `points` is an iterable of 3 or 4 points. Args: points: iterable of 3 or 4 2D/3D points in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Trace` entity Returns: :class:`Trace` """ return cast('Trace', self._add_quadrilateral('TRACE', points, dxfattribs)) def add_3dface(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'Face': """ Add a :class:`3DFace` element, `points` is an iterable 3 or 4 2D/3D points. Args: points: iterable of 3 or 4 2D/3D points in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`3DFace` entity Returns: :class:`3DFace` """ return cast('Face', self._add_quadrilateral('3DFACE', points, dxfattribs)) def add_text(self, text: str, dxfattribs: dict = None) -> 'Text': """ Add a :class:`Text` element, see also :class:`Style`. Args: text: content string dxfattribs (dict): additional DXF attributes for :class:`Text` entity Returns: :class:`Text` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['text'] = text dxfattribs.setdefault('insert', (0, 0)) return self.build_and_add_entity('TEXT', dxfattribs) def add_blockref(self, name: str, insert: 'Vertex', dxfattribs: dict = None) -> 'Insert': """ Add an :class:`Insert` element. Args: name: block name insert: insert location as 2D/3D point in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Insert` entity Returns: :class:`Insert` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['name'] = name dxfattribs['insert'] = insert blockref = self.build_and_add_entity('INSERT', dxfattribs) return blockref def add_auto_blockref(self, name: str, insert: 'Vertex', values: Dict[str, str], dxfattribs: dict = None) \ -> 'Insert': """ Add an :class:`Insert` element. This method adds for each :class:`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. The `values` dict defines the tag/value attributes as key=tag, value=tag value as string. The Attrib entities are placed relative to the insert point, which is equal to the block base point. Args: name: block name insert: insert location as 2D/3D point in :ref:`WCS` values (dict): :class:`Attrib` tag values as key=tag, value=tag value pairs dxfattribs (dict): additional DXF attributes for :class:`Insert` entity Returns: :class:`Insert` """ def get_dxfattribs(attdef) -> dict: dxfattribs = attdef.dxfattribs() dxfattribs.pop('prompt', None) dxfattribs.pop('handle', None) return dxfattribs def unpack(dxfattribs) -> Tuple[str, str, 'Vertex']: tag = dxfattribs.pop('tag') text = values.get(tag, "") insert = dxfattribs.pop('insert') return tag, text, insert def autofill(blockref, blockdef) -> None: # ATTRIBs are placed relative to the base point for attdef in blockdef.attdefs(): dxfattribs = get_dxfattribs(attdef) tag, text, insert = unpack(dxfattribs) blockref.add_attrib(tag, text, insert, dxfattribs) dxfattribs = copy_attribs(dxfattribs) autoblock = self._dxffactory.blocks.new_anonymous_block() blockref = autoblock.add_blockref(name, (0, 0)) blockdef = self._dxffactory.blocks[name] autofill(blockref, blockdef) return self.add_blockref(autoblock.name, insert, dxfattribs) def add_attrib(self, tag: str, text: str, insert: 'Vertex' = (0, 0), dxfattribs: dict = None) -> 'Attrib': """ Add an :class:`Attrib` as stand alone DXF entity. Args: tag: tag name as string text: tag value as string insert: insert location as 2D/3D point in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Attrib` entity Returns: :class:`Attrib` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['tag'] = tag dxfattribs['text'] = text dxfattribs['insert'] = insert return self.build_and_add_entity('ATTRIB', dxfattribs) def add_polyline2d(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'Polyline': """ Add a 2D :class:`Polyline` entity. Args: points: iterable of 2D points in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Polyline` entity Returns: :class:`Polyline` """ dxfattribs = copy_attribs(dxfattribs) closed = dxfattribs.pop('closed', False) polyline = self.build_and_add_entity('POLYLINE', dxfattribs) polyline.close(closed) polyline.append_vertices(points) return polyline def add_polyline3d(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'Polyline': """ Add a 3D :class:`Polyline` entity. Args: points: iterable of 3D points in :ref:`WCS` dxfattribs (dict): additional DXF attributes for :class:`Polyline` entity Returns: :class:`Polyline` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['flags'] = dxfattribs.get('flags', 0) | const.POLYLINE_3D_POLYLINE return self.add_polyline2d(points, dxfattribs) def add_polymesh(self, size: Tuple[int, int] = (3, 3), dxfattribs: dict = None) -> 'Polymesh': """ Add a :class:`Polymesh` entity, which is represented as :class:`Polyline` entity in the DXF file. A polymesh is a grid of `mcount` x `ncount` vertices and every vertex has its own xyz-coordinates. Args: size: 2-tuple (`mcount`, `ncount`) dxfattribs (dict): additional DXF attributes for :class:`Polyline` entity Returns: :class:`Polymesh` """ dxfattribs = copy_attribs(dxfattribs) 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 = self.build_and_add_entity('POLYLINE', dxfattribs) points = [(0, 0, 0)] * (m_size * n_size) polymesh.append_vertices(points) # init mesh vertices polymesh.close(m_close, n_close) return polymesh.cast() def add_polyface(self, dxfattribs: dict = None) -> 'Polyface': """ Add a :class:`Polyface` entity, which is represented as :class:`Polyline` entity in the DXF file. Args: dxfattribs (dict): additional DXF attributes for :class:`Polyline` entity Returns: :class:`Polyface` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['flags'] = dxfattribs.get('flags', 0) | const.POLYLINE_POLYFACE m_close = dxfattribs.pop('m_close', False) n_close = dxfattribs.pop('n_close', False) polyface = self.build_and_add_entity('POLYLINE', dxfattribs) polyface.close(m_close, n_close) return polyface.cast() def _add_quadrilateral(self, type_: str, points: Iterable['Vertex'], dxfattribs: dict = None) -> 'DXFEntity': dxfattribs = copy_attribs(dxfattribs) entity = self.build_and_add_entity(type_, dxfattribs) for x, point in enumerate(self._four_points(points)): entity[x] = point return entity @staticmethod def _four_points(points: Iterable['Vertex']) -> Iterable['Vertex']: 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: 'Vertex' = (0, 0), size: float = 1.0, dxfattribs: dict = None) -> 'Shape': """ Add a :class:`Shape` reference to a external stored shape. Args: name: shape name as string insert: insert location as 2D/3D point in :ref:`WCS` size: size factor dxfattribs (dict): additional DXF attributes for :class:`Shape` entity Returns: :class:`Shape` """ dxfattribs = copy_attribs(dxfattribs) dxfattribs['name'] = name dxfattribs['insert'] = insert dxfattribs['size'] = size return self.build_and_add_entity('SHAPE', dxfattribs) # new entities in DXF AC1015 (R2000) def add_lwpolyline(self, points: Iterable['Vertex'], format: str = 'xyseb', dxfattribs: dict = None) -> 'LWPolyline': """ Add a 2D polyline as :class:`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 (x, y, 0, 0, bulge). The :class:`LWPolyline` is defined as a single DXF entity and needs less disk space and memory than a :class:`Polyline` entity. (requires DXF R2000+) Args: points: iterable of (x, y, [start_width, [end_width, [bulge]]]) tuples format: user defined point format, default is "xyseb" dxfattribs (dict): additional DXF attributes for :class:`LWPolyline` entity Returns: :class:`LWPolyline` """ if self.dxfversion < 'AC1015': raise DXFVersionError('LWPOLYLINE requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) closed = dxfattribs.pop('closed', False) lwpolyline = self.build_and_add_entity('LWPOLYLINE', dxfattribs) lwpolyline.set_points(points, format=format) lwpolyline.closed = closed return lwpolyline def add_mtext(self, text: str, dxfattribs: dict = None) -> 'MText': """ Add a multiline text element with automatic text wrapping at boundaries as :class:`MText` entity. (requires DXF R2000+) Args: text: content string dxfattribs (dict): additional DXF attributes for :class:`MText` entity Returns: :class:`MText` """ if self.dxfversion < 'AC1015': raise DXFVersionError('MTEXT requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) mtext = self.build_and_add_entity('MTEXT', dxfattribs) mtext.set_text(text) return mtext def add_ray(self, start: 'Vertex', unit_vector: 'Vertex', dxfattribs: dict = None) -> 'Ray': """ Add a :class:`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 (dict): additional DXF attributes for :class:`Ray` entity Returns: :class:`Ray` """ if self.dxfversion < 'AC1015': raise DXFVersionError('RAY requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) dxfattribs['start'] = start dxfattribs['unit_vector'] = unit_vector return self.build_and_add_entity('RAY', dxfattribs) def add_xline(self, start: 'Vertex', unit_vector: 'Vertex', dxfattribs: dict = None) -> 'XLine': """ Add an infinity :class:`XLine` (construction line). (requires DXF R2000+) Args: start: location 3D point in :ref:`WCS` unit_vector: 3D vector (x, y, z) dxfattribs (dict): additional DXF attributes for :class:`XLine` entity Returns: :class:`XLine` """ if self.dxfversion < 'AC1015': raise DXFVersionError('XLINE requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) dxfattribs['start'] = start dxfattribs['unit_vector'] = unit_vector return self.build_and_add_entity('XLINE', dxfattribs) def add_spline(self, fit_points: Iterable['Vertex'] = None, degree: int = 3, dxfattribs: dict = None) -> 'Spline': """ Add a B-spline defined by 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. (requires DXF R2000+) AutoCAD creates a spline through fit points by a proprietary algorithm. `ezdxf` can not reproduce the control point calculation. Args: fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS`, if None -> user defined spline degree: degree fo B-spline dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ if self.dxfversion < 'AC1015': raise DXFVersionError('SPLINE requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) dxfattribs['degree'] = degree spline = self.build_and_add_entity('SPLINE', dxfattribs) if fit_points is not None: spline.set_fit_points(list(fit_points)) return spline def add_spline_control_frame(self, fit_points: Iterable['Vertex'], degree: int = 3, method: str = 'distance', power: float = .5, dxfattribs: dict = None) -> 'Spline': """ Create and add B-spline control frame from fit points. 1. method = "uniform", creates a uniform t vector, [0...1] equally spaced 2. method = "distance", creates a t vector with values proportional to the fit point distances 3. method = "centripetal", creates a t vector with values proportional to the fit point distances^power None of this methods matches the spline created from fit points by AutoCAD. Args: fit_points: iterable of fit points as (x, y[, z]) in :ref:`WCS` degree: degree of B-spline method: calculation method for parameter vector t power: power for centripetal method dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ bspline = bspline_control_frame(fit_points, degree=degree, method=method, power=power) return self.add_open_spline( control_points=bspline.control_points, degree=bspline.degree, knots=bspline.knot_values(), dxfattribs=dxfattribs, ) def add_spline_approx(self, fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance', power: float = .5, dxfattribs: dict = None) -> 'Spline': """ Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline. 1. method = 'uniform', creates a uniform t vector, [0...1] equally spaced 2. method = 'distance', creates a t vector with values proportional to the fit point distances 3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power (requires DXF R2000+) Args: fit_points: all fit points of B-spline count: count of designated control points degree: degree of B-spline method: calculation method for parameter vector t power: power for centripetal method dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ bspline = bspline_control_frame_approx(fit_points, count, degree=degree, method=method, power=power) return self.add_open_spline( control_points=bspline.control_points, degree=bspline.degree, dxfattribs=dxfattribs, ) def add_open_spline(self, control_points: Iterable['Vertex'], degree: int = 3, knots: Iterable[float] = None, dxfattribs: dict = None) -> 'Spline': """ Add an open uniform :class:`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 knots: knot values as iterable of floats dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ spline = self.add_spline(dxfattribs=dxfattribs) spline.set_open_uniform(list(control_points), degree) if knots is not None: spline.set_knot_values(list(knots)) return spline def add_closed_spline(self, control_points: Iterable['Vertex'], degree: int = 3, knots: Iterable[float] = None, dxfattribs: dict = None) -> 'Spline': """ Add a closed uniform :class:`Spline` defined by `control_points`. (requires DXF R2000+) Closed uniform B-splines is a closed curve start and end at the first control point. Args: control_points: iterable of 3D points in :ref:`WCS` degree: degree of B-spline knots: knot values as iterable of floats dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ spline = self.add_spline(dxfattribs=dxfattribs) spline.set_periodic(list(control_points), degree) if knots is not None: spline.set_knot_values(list(knots)) return spline def add_rational_spline(self, control_points: Iterable['Vertex'], weights: Sequence[float], degree: int = 3, knots: Iterable[float] = None, dxfattribs: dict = None) -> 'Spline': """ Add an open rational uniform :class:`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 knots: knot values as iterable of floats dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ spline = self.add_spline(dxfattribs=dxfattribs) spline.set_open_rational(list(control_points), weights, degree) if knots is not None: spline.set_knot_values(list(knots)) return spline def add_closed_rational_spline(self, control_points: Iterable['Vertex'], weights: Sequence[float], degree: int = 3, knots: Iterable[float] = None, dxfattribs: dict = None) -> 'Spline': """ Add a closed rational uniform :class:`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. Closed rational uniform B-splines start and end at the first control point. Args: control_points: iterable of 3D points in :ref:`WCS` weights: weight values as iterable of floats degree: degree of B-spline knots: knot values as iterable of floats dxfattribs (dict): additional DXF attributes for :class:`Spline` entity Returns: :class:`Spline` """ spline = self.add_spline(dxfattribs=dxfattribs) spline.set_periodic_rational(list(control_points), weights, degree) if knots is not None: spline.set_knot_values(list(knots)) return spline def add_body(self, acis_data: str = None, dxfattribs: dict = None) -> 'Body': """ Add a :class:`Body` entity. (requires DXF R2000+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`Body` entity Returns: :class:`Body` """ return self._add_acis_entiy('BODY', acis_data, dxfattribs) def add_region(self, acis_data: str = None, dxfattribs: dict = None) -> 'Region': """ Add a :class:`Region` entity. (requires DXF R2000+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`Region` entity Returns: :class:`Region` """ return cast('Region', self._add_acis_entiy('REGION', acis_data, dxfattribs)) def add_3dsolid(self, acis_data: str = None, dxfattribs: dict = None) -> 'Solid3d': """ Add a :class:`3DSolid` entity. (requires DXF R2000+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`3DSolid` entity Returns: :class:`3DSolid` """ return cast('Solid3d', self._add_acis_entiy('3DSOLID', acis_data, dxfattribs)) def add_surface(self, acis_data: str = None, dxfattribs: dict = None) -> 'Surface': """ Add a :class:`Surface` entity. (requires DXF R2007+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`Surface` entity Returns: :class:`Surface` """ if self.dxfversion < 'AC1021': raise DXFVersionError('SURFACE requires DXF version R2007+') return cast('Surface', self._add_acis_entiy('SURFACE', acis_data, dxfattribs)) def add_extruded_surface(self, acis_data: str = None, dxfattribs: dict = None) -> 'ExtrudedSurface': """ Add a :class:`ExtrudedSurface` entity. (requires DXF R2007+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`ExtrudedSurface` entity Returns: :class:`ExtrudedSurface` """ if self.dxfversion < 'AC1021': raise DXFVersionError('EXTRUDEDSURFACE requires DXF version R2007+') return cast('ExtrudedSurface', self._add_acis_entiy('EXTRUDEDSURFACE', acis_data, dxfattribs)) def add_lofted_surface(self, acis_data: str = None, dxfattribs: dict = None) -> 'LoftedSurface': """ Add a :class:`LoftedSurface` entity. (requires DXF R2007+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`LoftedSurface` entity Returns: :class:`LoftedSurface` """ if self.dxfversion < 'AC1021': raise DXFVersionError('LOFTEDSURFACE requires DXF version R2007+') return cast('LoftedSurface', self._add_acis_entiy('LOFTEDSURFACE', acis_data, dxfattribs)) def add_revolved_surface(self, acis_data: str = None, dxfattribs: dict = None) -> 'RevolvedSurface': """ Add a :class:`RevolvedSurface` entity. (requires DXF R2007+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`RevolvedSurface` entity Returns: :class:`RevolvedSurface` """ if self.dxfversion < 'AC1021': raise DXFVersionError('REVOLVEDSURFACE requires DXF version R2007+') return cast('RevolvedSurface', self._add_acis_entiy('REVOLVEDSURFACE', acis_data, dxfattribs)) def add_swept_surface(self, acis_data: str = None, dxfattribs: dict = None) -> 'SweptSurface': """ Add a :class:`SweptSurface` entity. (requires DXF R2007+) Args: acis_data: ACIS data as iterable of text lines as strings, no interpretation by ezdxf possible dxfattribs (dict): additional DXF attributes for :class:`SweptSurface` entity Returns: :class:`SweptSurface` """ if self.dxfversion < 'AC1021': raise DXFVersionError('SWEPT requires DXF version R2007+') return cast('SweptSurface', self._add_acis_entiy('SWEPTSURFACE', acis_data, dxfattribs)) def _add_acis_entiy(self, name, acis_data: str, dxfattribs: dict) -> 'Body': if self.dxfversion < 'AC1015': raise DXFVersionError('{} requires DXF version R2000+'.format(name)) dxfattribs = copy_attribs(dxfattribs) entity = self.build_and_add_entity(name, dxfattribs) if acis_data is not None: entity.set_acis_data(acis_data) return entity def add_hatch(self, color: int = 7, dxfattribs: dict = None) -> 'Hatch': """ Add a :class:`Hatch` entity. (requires DXF R2007+) Args: color: ACI (AutoCAD Color Index), default is 7 (black/white). dxfattribs (dict): additional DXF attributes for :class:`Hatch` entity Returns: :class:`Hatch` """ if self.dxfversion < 'AC1015': raise DXFVersionError('HATCH requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) dxfattribs['solid_fill'] = 1 dxfattribs['color'] = color dxfattribs['pattern_name'] = 'SOLID' return self.build_and_add_entity('HATCH', dxfattribs) def add_mesh(self, dxfattribs: dict = None) -> 'Mesh': """ Add a :class:`Mesh` entity. (requires DXF R2007+) Args: dxfattribs (dict): additional DXF attributes for :class:`Mesh` entity Returns: :class:`Mesh` """ if self.dxfversion < 'AC1015': raise DXFVersionError('MESH requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) return self.build_and_add_entity('MESH', dxfattribs) def add_image(self, image_def: 'ImageDef', insert: 'Vertex', size_in_units: Tuple[float, float], rotation: float = 0., dxfattribs: dict = None) -> 'Image': """ Add an :class:`Image` entity. Create :class:`ImageDef` by the :class:`Drawing` factory function :meth:`~Drawing.add_image_def`, see :ref:`tut_image`. (requires DXF R2000+) Args: image_def: required image definition as :class:`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 z-axis in degrees dxfattribs (dict): additional DXF attributes for :class:`Image` entity Returns: :class:`Image` """ def to_vector(units_per_pixel, angle_in_rad): x = math.cos(angle_in_rad) * units_per_pixel y = math.sin(angle_in_rad) * units_per_pixel return round(x, 6), round(y, 6), 0 # supports only images in the xy-plane if self.dxfversion < 'AC1015': raise DXFVersionError('IMAGE requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) x_pixels, y_pixels = image_def.dxf.image_size 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.) dxfattribs['insert'] = Vector(insert) dxfattribs['u_pixel'] = to_vector(x_units_per_pixel, x_angle_rad) dxfattribs['v_pixel'] = to_vector(y_units_per_pixel, y_angle_rad) dxfattribs['image_def'] = image_def.dxf.handle dxfattribs['image_size'] = image_def.dxf.image_size image = self.build_and_add_entity('IMAGE', dxfattribs) if self.drawing is not None: image_def_reactor = self.drawing.objects.add_image_def_reactor(image.dxf.handle) reactor_handle = image_def_reactor.dxf.handle image.dxf.image_def_reactor = reactor_handle image_def.append_reactor_handle(reactor_handle) return image def add_underlay(self, underlay_def: 'UnderlayDef', insert: 'Vertex' = (0, 0, 0), scale: Tuple[float, float, float] = (1, 1, 1), rotation: float = 0., dxfattribs: dict = None) -> 'Underlay': """ Add an :class:`Underlay` entity. Create :class:`UnderlayDef` by the :class:`Drawing` factory function :meth:`~Drawing.add_underlay_def`, see :ref:`tut_underlay`. (requires DXF R2000+) Args: underlay_def: required underlay definition as :class:`UnderlayDef` insert: insertion point as 3D point in :ref:`WCS` scale: underlay scaling factor as (x, y, z) tuple rotation: rotation angle around the z-axis in degrees dxfattribs (dict): additional DXF attributes for :class:`Underlay` entity Returns: :class:`Underlay` """ if self.dxfversion < 'AC1015': raise DXFVersionError('UNDERLAY requires DXF version R2000+') dxfattribs = copy_attribs(dxfattribs) dxfattribs['insert'] = insert dxfattribs['underlay_def'] = underlay_def.dxf.handle dxfattribs['rotation'] = rotation underlay = self.build_and_add_entity(underlay_def.entity_name, dxfattribs) underlay.scale = scale underlay_def.append_reactor_handle(underlay.dxf.handle) return underlay def add_linear_dim(self, base: 'Vertex', p1: 'Vertex', p2: 'Vertex', location: 'Vertex' = None, text: str = "<>", angle: float = 0, # 0=horizontal, 90=vertical, else=rotated text_rotation: float = None, dimstyle: str = 'EZDXF', override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: """ Add horizontal, vertical and rotated dimension line. If an :class:`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. Manual set extrusion vector will be replaced by OCS defined by UCS or (0, 0, 1) if no UCS is used. This method returns a :class:`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:`Dimension` entity between creation and rendering. 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 text mid point (in UCS) text: None or "<>" the measurement is drawn as text, " " (one space) suppresses the dimension text, everything else `text` is drawn as dimension text dimstyle: dimension style name (:class:`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 away from its default orientation (the direction of the dimension line) in degrees override: :class:`DimStyleOverride` attributes dxfattribs: DXF attributes for :class:`Dimension` entity Returns: :class:`DimStyleOverride` """ type_ = {'dimtype': const.DIM_LINEAR | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dxfattribs['dimstyle'] = dimstyle dxfattribs['defpoint'] = Vector(base) dxfattribs['text'] = text dxfattribs['defpoint2'] = Vector(p1) dxfattribs['defpoint3'] = Vector(p2) dxfattribs['angle'] = float(angle) # text_rotation ALWAYS overrides implicit angles as absolute angle (0 is horizontal)! 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.set_location(location, leader=False, relative=False) return style def add_multi_point_linear_dim(self, base: 'Vertex', points: Iterable['Vertex'], angle: float = 0, ucs: 'UCS' = None, avoid_double_rendering: bool = True, dimstyle: str = 'EZDXF', override: dict = None, dxfattribs: dict = None, discard=False) -> None: """ Add multiple linear dimensions for iterable `points`. If an :class:`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. Manual set extrusion vector will be replaced by OCS defined by UCS or (0, 0, 1) if no UCS is used. This method sets many design decisions by itself, the necessary geometry will be generated automatically, no required nor possible :meth:`render` call. This method is easy to use but you get what you get. 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:`DimStyleOverride` attributes dxfattribs: DXF attributes for :class:`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) """ 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: 'Vertex', p2: 'Vertex', distance: float, text: str = "<>", dimstyle: str = 'EZDXF', override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: """ Add linear dimension aligned with measurement points `p1` and `p2`. If an :class:`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. Manual set extrusion vector will be replaced by OCS defined by UCS or (0, 0, 1) if no UCS is used. This method returns a :class:`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:`Dimension` entity between creation and rendering. 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, " " (one space) suppresses the dimension text, everything else `text` is drawn as dimension text dimstyle: dimension style name (:class:`DimStyle` table entry), default is "EZDXF" override: :class:`DimStyleOverride` attributes dxfattribs: DXF attributes for :class:`Dimension` entity Returns: :class:`DimStyleOverride` """ p1 = Vector(p1) p2 = Vector(p2) direction = p2 - p1 angle = direction.angle_deg base = direction.orthogonal().normalize(distance) 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(self, override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: type_ = {'dimtype': const.DIM_ANGULAR | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dimline.update_dxf_attribs(dxfattribs) style = DimStyleOverride(dimline, override=override) return style def add_diameter_dim(self, override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: type_ = {'dimtype': const.DIM_DIAMETER | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dimline.update_dxf_attribs(dxfattribs) style = DimStyleOverride(dimline, override=override) return style def add_radius_dim(self, override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: type_ = {'dimtype': const.DIM_RADIUS | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dimline.update_dxf_attribs(dxfattribs) style = DimStyleOverride(dimline, override=override) return style def add_angular_3p_dim(self, override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: type_ = {'dimtype': const.DIM_ANGULAR_3P | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dimline.update_dxf_attribs(dxfattribs) style = DimStyleOverride(dimline, override=override) return style def add_ordinate_dim(self, override: dict = None, dxfattribs: dict = None) -> DimStyleOverride: type_ = {'dimtype': const.DIM_ORDINATE | const.DIM_BLOCK_EXCLUSIVE} dimline = cast('Dimension', self.build_and_add_entity('DIMENSION', dxfattribs=type_).cast()) dxfattribs = copy_attribs(dxfattribs) dimline.update_dxf_attribs(dxfattribs) style = DimStyleOverride(dimline, override=override) return style def add_arrow(self, name: str, insert: 'Vertex', size: float = 1., rotation: float = 0, dxfattribs: dict = None) -> Vector: return ARROWS.render_arrow(self, name=name, insert=insert, size=size, rotation=rotation, dxfattribs=dxfattribs) def add_arrow_blockref(self, name: str, insert: 'Vertex', size: float = 1., rotation: float = 0, dxfattribs: dict = None) -> Vector: return ARROWS.insert_arrow(self, name=name, insert=insert, size=size, rotation=rotation, dxfattribs=dxfattribs)