Ejemplo n.º 1
0
 def mleader(self, text):
     return MultiLeader.load(ExtendedTags.from_text(text))
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
def processor():
    return SubclassProcessor(ExtendedTags.from_text(TEST_1))
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
def lwpolyline(layout):
    tags = tag_processor(ExtendedTags.from_text(LWPOLYLINE1))
    return layout._dxffactory.wrap_entity(tags)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
class Shape(ModernGraphicEntity):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_SHAPE_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, shape_subclass)
Ejemplo n.º 9
0
def lwtags(text):
    tags = ExtendedTags.from_text(text)
    return tags.get_subclass("AcDbPolyline")
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
class Arc(r12graphics.Arc, ModernGraphicEntityExtension):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_ARC_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass,
                               arc_subclass)
Ejemplo n.º 12
0
class Circle(r12graphics.Circle, ModernGraphicEntityExtension):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_CIRCLE_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass)
Ejemplo n.º 13
0
class Point(r12graphics.Point, ModernGraphicEntityExtension):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_POINT_TPL)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, point_subclass)
Ejemplo n.º 14
0
class Line(r12graphics.Line, ModernGraphicEntityExtension):
    __slots__ = ()
    TEMPLATE = ExtendedTags.from_text(_LINETEMPLATE)
    DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, line_subclass)