def move_to_layout(self, layout: 'BaseLayout', source: 'BaseLayout' = None) -> None: """ Move entity from model space or a paper space layout to another layout. For block layout as source, the block layout has to be specified. Moving between different DXF drawings is not supported. Args: layout: any layout (model space, paper space, block) source: provide source layout, faster for DXF R12, if entity is in a block layout Raises: DXFStructureError: for moving between different DXF drawings """ if source is None: source = self.get_layout() if source is None: raise DXFValueError('Source layout for entity not found.') source.move_to_layout(self, layout)
def __init__(self, control_points: Iterable['Vertex'], order: int = 4, knots: Iterable[float] = None, weights: Iterable[float] = None): self.control_points = Vector.list(control_points) self.order = order if order > self.count: raise DXFValueError( 'Invalid need more control points for order {}'.format(order)) if knots is None: knots = open_uniform_knot_vector(self.count, self.order) else: knots = list(knots) if len(knots) != self.nplusc: raise ValueError("{} knot values required, got {}.".format( self.nplusc, len(knots))) self.basis = Basis(knots, self.order, self.count, weights=weights)
def move_to_layout(self, entity: 'DXFGraphic', layout: 'BaseLayout') -> None: """ Move entity to another layout. Args: entity: DXF entity to move layout: any layout (modelspace, paperspace, block) from **same** drawing """ if entity.doc != layout.doc: raise DXFStructureError( 'Moving between different DXF drawings is not supported.') try: self.unlink_entity(entity) except ValueError: raise DXFValueError('Layout does not contain entity.') else: layout.add_entity(entity)
def get_xlist(self, appid: str, name: str) -> List[Tuple]: """Get list `name` from XDATA `appid`. Args: appid: APPID name: list name Returns: list of DXFTags including list name and curly braces '{' '}' tags Raises: DXFKeyError: XDATA `appid` does not exist DXFValueError: list `name` does not exist """ xdata = self.get(appid) try: return get_named_list_from_xdata(name, xdata) except NotFoundException: raise DXFValueError( f'No data list "{name}" not found for APPID "{appid}"')
def set_blk_handle(self, attr: str, arrow_name: str) -> None: if arrow_name == ARROWS.closed_filled: # special arrow, no handle needed (is '0' if set) # do not create block by default, this will be done if arrow is used # and block record handle is not needed here self.dxf.discard(attr) return blocks = self.doc.blocks if ARROWS.is_acad_arrow(arrow_name): # create block, because need block record handle is needed here block_name = ARROWS.create_block(blocks, arrow_name) else: block_name = arrow_name blk = blocks.get(block_name) if blk is not None: self.set_dxf_attrib(attr, blk.block_record_handle) else: raise DXFValueError(f'Block {arrow_name} does not exist.')
def new(self, name: str) -> 'DXFObject': """ Create a new object of type `self.object_type` and store its handle in the object manager dictionary. Args: name: name of new object as string Returns: new object of type `self.object_type` Raises: DXFValueError: if object name already exist (internal API) """ if name in self.object_dict: raise DXFValueError( f'{self.object_type} entry {name} already exists.') return self._new(name, dxfattribs={'name': name})
def to_ellipse(self, replace=True) -> "Ellipse": """Convert CIRCLE/ARC to an :class:`~ezdxf.entities.Ellipse` entity. Adds the new ELLIPSE entity to the entity database and to the same layout as the source entity. Args: replace: replace (delete) source entity by ELLIPSE entity if ``True`` """ from ezdxf.entities import Ellipse layout = self.get_layout() if layout is None: raise DXFValueError("valid layout required") ellipse = Ellipse.from_arc(self) if replace: replace_entity(self, ellipse, layout) else: add_entity(ellipse, layout) return ellipse
def to_spline(self, replace=True) -> "Spline": """Convert CIRCLE/ARC to a :class:`~ezdxf.entities.Spline` entity. Adds the new SPLINE entity to the entity database and to the same layout as the source entity. Args: replace: replace (delete) source entity by SPLINE entity if ``True`` """ from ezdxf.entities import Spline layout = self.get_layout() if layout is None: raise DXFValueError("valid layout required") spline = Spline.from_arc(self) if replace: replace_entity(self, spline, layout) else: add_entity(spline, layout) return spline
def bspline_control_frame(fit_points, degree=3, method='distance', power=.5): """ Calculate B-spline control frame, 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 Args: fit_points: fit points of B-spline degree: degree of B-spline method: calculation method for parameter vector t power: power for centripetal method """ def create_t_vector(): if method == 'uniform': return uniform_t_vector(fit_points) # equally spaced 0 .. 1 elif method == 'distance': return distance_t_vector(fit_points) elif method == 'centripetal': return centripetal_t_vector(fit_points, power=power) else: raise DXFValueError('Unknown method: {}'.format(method)) fit_points = list(fit_points) count = len(fit_points) order = degree + 1 if order > count: raise DXFValueError( 'Need more fit points for degree {}'.format(degree)) t_vector = list(create_t_vector()) knots = list(control_frame_knots(count - 1, degree, t_vector)) control_points = global_curve_interpolation(fit_points, degree, t_vector, knots) bspline = BSpline(control_points, order=order, knots=knots) bspline.t_array = t_vector return bspline
def set_plot_type(self, value: int = 5) -> None: """ === ============================================================ 0 last screen display 1 drawing extents 2 drawing limits 3 view specific (defined by :attr:`Layout.dxf.plot_view_name`) 4 window specific (defined by :meth:`Layout.set_plot_window_limits`) 5 layout information (default) === ============================================================ Args: value: plot type Raises: DXFValueError: for `value` out of range """ if 0 <= int(value) <= 5: self.dxf.plot_type = value # type: ignore else: raise DXFValueError('Plot type value out of range (0-5).')
def get_extension_dict(self, create: bool = True) -> 'DXFDictionary': """ Returns the associated extension dictionary. Args: create (bool): create extension dictionary if not exists Raises: DXFValueError: if extension dictionary do not exists and `create` is False """ block_record = self.block_record try: xdict = block_record.get_extension_dict() except (DXFValueError, DXFKeyError): # DXFValueError: block_record has no extension dict # DXFKeyError: block_record has an extension dict handle, but extension dict does not exist if create: xdict = block_record.new_extension_dict() else: raise DXFValueError('Extension dictionary do not exist.') return xdict
def set_active_layout(self, name: str) -> None: """ Set active paper space layout. Args: name (str): layout name as shown in tab """ if name == 'Model': # reserved layout name raise DXFValueError('Can not set model space as active layout') new_active_layout = self.get( name) # raises KeyError if no layout 'name' exists old_active_layout_key = self.drawing.get_active_layout_key() if old_active_layout_key == new_active_layout.layout_key: return # layout 'name' is already the active layout blocks = self.drawing.blocks new_active_paper_space_name = new_active_layout.block_record_name blocks.rename_block(PAPER_SPACE, TMP_PAPER_SPACE_NAME) blocks.rename_block(new_active_paper_space_name, PAPER_SPACE) blocks.rename_block(TMP_PAPER_SPACE_NAME, new_active_paper_space_name)
def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance', power: float = .5): """ 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 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 """ def create_t_vector(): if method == 'uniform': return uniform_t_vector(fit_points) # equally spaced 0 .. 1 elif method == 'distance': return distance_t_vector(fit_points) elif method == 'centripetal': return centripetal_t_vector(fit_points, power=power) else: raise DXFValueError('Unknown method: {}'.format(method)) fit_points = list(fit_points) order = degree + 1 if order > count: raise DXFValueError('More control points for degree {} required.'.format(degree)) t_vector = list(create_t_vector()) knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector)) control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots) bspline = BSpline(control_points, order=order) return bspline
def add_underlay_def(self, filename: str, format: str = 'pdf', name: str = None) -> 'UnderlayDef': """ Add an :class:`~ezdxf.entities.underlay.UnderlayDef` entity to the drawing (OBJECTS section). `filename` is the underlay file name as relative or absolute path and `format` as string (pdf, dwf, dgn). The underlay definition is required to create an underlay reference. Args: filename: underlay file name format: file format as string ``'pdf'|'dwf'|'dgn'`` or ``'ext'`` for getting file format from filename extension name: pdf format = page number to display; dgn format = ``'default'``; dwf: ???? """ fmt = format.upper() if fmt in ('PDF', 'DWF', 'DGN'): underlay_dict_name = 'ACAD_{}DEFINITIONS'.format(fmt) underlay_def_entity = "{}DEFINITION".format(fmt) else: raise DXFValueError("Unsupported file format: '{}'".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.dxffactory.next_underlay_key(lambda k: k not in underlay_dict) underlay_dict[key] = underlay_def.dxf.handle return cast('UnderlayDef', underlay_def)
def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance', power: float = .5): """ Approximate `B-spline`_ by a reduced count of control points, given are the fit points and the degree of the B-spline. Args: fit_points: all fit points of B-spline as :class:`Vector` compatible objects count: count of designated control points degree: degree of B-spline method: calculation method for parameter vector t, see :func:`bspline_control_frame` power: power for centripetal method Returns: :class:`BSpline` """ def create_t_vector(): if method == 'uniform': return uniform_t_vector(fit_points) # equally spaced 0 .. 1 elif method == 'distance': return distance_t_vector(fit_points) elif method == 'centripetal': return centripetal_t_vector(fit_points, power=power) else: raise DXFValueError('Unknown method: {}'.format(method)) fit_points = list(fit_points) order = degree + 1 if order > count: raise DXFValueError('More control points for degree {} required.'.format(degree)) t_vector = list(create_t_vector()) knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector)) control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots) bspline = BSpline(control_points, order=order) return bspline
def required_knot_values(count: int, order: int) -> int: """ Returns the count of required knot values for a B-spline of `order` and `count` control points. degree = degree of B-spline, in math papers often called: `p` Args: count: count of control points, in math papers often called: `n` + 1 order: order of B-Spline, in math papers often called: `k` Relationships: - `k` (order) = `p` (degree) + 1 - 2 ≤ `k` (order) ≤ `n` + 1 (count) """ k = order n = count - 1 p = k - 1 if not (2 <= k <= (n + 1)): raise DXFValueError('Invalid count/order combination') # n + p + 2 = count + order return n + p + 2
def control_frame_knots(n: int, p: int, t_vector: Iterable[float]) -> Iterable[float]: """ Generates a 'clamped' knot vector for control frame creation. All knot values in the range [0 .. 1]. Args: n: count fit points - 1 p: degree of spline t_vector: parameter vector, length(t_vector) == n+1 Yields: n+p+2 knot values as floats """ order = int(p + 1) if order > (n + 1): raise DXFValueError('Invalid n/p combination') t_vector = [float(t) for t in t_vector] for _ in range(order): # clamped spline has 'order' leading 0s yield t_vector[0] for j in range(1, n - p + 1): yield sum(t_vector[j: j + p]) / p for _ in range(order): # clamped spline has 'order' appended 1s yield t_vector[-1]
def dispatch(self, override: "DimStyleOverride", ucs: "UCS" = None) -> "BaseDimensionRenderer": dimension = override.dimension dim_type = dimension.dimtype dxf_type = dimension.dxftype() if dxf_type == "ARC_DIMENSION": return self.arc_length(dimension, ucs, override) elif dxf_type == "LARGE_RADIAL_DIMENSION": return self.large_radial(dimension, ucs, override) elif dim_type in (0, 1): return self.linear(dimension, ucs, override) elif dim_type == 2: return self.angular(dimension, ucs, override) elif dim_type == 3: return self.diameter(dimension, ucs, override) elif dim_type == 4: return self.radius(dimension, ucs, override) elif dim_type == 5: return self.angular3p(dimension, ucs, override) elif dim_type == 6: return self.ordinate(dimension, ucs, override) else: raise DXFValueError(f"Unknown DIMENSION type: {dim_type}")
def delete(self, name: str) -> None: """ Delete layout `name` and all entities owned by it. Args: name (str): layout name as shown in tabs Raises: KeyError: if layout `name` do not exists ValueError: if `name` is ``'Model'``, deleting modelspace is not possible """ assert isinstance(name, str), type(name) if name == 'Model': raise DXFValueError("Can not delete model space layout.") layout = self._layouts[name] if layout.layout_key == self.get_active_layout_key(): # name is the active layout for layout_name in self.names(): if layout_name not in (name, 'Model'): # set any other layout as active layout self.set_active_layout(layout_name) break self._dxf_layouts.remove(layout.name) del self._layouts[layout.name] layout.destroy()
def layers(self, names): if isstring(names): raise DXFValueError('requires iterable but not string') self.layers[:] = list(names)
def groupby(entities: Iterable['DXFEntity'], dxfattrib: str = '', key: 'KeyFunc' = None) \ -> Dict[Hashable, List['DXFEntity']]: """ Groups a sequence of DXF entities by a DXF attribute like ``'layer'``, returns a dict with `dxfattrib` values as key and a list of entities matching this `dxfattrib`. A `key` function can be used to combine some DXF attributes (e.g. layer and color) and should return a hashable data type like a tuple of strings, integers or floats, `key` function example:: def group_key(entity: DXFEntity): return entity.dxf.layer, entity.dxf.color For not suitable DXF entities return ``None`` to exclude this entity, in this case it's not required, because :func:`groupby` catches :class:`DXFAttributeError` exceptions to exclude entities, which do not provide layer and/or color attributes, automatically. Result dict for `dxfattrib` = ``'layer'`` may look like this:: { '0': [ ... list of entities ], 'ExampleLayer1': [ ... ], 'ExampleLayer2': [ ... ], ... } Result dict for `key` = `group_key`, which returns a ``(layer, color)`` tuple, may look like this:: { ('0', 1): [ ... list of entities ], ('0', 3): [ ... ], ('0', 7): [ ... ], ('ExampleLayer1', 1): [ ... ], ('ExampleLayer1', 2): [ ... ], ('ExampleLayer1', 5): [ ... ], ('ExampleLayer2', 7): [ ... ], ... } All entity containers (modelspace, paperspace layouts and blocks) and the :class:`~ezdxf.query.EntityQuery` object have a dedicated :meth:`groupby` method. Args: entities: sequence of DXF entities to group by a DXF attribute or a `key` function dxfattrib: grouping DXF attribute like ``'layer'`` key: key function, which accepts a :class:`DXFEntity` as argument and returns a hashable grouping key or ``None`` to ignore this entity. """ if all((dxfattrib, key)): raise DXFValueError( 'Specify a dxfattrib or a key function, but not both.') if dxfattrib != '': key = lambda entity: entity.get_dxf_attrib(dxfattrib, None) if key is None: raise DXFValueError( 'no valid argument found, specify a dxfattrib or a key function, but not both.' ) result = dict() for dxf_entity in entities: if not dxf_entity.is_alive: continue try: group_key = key(dxf_entity) except DXFAttributeError: # ignore DXF entities, which do not support all query attributes continue if group_key is not None: group = result.setdefault(group_key, []) group.append(dxf_entity) return result
def get(self, appid: str) -> Tags: if appid in self.data: return self.data[appid] else: raise DXFValueError(appid)
def add_edge(self, vertices: Sequence[Sequence[float]]) -> Sequence[int]: if len(vertices) != 2: raise DXFValueError( "Parameter vertices has to be a list/tuple of 2 vertices [(x1, y1, z1), (x2, y2, z2)]." ) return self.add_entity(vertices, self.edges)
def layers(self, names: Iterable[str]): if isinstance(names, str): raise DXFValueError('requires iterable but not string') self.layers[:] = list(names)
def add_foreign_entity(self, entity: "DXFGraphic", copy=True) -> None: """Add a foreign DXF entity to a layout, this foreign entity could be from another DXF document or an entity without an assigned DXF document. The intention of this method is to add **simple** entities from another DXF document or from a DXF iterator, for more complex operations use the :mod:`~ezdxf.addons.importer` add-on. Especially objects with BLOCK section (INSERT, DIMENSION, MLEADER) or OBJECTS section dependencies (IMAGE, UNDERLAY) can not be supported by this simple method. Not all DXF types are supported and every dependency or resource reference from another DXF document will be removed except attribute layer will be preserved but only with default attributes like color ``7`` and linetype ``CONTINUOUS`` because the layer attribute doesn't need a layer table entry. If the entity is part of another DXF document, it will be unlinked from this document and its entity database if argument `copy` is ``False``, else the entity will be copied. Unassigned entities like from DXF iterators will just be added. Supported DXF types: - POINT - LINE - CIRCLE - ARC - ELLIPSE - LWPOLYLINE - SPLINE - POLYLINE - 3DFACE - SOLID - TRACE - SHAPE - MESH - ATTRIB - ATTDEF - TEXT - MTEXT - HATCH Args: entity: DXF entity to copy or move copy: if ``True`` copy entity from other document else unlink from other document """ foreign_doc = entity.doc dxftype = entity.dxftype() if dxftype not in SUPPORTED_FOREIGN_ENTITY_TYPES: raise DXFTypeError(f"unsupported DXF type: {dxftype}") if foreign_doc is self.doc: raise DXFValueError("entity from same DXF document") if foreign_doc is not None: if copy: entity = entity.copy() else: # Unbind entity from other document without destruction. factory.unbind(entity) entity.remove_dependencies(self.doc) # add to this layout & bind to document self.add_entity(entity)
def edit_pattern(self): if self.has_solid_fill: raise DXFValueError('Solid fill HATCH has no pattern data.') pattern_data = PatternData(self) yield pattern_data self._set_pattern_data(pattern_data)
def edit_gradient(self): if not self.has_gradient_data: raise DXFValueError('HATCH has no gradient data.') gradient_data = GradientData.from_tags(self.AcDbHatch) yield gradient_data self._set_gradient(gradient_data)
def get_gradient(self): if not self.has_gradient_data: raise DXFValueError('HATCH has no gradient data.') return GradientData.from_tags(self.AcDbHatch)
def __setitem__(self, key: str, value: Any) -> None: try: tags = self._headervar_factory(key, value) except (IndexError, ValueError): raise DXFValueError(str(value)) self.hdrvars[key] = HeaderVar(tags)
def page_setup(self, size=(297, 210), margins=(0, 0, 0, 0), units='mm', offset=(0, 0), rotation=0, scale=16): if self._paperspace == 0: raise DXFTypeError("No paper setup for model space.") # remove existing viewports for viewport in self.viewports(): self.delete_entity(viewport) if int(rotation) not in (0, 1, 2, 3): raise DXFValueError("valid rotation values: 0-3") if isinstance(scale, int): scale = STD_SCALES.get(scale, (1, 1)) if scale[0] == 0: raise DXFValueError("scale numerator can't be 0.") if scale[1] == 0: raise DXFValueError("scale denominator can't be 0.") scale_factor = scale[1] / scale[0] # TODO: don't know how to set inch or mm mode in R12 units = units.lower() if units.startswith('inch'): units = 'Inches' plot_paper_units = 0 unit_factor = 25.4 # inch to mm elif units == 'mm': units = 'MM' plot_paper_units = 1 unit_factor = 1.0 else: raise DXFValueError('Supported units: "mm" and "inch"') # all viewport parameters are scaled paper space units def paper_units(value): return value * scale_factor # TODO: don't know how paper setup in DXF R12 works paper_width, paper_height = size # TODO: don't know how margins setup in DXF R12 works margin_top, margin_right, margin_bottom, margin_left = margins paper_width = paper_units(size[0]) paper_height = paper_units(size[1]) plimmin = self.drawing.header['$PLIMMIN'] = (0, 0) plimmax = self.drawing.header['$PLIMMAX'] = (paper_width, paper_height) # TODO: don't know how paper setup in DXF R12 works pextmin = self.drawing.header['$PEXTMIN'] = (0, 0, 0) pextmax = self.drawing.header['$PEXTMAX'] = (paper_width, paper_height, 0) # printing area printable_width = paper_width - paper_units(margin_left) - paper_units( margin_right) printable_height = paper_height - paper_units( margin_bottom) - paper_units(margin_top) # AutoCAD viewport (window) size vp_width = paper_width * 1.1 vp_height = paper_height * 1.1 # center of printing area center = (printable_width / 2, printable_height / 2) # create 'main' viewport main_viewport = self.add_viewport( center=center, # no influence to 'main' viewport? size=(vp_width, vp_height), # I don't get it, just use paper size! view_center_point=center, # same as center view_height=vp_height, # view height in paper space units ) main_viewport.dxf.id = 1 # set as main viewport main_viewport.dxf.status = 2 # AutoCAD default value with main_viewport.edit_data() as vpdata: vpdata.view_mode = 1000 # AutoDesk default