def mleader(self, text): return MultiLeader.load(ExtendedTags.from_text(text))
def single_pass_modelspace(stream: BinaryIO, types: Iterable[str] = None ) -> Iterable[DXFGraphic]: """ Iterate over all modelspace entities as :class:`DXFGraphic` objects in one single pass. Use this function to 'quick' iterate over modelspace entities of a **not** seekable binary DXF stream, filtering DXF types may speed up things if many entity types will be skipped. Args: stream: (not seekable) binary DXF stream types: DXF types like ``['LINE', '3DFACE']`` which should be returned, ``None`` returns all supported types. """ fetch_header_var: Optional[str] = None encoding = 'cp1252' version = 'AC1009' prev_code: int = -1 prev_value: str = '' entities = False requested_types = _requested_types(types) for code, value in binary_tagger(stream): if code == 0 and value == b'ENDSEC': break elif code == 2 and prev_code == 0 and value != b'HEADER': # (0, SECTION), (2, name) # First section is not the HEADER section entities = (value == b'ENTITIES') break elif code == 9 and value == b'$DWGCODEPAGE': fetch_header_var = 'ENCODING' elif code == 9 and value == b'$ACADVER': fetch_header_var = 'VERSION' elif fetch_header_var == 'ENCODING': encoding = toencoding(value.decode()) fetch_header_var = None elif fetch_header_var == 'VERSION': version = value.decode() fetch_header_var = None prev_code = code if version >= 'AC1021': encoding = 'utf-8' queued: Optional[DXFEntity] = None tags: List[DXFTag] = [] linked_entity = entity_linker() for tag in tag_compiler(binary_tagger(stream, encoding)): code = tag.code value = tag.value if entities: if code == 0 and value == 'ENDSEC': if queued: yield queued return if code == 0: if len(tags) and tags[0].value in requested_types: entity = factory.load(ExtendedTags(tags)) if not linked_entity( entity) and entity.dxf.paperspace == 0: if queued: # queue one entity for collecting linked entities (VERTEX, ATTRIB) yield queued queued = entity tags = [tag] else: tags.append(tag) continue # if entities - nothing else matters elif code == 2 and prev_code == 0 and prev_value == 'SECTION': entities = (value == 'ENTITIES') prev_code = code prev_value = value
def processor(): return SubclassProcessor(ExtendedTags.from_text(TEST_1))
class DimStyle(DXFEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_DIMSTYLETEMPLATE) DXFATTRIBS = DXFAttributes( DefSubclass( None, { 'handle': DXFAttr(105), 'name': DXFAttr(2), 'flags': DXFAttr(70), 'dimpost': DXFAttr(3), 'dimapost': DXFAttr(4), 'dimblk': DXFAttr(5), 'dimblk1': DXFAttr(6), 'dimblk2': DXFAttr(7), 'dimscale': DXFAttr(40), 'dimasz': DXFAttr(41), 'dimexo': DXFAttr(42), 'dimdli': DXFAttr(43), 'dimexe': DXFAttr(44), 'dimrnd': DXFAttr(45), 'dimdle': DXFAttr(46), 'dimtp': DXFAttr(47), 'dimtm': DXFAttr(48), 'dimtxt': DXFAttr(140), 'dimcen': DXFAttr(141), 'dimtsz': DXFAttr(142), 'dimaltf': DXFAttr(143, default=1.), 'dimlfac': DXFAttr(144), 'dimtvp': DXFAttr(145), 'dimtfac': DXFAttr(146), 'dimgap': DXFAttr(147), 'dimtol': DXFAttr(71), 'dimlim': DXFAttr(72), 'dimtih': DXFAttr(73), 'dimtoh': DXFAttr(74), 'dimse1': DXFAttr(75), 'dimse2': DXFAttr(76), 'dimtad': DXFAttr(77), # 0 center, 1 above, 4 below dimline 'dimzin': DXFAttr(78), 'dimalt': DXFAttr(170), 'dimaltd': DXFAttr(171), 'dimtofl': DXFAttr(172), 'dimsah': DXFAttr(173), 'dimtix': DXFAttr(174), 'dimsoxd': DXFAttr(175), 'dimclrd': DXFAttr(176), 'dimclre': DXFAttr(177), 'dimclrt': DXFAttr(178), })) CODE_TO_DXF_ATTRIB = dict(DXFATTRIBS.build_group_code_items(dim_filter)) def dim_attribs(self) -> Iterable[Tuple[str, DXFAttr]]: return ((name, attrib) for name, attrib in self.DXFATTRIBS.items() if name.startswith('dim')) def print_dim_attribs(self) -> None: for name, attrib in self.dim_attribs(): code = attrib.code value = self.get_dxf_attrib(name, None) if value is not None: print("{name} ({code}) = {value}".format(name=name, value=value, code=code)) def copy_to_header(self, dwg): attribs = self.dxfattribs() header = dwg.header header['$DIMSTYLE'] = self.dxf.name for name, value in attribs.items(): if name.startswith('dim'): header_var = '$' + name.upper() try: header[header_var] = value except DXFKeyError: logger.debug( 'Unsupported header variable: {}.'.format(header_var)) def set_arrows(self, blk: str = '', blk1: str = '', blk2: str = '') -> None: """ Set arrows by block names or AutoCAD standard arrow names, set dimtsz = 0 which disables tick. Args: blk: block/arrow name for both arrows, if dimsah == 0 blk1: block/arrow name for first arrow, if dimsah == 1 blk2: block/arrow name for second arrow, if dimsah == 1 """ self.set_dxf_attrib('dimblk', blk) self.set_dxf_attrib('dimblk1', blk1) self.set_dxf_attrib('dimblk2', blk2) self.set_dxf_attrib('dimtsz', 0) # use blocks # only existing BLOCK definitions allowed if self.drawing: blocks = self.drawing.blocks for b in (blk, blk1, blk2): if ARROWS.is_acad_arrow(b): # not real blocks continue if b and b not in blocks: raise DXFValueError( 'BLOCK "{}" does not exist.'.format(blk)) def set_tick(self, size: float = 1) -> None: """ Use oblique stroke as tick, disables arrows. Args: size: arrow size in daring units """ self.set_dxf_attrib('dimtsz', size) def set_text_align(self, halign: str = None, valign: str = None, vshift: float = None) -> None: """ Set measurement text alignment, `halign` defines the horizontal alignment (requires DXFR2000+), `valign` defines the vertical alignment, `above1` and `above2` means above extension line 1 or 2 and aligned with extension line. Args: halign: `left`, `right`, `center`, `above1`, `above2`, requires DXF R2000+ valign: `above`, `center`, `below` vshift: vertical text shift, if `valign` is `center`; >0 shift upward, <0 shift downwards """ if valign: valign = valign.lower() self.set_dxf_attrib('dimtad', DIMTAD[valign]) if valign == 'center' and vshift is not None: self.set_dxf_attrib('dimtvp', vshift) try: if halign: self.set_dxf_attrib('dimjust', DIMJUST[halign.lower()]) except const.DXFAttributeError: raise DXFVersionError('DIMJUST require DXF R2000+') def set_text_format(self, prefix: str = '', postfix: str = '', rnd: float = None, dec: int = None, sep: str = None, leading_zeros: bool = True, trailing_zeros: bool = True): """ Set dimension text format, like prefix and postfix string, rounding rule and number of decimal places. Args: prefix: Dimension text prefix text as string postfix: Dimension text postfix text as string rnd: Rounds all dimensioning distances to the specified value, for instance, if DIMRND is set to 0.25, all distances round to the nearest 0.25 unit. If you set DIMRND to 1.0, all distances round to the nearest integer. dec: Sets the number of decimal places displayed for the primary units of a dimension. requires DXF R2000+ sep: "." or "," as decimal separator requires DXF R2000+ leading_zeros: suppress leading zeros for decimal dimensions if False trailing_zeros: suppress trailing zeros for decimal dimensions if False """ if prefix or postfix: self.dxf.dimpost = prefix + '<>' + postfix if rnd is not None: self.dxf.dimrnd = rnd # works only with decimal dimensions not inch and feet, US user set dimzin directly if leading_zeros is not None or trailing_zeros is not None: dimzin = 0 if leading_zeros is False: dimzin = const.DIMZIN_SUPPRESSES_LEADING_ZEROS if trailing_zeros is False: dimzin += const.DIMZIN_SUPPRESSES_TRAILING_ZEROS self.dxf.dimzin = dimzin try: if dec is not None: self.dxf.dimdec = dec if sep is not None: self.dxf.dimdsep = ord(sep) except const.DXFAttributeError: raise DXFVersionError('DIMDSEP and DIMDEC require DXF R2000+') def set_dimline_format(self, color: int = None, linetype: str = None, lineweight: int = None, extension: float = None, disable1: bool = None, disable2: bool = None): """ Set dimension line properties Args: color: color index linetype: linetype as string, requires DXF R2007+ lineweight: line weight as int, 13 = 0.13mm, 200 = 2.00mm, requires DXF R2000+ extension: extension length disable1: True to suppress first part of dimension line, requires DXF R2000+ disable2: True to suppress second part of dimension line, requires DXF R2000+ """ if color is not None: self.dxf.dimclrd = color if extension is not None: self.dxf.dimdle = extension try: if lineweight is not None: self.dxf.dimlwd = lineweight if disable1 is not None: self.dxf.dimsd1 = disable1 if disable2 is not None: self.dxf.dimsd2 = disable2 except const.DXFAttributeError: raise DXFVersionError( 'DIMLWD, DIMSD1 and DIMSD2 requires DXF R2000+') try: if linetype is not None: self.dxf.dimltype = linetype except const.DXFAttributeError: raise DXFVersionError('DIMLTYPE requires DXF R2007+') def set_extline_format(self, color: int = None, lineweight: int = None, extension: float = None, offset: float = None, fixed_length: float = None): """ Set common extension line attributes. Args: color: color index lineweight: line weight as int, 13 = 0.13mm, 200 = 2.00mm extension: extension length above dimension line offset: offset from measurement point fixed_length: set fixed length extension line, length below the dimension line """ if color is not None: self.dxf.dimclre = color if extension is not None: self.dxf.dimexe = extension if offset is not None: self.dxf.dimexo = offset try: if lineweight is not None: self.dxf.dimlwe = lineweight except const.DXFAttributeError: raise DXFVersionError('DIMLWE requires DXF R2000+') try: if fixed_length is not None: self.dxf.dimfxlon = 1 self.dxf.dimfxl = fixed_length except const.DXFAttributeError: raise DXFVersionError('DIMFXL requires DXF R2007+') def set_extline1(self, linetype: str = None, disable=False): """ Set extension line 1 attributes. Args: linetype: linetype for extension line 1, requires DXF R2007+ disable: disable extension line 1 if True """ if disable: self.dxf.dimse1 = 1 try: if linetype is not None: self.dxf.dimltex1 = linetype except const.DXFAttributeError: raise DXFVersionError('DIMLTEX1 requires DXF R2007+') def set_extline2(self, linetype: str = None, disable=False): """ Set extension line 2 attributes. Args: linetype: linetype for extension line 2, requires DXF R2007+ disable: disable extension line 2 if True """ if disable: self.dxf.dimse2 = 1 try: if linetype is not None: self.dxf.dimltex2 = linetype except const.DXFAttributeError: raise DXFVersionError('DIMLTEX2 requires DXF R2007+') def set_tolerance(self, upper: float, lower: float = None, hfactor: float = 1.0, align: str = None, dec: int = None, leading_zeros: bool = None, trailing_zeros: bool = None) -> None: """ Set tolerance text format, upper and lower value, text height factor, number of decimal places or leading and trailing zero suppression. Args: upper: upper tolerance value lower: lower tolerance value, if None same as upper hfactor: tolerance text height factor in relation to the dimension text height align: tolerance text alignment "TOP", "MIDDLE", "BOTTOM", required DXF R2000+ dec: Sets the number of decimal places displayed, required DXF R2000+ leading_zeros: suppress leading zeros for decimal dimensions if False, required DXF R2000+ trailing_zeros: suppress trailing zeros for decimal dimensions if False, required DXF R2000+ """ # exclusive tolerances self.dxf.dimtol = 1 self.dxf.dimlim = 0 self.dxf.dimtp = float(upper) if lower is not None: self.dxf.dimtm = float(lower) else: self.dxf.dimtm = float(upper) if hfactor is not None: self.dxf.dimtfac = float(hfactor) if self.dxfversion > 'AC1009': # works only with decimal dimensions not inch and feet, US user set dimzin directly if leading_zeros is not None or trailing_zeros is not None: dimtzin = 0 if leading_zeros is False: dimtzin = const.DIMZIN_SUPPRESSES_LEADING_ZEROS if trailing_zeros is False: dimtzin += const.DIMZIN_SUPPRESSES_TRAILING_ZEROS self.dxf.dimtzin = dimtzin if align is not None: self.dxf.dimtolj = const.MTEXT_INLINE_ALIGN[align.upper()] if dec is not None: self.dxf.dimtdec = int(dec) def set_limits(self, upper: float, lower: float, hfactor: float = 1.0, dec: int = None, leading_zeros: bool = None, trailing_zeros: bool = None) -> None: """ Set limits text format, upper and lower limit values, text height factor, number of decimal places or leading and trailing zero suppression. Args: upper: upper limit value added to measurement value lower: lower lower value subtracted from measurement value hfactor: limit text height factor in relation to the dimension text height dec: Sets the number of decimal places displayed, required DXF R2000+ leading_zeros: suppress leading zeros for decimal dimensions if False, required DXF R2000+ trailing_zeros: suppress trailing zeros for decimal dimensions if False, required DXF R2000+ """ # exclusive limits self.dxf.dimlim = 1 self.dxf.dimtol = 0 self.dxf.dimtp = float(upper) self.dxf.dimtm = float(lower) self.dxf.dimtfac = float(hfactor) if self.dxfversion > 'AC1009': # works only with decimal dimensions not inch and feet, US user set dimzin directly if leading_zeros is not None or trailing_zeros is not None: dimtzin = 0 if leading_zeros is False: dimtzin = const.DIMZIN_SUPPRESSES_LEADING_ZEROS if trailing_zeros is False: dimtzin += const.DIMZIN_SUPPRESSES_TRAILING_ZEROS self.dxf.dimtzin = dimtzin self.dxf.dimtolj = 0 # set bottom as default if dec is not None: self.dxf.dimtdec = int(dec)
def from_text(cls: Type[T], text: str, doc: 'Drawing' = None) -> T: """ Load constructor from text for testing. (internal API)""" return cls.load(ExtendedTags.from_text(text), doc)
def lwpolyline(layout): tags = tag_processor(ExtendedTags.from_text(LWPOLYLINE1)) return layout._dxffactory.wrap_entity(tags)
class Table: def __init__(self, entities: Iterable[Tags], drawing: 'Drawing'): self._table_header = None self._dxfname = None self._drawing = drawing self._table_entries = [] self._build_table_entries(iter(entities)) # start public interface @staticmethod def key(entity: Union[str, 'DXFEntity']) -> str: if not isinstance(entity, str): entity = entity.dxf.name return entity.lower() # table key is lower case @property def name(self) -> str: return tablename(self._dxfname) def has_entry(self, name: str) -> bool: """ Check if an table-entry 'name' exists. """ key = self.key(name) return any(self.key(entry) == key for entry in self) __contains__ = has_entry def new(self, name: str, dxfattribs: dict = None) -> 'DXFEntity': if self.has_entry(name): raise DXFTableEntryError('%s %s already exists!' % (self._dxfname, name)) dxfattribs = dxfattribs or {} dxfattribs['name'] = name return self.new_entry(dxfattribs) def get(self, name: str) -> 'DXFEntity': """ Get table-entry by name as WrapperClass(). """ key = self.key(name) for entry in iter(self): if self.key(entry) == key: return entry raise DXFTableEntryError(name) def remove(self, name: str) -> None: """ Remove table-entry from table and entitydb by name. """ entry = self.get(name) handle = entry.dxf.handle self.remove_handle(handle) def __len__(self) -> int: return len(self._table_entries) def __iter__(self) -> Iterable['DXFEntity']: for handle in self._table_entries: yield self.get_table_entry_wrapper(handle) # end public interface def _build_table_entries(self, entities: Iterator[Tags]) -> None: table_head = next(entities) if table_head[0].value != 'TABLE': raise DXFStructureError("Critical structure error in TABLES section.") self._dxfname = table_head[1].value self._table_header = ExtendedTags(table_head) # do not store the table head in the entity database for table_entry in entities: self._append_entry_handle(table_entry.get_handle()) @property def entitydb(self) -> 'EntityDB': return self._drawing.entitydb @property def handles(self) -> 'HandleGenerator': return self._drawing.entitydb.handles @property def dxffactory(self) -> 'DXFFactoryType': return self._drawing.dxffactory def _iter_table_entries_as_tags(self) -> Iterable[ExtendedTags]: """ Iterate over table-entries as Tags(). """ return (self.entitydb[handle] for handle in self._table_entries) def new_entry(self, dxfattribs: dict) -> 'DXFEntity': """ Create new table-entry of type 'self._dxfname', and add new entry to table. Does not check if an entry dxfattribs['name'] already exists! Duplicate entries are possible for Viewports. """ handle = self.handles.next() entry = self.dxffactory.new_entity(self._dxfname, handle, dxfattribs) self._add_entry(entry) return entry def duplicate_entry(self, name: str, new_name: str) -> 'DXFEntity': entry = self.get(name) xtags = self.entitydb.duplicate_tags(entry.tags) new_entity = self.dxffactory.wrap_entity(xtags) new_entity.dxf.name = new_name self._add_entry(new_entity) return new_entity def _add_entry(self, entry: Union[ExtendedTags, 'DXFEntity']) -> None: """ Add table-entry to table and entitydb. """ if isinstance(entry, ExtendedTags): tags = entry else: tags = entry.tags handle = self.entitydb.add_tags(tags) self._append_entry_handle(handle) def _append_entry_handle(self, handle: str) -> None: if handle not in self._table_entries: self._table_entries.append(handle) def get_table_entry_wrapper(self, handle: str) -> 'DXFEntity': tags = self.entitydb[handle] return self.dxffactory.wrap_entity(tags) def write(self, tagwriter: 'TagWriter') -> None: """ Write DXF representation to stream, stream opened with mode='wt'. """ def prologue(): self._update_owner_handles() self._update_meta_data() tagwriter.write_tags(self._table_header) def content(): for tags in self._iter_table_entries_as_tags(): tagwriter.write_tags(tags) def epilogue(): tagwriter.write_tag2(0, 'ENDTAB') prologue() content() epilogue() def _update_owner_handles(self) -> None: if self._drawing.dxfversion <= 'AC1009': return # no owner handles owner_handle = self._table_header.get_handle() for entry in iter(self): if not entry.supports_dxf_attrib('owner'): raise DXFAttributeError(repr(entry)) entry.dxf.owner = owner_handle def _update_meta_data(self) -> None: count = len(self) if self._drawing.dxfversion > 'AC1009': subclass = self._table_header.get_subclass('AcDbSymbolTable') else: subclass = self._table_header.noclass subclass.set_first(DXFTag(70, count)) def remove_handle(self, handle: str) -> None: """ Remove table-entry from table and entitydb by handle. """ self._table_entries.remove(handle) del self.entitydb[handle] def audit(self, auditor) -> None: """ Checks for table entries with same key. """ entries = sorted(self._table_entries, key=lambda e: self.key(e)) prev_key = None for entry in entries: key = self.key(entry) if key == prev_key: auditor.add_error( code=Error.DUPLICATE_TABLE_ENTRY_NAME, message="Duplicate table entry name '{1}' in table {0}".format(self.name, entry.dxf.name), dxf_entity=self, data=key, ) prev_key = key
class Shape(ModernGraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_SHAPE_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, shape_subclass)
def lwtags(text): tags = ExtendedTags.from_text(text) return tags.get_subclass("AcDbPolyline")
class SeqEnd(r12graphics.SeqEnd): __slots__ = () TEMPLATE = ExtendedTags.from_text( " 0\nSEQEND\n 5\n0\n330\n 0\n100\nAcDbEntity\n") DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass)
class Arc(r12graphics.Arc, ModernGraphicEntityExtension): __slots__ = () TEMPLATE = ExtendedTags.from_text(_ARC_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass, arc_subclass)
class Circle(r12graphics.Circle, ModernGraphicEntityExtension): __slots__ = () TEMPLATE = ExtendedTags.from_text(_CIRCLE_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass)
class Point(r12graphics.Point, ModernGraphicEntityExtension): __slots__ = () TEMPLATE = ExtendedTags.from_text(_POINT_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, point_subclass)
class Line(r12graphics.Line, ModernGraphicEntityExtension): __slots__ = () TEMPLATE = ExtendedTags.from_text(_LINETEMPLATE) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, line_subclass)