def add_ellipse(self, center: Tuple[float, float], major_axis: Tuple[float, float] = (1., 0.), ratio: float = 1., start_angle: float = 0., end_angle: float = 360., is_counter_clockwise: int = 0) -> 'EllipseEdge': """ Add an :class:`EllipseEdge`. Args: center: center point of ellipse, ``(x, y)`` tuple major_axis: vector of major axis as ``(x, y)`` tuple ratio: ratio of minor axis to major axis as float start_angle: start angle of arc in degrees end_angle: end angle of arc in degrees is_counter_clockwise: ``1`` for counter clockwise ``0`` for clockwise orientation """ if ratio > 1.: raise const.DXFValueError("Parameter 'ratio' has to be <= 1.0") ellipse = EllipseEdge() ellipse.center = center ellipse.major_axis = major_axis ellipse.ratio = ratio ellipse.start_angle = start_angle ellipse.end_angle = end_angle ellipse.is_counter_clockwise = is_counter_clockwise self.edges.append(ellipse) return ellipse
def set_arrows(self, blk: str = '', blk1: str = '', blk2: str = '', ldrblk: str = '') -> None: """ Set arrows by block names or AutoCAD standard arrow names, set DIMTSZ to ``0`` which disables tick. Args: blk: block/arrow name for both arrows, if DIMSAH is 0 blk1: block/arrow name for first arrow, if DIMSAH is 1 blk2: block/arrow name for second arrow, if DIMSAH is 1 ldrblk: block/arrow name for leader """ self.set_dxf_attrib('dimblk', blk) self.set_dxf_attrib('dimblk1', blk1) self.set_dxf_attrib('dimblk2', blk2) self.set_dxf_attrib('dimldrblk', ldrblk) self.set_dxf_attrib('dimtsz', 0) # use blocks # only existing BLOCK definitions allowed if self.doc: blocks = self.doc.blocks for b in (blk, blk1, blk2, ldrblk): if ARROWS.is_acad_arrow(b): # not real blocks ARROWS.create_block(blocks, b) continue if b and b not in blocks: raise const.DXFValueError( 'BLOCK "{}" does not exist.'.format(blk))
def get_sortents_table(self, create: bool = True) -> 'SortEntsTable': """ Get/Create the SORTENTSTABLE object associated to the layout. Args: create: new table if table do not exist and `create` is ``True`` Raises: DXFValueError: if table not exist and `create` is ``False`` (internal API) """ xdict = self.get_extension_dict() try: sortents_table = xdict['ACAD_SORTENTS'] except const.DXFKeyError: if create: sortents_table = self.doc.objects.new_entity( 'SORTENTSTABLE', dxfattribs={ 'owner': xdict.dxf.handle, 'block_record_handle': self.layout_key }, ) xdict['ACAD_SORTENTS'] = sortents_table.dxf.handle else: raise const.DXFValueError( 'Extension dictionary entry ACAD_SORTENTS does not exist.') return sortents_table
def new(self, name: str = None, description: str = "", selectable: bool = True) -> DXFGroup: r""" Creates a new group. If `name` is ``None`` an unnamed group is created, which has an automatically generated name like "\*Annnn". Args: name: group name as string description: group description as string selectable: group is selectable if ``True`` """ if name in self: raise const.DXFValueError(f"GROUP '{name}' already exists.") if name is None: name = self.next_name() unnamed = 1 else: unnamed = 0 # The group name isn't stored in the group entity itself. dxfattribs = { 'description': description, 'unnamed': unnamed, 'selectable': int(bool(selectable)), } return cast(DXFGroup, self._new(name, dxfattribs))
def new_geodata(self, dxfattribs: dict = None) -> 'GeoData': """ Creates a new :class:`GeoData` entity and replaces existing ones. The GEODATA entity resides in the OBJECTS section and not in the modelspace, it is linked to the modelspace by an :class:`~ezdxf.entities.ExtensionDict` located in BLOCK_RECORD of the modelspace. The GEODATA entity requires DXF R2010. The DXF reference does not document if other layouts than the modelspace supports geo referencing, so I assume getting/setting geo data may only make sense for the modelspace. Args: dxfattribs: DXF attributes for :class:`~ezdxf.entities.GeoData` entity """ if self.doc.dxfversion < const.DXF2010: raise const.DXFValueError( 'GEODATA entity requires DXF R2010 or later.') if dxfattribs is None: dxfattribs = {} xdict = self.get_extension_dict() geodata = self.doc.objects.add_geodata( owner=xdict.dictionary.dxf.handle, dxfattribs=dxfattribs, ) xdict['ACAD_GEOGRAPHICDATA'] = geodata return geodata
def set_style(self, name: str) -> None: """Set MLINESTYLE by name and update geometry accordingly. The MLINESTYLE definition must exist. """ if self.doc is None: logger.debug("Can't change style of unbounded MLINE entity.") return try: style = self.doc.mline_styles[name] except const.DXFKeyError: raise const.DXFValueError(f"Undefined MLINE style: {name}") assert isinstance(style, MLineStyle) # Line- and fill parametrization depends on the count of # elements, a change in the number of elements triggers a # reset of the parametrization: old_style = self.style new_element_count = len(style.elements) reset = False if old_style is not None: # Do not trust the stored "style_element_count" value reset = len(old_style.elements) != new_element_count self.dxf.style_name = name self.dxf.style_handle = style.dxf.handle self.dxf.style_element_count = new_element_count if reset: self.update_geometry()
def rgb(self, value: Optional[colors.RGB]): if value is None: self._true_color = None elif validator.is_valid_rgb(value): self._true_color = value else: raise const.DXFValueError(f"invalid true color value '{value}'")
def set_current_lineweight(doc: "Drawing", lineweight: int): """Set current lineweight, see :ref:`lineweights` reference for valid values. """ if not validator.is_valid_lineweight(lineweight): raise const.DXFValueError(f'invalid lineweight value: "{lineweight}"') doc.header[CURRENT_LINEWEIGHT] = lineweight
def set_gradient( self, color1: RGB = (0, 0, 0), color2: RGB = (255, 255, 255), rotation: float = 0.0, centered: float = 0.0, one_color: int = 0, tint: float = 0.0, name: str = "LINEAR", ) -> None: """Set :class:`Hatch` and :class:`MPolygon` to gradient fill mode and removes all pattern fill related data. Gradient support requires DXF R2004+. A gradient filled hatch is also a solid filled hatch. Valid gradient type names are: - ``'LINEAR'`` - ``'CYLINDER'`` - ``'INVCYLINDER'`` - ``'SPHERICAL'`` - ``'INVSPHERICAL'`` - ``'HEMISPHERICAL'`` - ``'INVHEMISPHERICAL'`` - ``'CURVED'`` - ``'INVCURVED'`` Args: color1: (r, g, b)-tuple for first color, rgb values as int in the range [0, 255] color2: (r, g, b)-tuple for second color, rgb values as int in the range [0, 255] rotation: rotation angle in degrees centered: determines whether the gradient is centered or not one_color: 1 for gradient from `color1` to tinted `color1` tint: determines the tinted target `color1` for a one color gradient. (valid range 0.0 to 1.0) name: name of gradient type, default "LINEAR" """ if self.doc is not None and self.doc.dxfversion < const.DXF2004: raise const.DXFVersionError("Gradient support requires DXF R2004") if name and name not in const.GRADIENT_TYPES: raise const.DXFValueError(f"Invalid gradient type name: {name}") self.pattern = None self.dxf.solid_fill = 1 self.dxf.pattern_name = "SOLID" self.dxf.pattern_type = const.HATCH_TYPE_PREDEFINED gradient = Gradient() gradient.color1 = color1 gradient.color2 = color2 gradient.one_color = one_color gradient.rotation = rotation gradient.centered = centered gradient.tint = tint gradient.name = name self.gradient = gradient
def add( self, name: str, *, color: int = const.BYLAYER, true_color: int = None, linetype: str = "Continuous", lineweight: int = const.LINEWEIGHT_BYLAYER, plot: bool = True, transparency: Optional[float] = None, dxfattribs: Dict = None, ) -> "Layer": """Add a new :class:`~ezdxf.entities.Layer`. Args: name (str): layer name color (int): :ref:`ACI` value, default is BYLAYER true_color (int): true color value, use :func:`ezdxf.rgb2int` to create ``int`` values from RGB values linetype (str): line type name, default is "Continuous" lineweight (int): line weight, default is BYLAYER plot (bool): plot layer as bool, default is ``True`` transparency: transparency value in the range [0, 1], where 1 is 100% transparent and 0 is opaque dxfattribs (dict): additional DXF attributes .. versionadded:: 0.17 """ dxfattribs = dict(dxfattribs or {}) if validator.is_valid_aci_color(color): dxfattribs["color"] = color else: raise const.DXFValueError(f"invalid color: {color}") dxfattribs["linetype"] = linetype if validator.is_valid_lineweight(lineweight): dxfattribs["lineweight"] = lineweight else: raise const.DXFValueError(f"invalid lineweight: {lineweight}") if true_color is not None: dxfattribs["true_color"] = int(true_color) dxfattribs["plot"] = int(plot) layer = cast("Layer", self.new(name, dxfattribs)) if transparency is not None: layer.transparency = transparency return layer
def transparency(self, value: Optional[float]): if value is None: self._transparency = None elif value == TRANSPARENCY_BYBLOCK: self._transparency = TRANSPARENCY_BYBLOCK elif isinstance(value, float) and (0.0 <= value <= 1.0): self._transparency = value else: raise const.DXFValueError(f"invalid transparency value '{value}'")
def __setitem__(self, key: str, value: Any) -> None: """ Set header variable `key` to `value` by index operator like: :code:`drawing.header['$ANGDIR'] = 1` """ try: tags = self._headervar_factory(key, value) except (IndexError, ValueError): raise const.DXFValueError(str(value)) self.hdrvars[key] = HeaderVar(tags)
def acquire_arrow(self, name: str): """ For standard AutoCAD and ezdxf arrows create block definitions if required, otherwise check if block `name` exist. (internal API) """ from ezdxf.render.arrows import ARROWS if ARROWS.is_acad_arrow(name) or ARROWS.is_ezdxf_arrow(name): ARROWS.create_block(self.blocks, name) elif name not in self.blocks: raise const.DXFValueError(f'Arrow block "{name}" does not exist.')
def set_seed_points(self, points: Sequence[Tuple[float, float]]) -> None: """ Set seed points, `points` is a list of ``(x, y)`` tuples, I don't know why there can be more than one seed point. All points in :ref:`OCS` (:attr:`Hatch.dxf.elevation` is the Z value) """ if len(points) < 1: raise const.DXFValueError( "Param points should be a collection of 2D points and requires at least one point.") self.seeds = list(points) self.dxf.n_seed_points = len(self.seeds)
def new_line(angle: float = 0., base_point: Tuple[float, float] = (0., 0.), offset: Tuple[float, float] = (0., 0.), dash_length_items: List[float] = None) -> 'PatternLine': """ Create a new pattern definition line, but does not add the line to the :attr:`Pattern.lines` attribute. """ if dash_length_items is None: raise const.DXFValueError("Parameter 'dash_length_items' must not be None.") return PatternLine(angle, base_point, offset, dash_length_items)
def delete_layout(self, name: str) -> None: """ Delete paper space layout `name` and all entities owned by this layout. Available only for DXF R2000 or later, DXF R12 supports only one paperspace and it can't be deleted. """ if name not in self.layouts: raise const.DXFValueError(f"Layout '{name}' does not exist.") else: self.layouts.delete(name)
def set_gradient(self, color1: 'RGB' = (0, 0, 0), color2: 'RGB' = (255, 255, 255), rotation: float = 0., centered: float = 0., one_color: int = 0, tint: float = 0., name: str = 'LINEAR') -> None: """ Set :class:`Hatch` to gradient fill mode and removes all pattern fill related data. Gradient support requires DXF DXF R2004. A gradient filled hatch is also a solid filled hatch. Valid gradient type names are: - ``'LINEAR'`` - ``'CYLINDER'`` - ``'INVCYLINDER'`` - ``'SPHERICAL'`` - ``'INVSPHERICAL'`` - ``'HEMISPHERICAL'`` - ``'INVHEMISPHERICAL'`` - ``'CURVED'`` - ``'INVCURVED'`` Args: color1: ``(r, g, b)`` tuple for first color, rgb values as int in range 0..255 color2: ``(r, g, b)`` tuple for second color, rgb values as int in range 0..255 rotation: rotation in degrees centered: determines whether the gradient is centered or not one_color: ``1`` for gradient from `color1` to tinted `color1`` tint: determines the tinted target `color1` for a one color gradient. (valid range ``0.0`` to ``1.0``) name: name of gradient type, default ``'LINEAR'`` """ if self.doc is not None and self.drawing.dxfversion < DXF2004: raise const.DXFVersionError("Gradient support requires DXF R2004") if name not in const.GRADIENT_TYPES: raise const.DXFValueError('Invalid gradient type name: %s' % name) self.pattern = None self.dxf.solid_fill = 1 self.dxf.pattern_name = 'SOLID' self.dxf.pattern_type = const.HATCH_TYPE_PREDEFINED gradient = Gradient() gradient.color1 = color1 gradient.color2 = color2 gradient.one_color = one_color gradient.rotation = rotation gradient.centered = centered gradient.tint = tint gradient.name = name self.gradient = gradient
def check(value): value = cast_value(attrib_def.code, value) if not attrib_def.is_valid_value(value): if attrib_def.fixer: value = attrib_def.fixer(value) logger.debug(f'Fixed invalid attribute "{key}" in entity' f' {entity()} to "{str(value)}".') else: raise const.DXFValueError( f'Invalid value {str(value)} for attribute "{key}" in ' f'entity {entity()}.') return value
def add_ellipse( self, center: "Vertex", major_axis: "Vertex" = (1.0, 0.0), ratio: float = 1.0, start_angle: float = 0.0, end_angle: float = 360.0, ccw: bool = True, ) -> "EllipseEdge": """Add an :class:`EllipseEdge`. **Adding Clockwise Oriented Ellipses:** Clockwise oriented :class:`EllipseEdge` objects are sometimes necessary to build closed loops, but the :class:`EllipseEdge` objects are always represented in counter-clockwise orientation. To add a clockwise oriented :class:`EllipseEdge` you have to swap the start- and end angle and set the `ccw` flag to ``False``, e.g. to add a clockwise oriented :class:`EllipseEdge` from 180 to 90 degree, add the :class:`EllipseEdge` in counter-clockwise orientation with swapped angles:: edge_path.add_ellipse(center, major_axis, ratio, start_angle=90, end_angle=180, ccw=False) Args: center: center point of ellipse, (x, y)-tuple major_axis: vector of major axis as (x, y)-tuple ratio: ratio of minor axis to major axis as float start_angle: start angle of ellipse in degrees (`end_angle` for a clockwise oriented ellipse) end_angle: end angle of ellipse in degrees (`start_angle` for a clockwise oriented ellipse) ccw: ``True`` for counter clockwise ``False`` for clockwise orientation """ if ratio > 1.0: raise const.DXFValueError("argument 'ratio' has to be <= 1.0") ellipse = EllipseEdge() ellipse.center = Vec2(center) ellipse.major_axis = Vec2(major_axis) ellipse.ratio = ratio # Start- and end angles are always stored in counter-clockwise # orientation! ellipse.start_angle = start_angle ellipse.end_angle = end_angle # Flag to export the counter-clockwise oriented ellipse in # correct clockwise orientation: ellipse.ccw = bool(ccw) self.edges.append(ellipse) return ellipse
def set_seed_points(self, points: Iterable[Tuple[float, float]]) -> None: """Set seed points, `points` is an iterable of (x, y)-tuples. I don't know why there can be more than one seed point. All points in :ref:`OCS` (:attr:`Hatch.dxf.elevation` is the Z value) """ points = list(points) if len(points) < 1: raise const.DXFValueError( "Param points should be an iterable of 2D points and requires at " "least one point.") self.seeds = list(points) self.dxf.n_seed_points = len(self.seeds)
def get_xdata(self, appid: str) -> Tags: """ Returns extended data for `appid`. Args: appid: application name as defined in the APPID table. Raises: DXFValueError: no extended data for `appid` found """ if self.xdata: return Tags(self.xdata.get(appid)[1:]) else: raise const.DXFValueError(appid)
def replace(self, tag: str, value: str) -> None: """ Replaces the value of the first custom property `tag` by a new `value`. Raises :class:`DXFValueError` if `tag` does not exist. """ properties = self.properties for index in range(len(properties)): name = properties[index][0] if name == tag: properties[index] = (name, value) return raise const.DXFValueError(f"Tag '{tag}' does not exist")
def set_vertices(self, vertices: Sequence[Sequence[float]], is_closed: bool = True) -> None: """ Set new `vertices` as new polyline path, a vertex has to be a ``(x, y)`` or a ``(x, y, bulge)`` tuple. """ new_vertices = [] for vertex in vertices: if len(vertex) == 2: x, y = vertex bulge = 0 elif len(vertex) == 3: x, y, bulge = vertex else: raise const.DXFValueError("Invalid vertex format, expected (x, y) or (x, y, bulge)") new_vertices.append((x, y, bulge)) self.vertices = new_vertices self.is_closed = is_closed
def get_xdata_list(self, appid: str, name: str) -> Tags: """ Returns tag list `name` for extended data `appid`. Args: appid: application name as defined in the APPID table. name: extended data list name Raises: DXFValueError: no extended data for `appid` found or no data list `name` not found """ if self.xdata: return Tags(self.xdata.get_xlist(appid, name)) else: raise const.DXFValueError(appid)
def new(cls, start: Vertex, line_direction: Vertex, miter_direction: Vertex, line_params: Iterable = None, fill_params: Iterable = None) -> 'MLineVertex': vtx = MLineVertex() vtx.location = Vec3(start) vtx.line_direction = Vec3(line_direction) vtx.miter_direction = Vec3(miter_direction) vtx.line_params = list(line_params or []) vtx.fill_params = list(fill_params or []) if len(vtx.line_params) != len(vtx.fill_params): raise const.DXFValueError( 'Count mismatch of line- and fill parameters') return vtx
def remove(self, tag: str, all: bool = False) -> None: """ Removes the first occurrence of custom property `tag`, removes all occurrences if `all` is ``True``. Raises `:class:`DXFValueError` if `tag` does not exist. """ found_tag = False for item in self.properties: if item[0] == tag: self.properties.remove(item) found_tag = True if not all: return if not found_tag: raise const.DXFValueError(f"Tag '{tag}' does not exist")
def delete(self, group: Union[DXFGroup, str]) -> None: """ Delete `group`, `group` can be an object of type :class:`DXFGroup` or a group name as string. """ # Delete group by name: if isinstance(group, str): name = group elif group.dxftype() == 'GROUP': name = get_group_name(group, self.entitydb) else: raise TypeError(group.dxftype()) if name in self: super().delete(name) else: raise const.DXFValueError("GROUP not in group table registered.")
def export_dxf(self, tagwriter: 'TagWriter') -> None: write_tag = tagwriter.write_tag2 write_tag(72, 4) # edge type write_tag(94, int(self.degree)) write_tag(73, int(self.rational)) write_tag(74, int(self.periodic)) write_tag(95, len(self.knot_values)) # number of knots write_tag(96, len(self.control_points)) # number of control points # build knot values list # knot values have to be present and valid, otherwise AutoCAD crashes if len(self.knot_values): for value in self.knot_values: write_tag(40, float(value)) else: raise const.DXFValueError( "SplineEdge: missing required knot values") # build control points # control points have to be present and valid, otherwise AutoCAD crashes for x, y, *_ in self.control_points: tagwriter.write_tag2(10, float(x)) tagwriter.write_tag2(20, float(y)) # build weights list, optional for value in self.weights: write_tag(42, float(value)) # build fit points # fit points have to be present and valid, otherwise AutoCAD crashes # edit 2016-12-20: this is not true - there are examples with no fit points and without crashing AutoCAD write_tag(97, len(self.fit_points)) for x, y, *_ in self.fit_points: tagwriter.write_tag2(11, float(x)) tagwriter.write_tag2(21, float(y)) if self.start_tangent is not None: x, y, *_ = self.start_tangent tagwriter.write_tag2(12, float(x)) tagwriter.write_tag2(22, float(y)) if self.end_tangent is not None: x, y, *_ = self.end_tangent tagwriter.write_tag2(13, float(x)) tagwriter.write_tag2(23, float(y))
def add_underlay_def( self, filename: str, fmt: str = "pdf", name: str = None ) -> "UnderlayDefinition": """Add an :class:`~ezdxf.entities.underlay.UnderlayDefinition` entity to the drawing (OBJECTS section). `filename` is the underlay file name as relative or absolute path and `fmt` as string (pdf, dwf, dgn). The underlay definition is required to create an underlay reference. Args: filename: underlay file name fmt: file format as string ``'pdf'|'dwf'|'dgn'`` name: pdf format = page number to display; dgn format = ``'default'``; dwf: ???? """ fmt = fmt.upper() if fmt in ("PDF", "DWF", "DGN"): underlay_dict_name = f"ACAD_{fmt}DEFINITIONS" underlay_def_entity = f"{fmt}DEFINITION" else: raise const.DXFValueError(f"Unsupported file format: '{fmt}'") if name is None: if fmt == "PDF": name = "1" # Display first page by default elif fmt == "DGN": name = "default" else: name = "Model" # Display model space for DWF ??? underlay_dict = self.rootdict.get_required_dict(underlay_dict_name) underlay_def = self.new_entity( underlay_def_entity, dxfattribs={ "owner": underlay_dict.dxf.handle, "filename": filename, "name": name, }, ) # auto-generated underlay key key = self.next_underlay_key(lambda k: k not in underlay_dict) underlay_dict[key] = underlay_def.dxf.handle return cast("UnderlayDefinition", underlay_def)
def new_layout(self, name, dxfattribs=None) -> 'Layout': """ Create a new paperspace layout `name`. Returns a :class:`~ezdxf.layouts.Layout` object. DXF R12 (AC1009) supports only one paperspace layout, only the active paperspace layout is saved, other layouts are dismissed. Args: name: unique layout name dxfattribs: additional DXF attributes for the :class:`~ezdxf.entities.layout.DXFLayout` entity Raises: DXFValueError: :class:`~ezdxf.layouts.Layout` `name` already exist """ if name in self.layouts: raise const.DXFValueError(f"Layout '{name}' already exists.") else: return self.layouts.new(name, dxfattribs)