class Image(ModernGraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_IMAGE_TPL) CLASS = ExtendedTags.from_text(_IMAGE_CLS) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, image_subclass) # flags for IMAGE SHOW_IMAGE = 1 SHOW_IMAGE_WHEN_NOT_ALIGNED = 2 USE_CLIPPING_BOUNDARY = 4 USE_TRANSPARENCY = 8 def post_new_hook(self): self.reset_boundary_path() def set_boundary_path(self, vertices): vertices = list(vertices) if len(vertices) > 2 and vertices[-1] != vertices[0]: vertices.append(vertices[0]) # close path, else AutoCAD crashes self._set_path_tags(vertices) self.set_flag_state(self.USE_CLIPPING_BOUNDARY, state=True) self.dxf.clipping = 1 self.dxf.clipping_boundary_type = 1 if len(vertices) < 3 else 2 def _set_path_tags(self, vertices): boundary = [DXFVertex(14, value) for value in vertices] subclasstags = Tags(tag for tag in self.tags.subclasses[2] if tag.code != 14) subclasstags.extend(boundary) self.tags.subclasses[2] = subclasstags self.dxf.count_boundary_points = len(vertices) def reset_boundary_path(self): lower_left_corner = (-.5, -.5) upper_right_corner = Vector(self.dxf.image_size) + lower_left_corner self._set_path_tags([lower_left_corner, upper_right_corner[:2]]) self.set_flag_state(Image.USE_CLIPPING_BOUNDARY, state=False) self.dxf.clipping = 0 self.dxf.clipping_boundary_type = 1 def get_boundary_path(self): image_subclass = self.tags.subclasses[2] return [tag.value for tag in image_subclass if tag.code == 14] def get_image_def(self): return self.dxffactory.wrap_handle(self.dxf.image_def) def destroy(self): super(Image, self).destroy() # remove rectors image_def = self.get_image_def() reactor_handle = self.get_dxf_attrib('image_def_reactor', None) if reactor_handle is None: return image_def.remove_reactor_handle(reactor_handle) if self.drawing is not None: reactor = self.dxffactory.wrap_handle(reactor_handle) self.drawing.objects.delete_entity(reactor)
class ImageDefReactor(DXFEntity): TEMPLATE = ExtendedTags.from_text(_IMAGE_DEF_REACTOR_TPL) CLASS = ExtendedTags.from_text(_IMAGE_DEF_REACTOR_CLS) DXFATTRIBS = DXFAttributes( none_subclass, DefSubclass( 'AcDbRasterImageDef', { 'image': DXFAttr(330), # handle to image }))
class PdfDefinition(DXFEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_PDF_DEF_TPL) CLASS = ExtendedTags.from_text(_PDF_DEF_CLS) DXFATTRIBS = DXFAttributes(none_subclass, underlay_def_subclass) @property def entity_name(self): return self.dxftype()[:3] + "UNDERLAY" def post_new_hook(self): self.set_reactors([self.dxf.owner])
class RasterVariables(DXFEntity): TEMPLATE = ExtendedTags.from_text(_RASTER_VARIABLES_TPL) CLASS = ExtendedTags.from_text(_RASTER_VARIABLES_CLS) DXFATTRIBS = DXFAttributes( none_subclass, DefSubclass('AcDbRasterVariables', { 'version': DXFAttr(90, default=0), 'frame': DXFAttr(70, default=0), # 0 = no frame; 1= show frame 'quality': DXFAttr(71, default=1), # 0=draft; 1=high 'units': DXFAttr(72, default=3), # 0 = None; 1 = mm; 2 = cm 3 = m; 4 = km; 5 = in 6 = ft; 7 = yd; 8 = mi }), )
class Linetype(DXFEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_LTYPETEMPLATE) DXFATTRIBS = DXFAttributes( DefSubclass( None, { 'handle': DXFAttr(5), 'name': DXFAttr(2), 'description': DXFAttr(3), 'length': DXFAttr(40), 'items': DXFAttr(73), })) @classmethod def new(cls, handle: str, dxfattribs: dict = None, dxffactory: 'DXFFactoryType' = None) -> DXFEntity: if dxfattribs is not None: pattern = dxfattribs.pop('pattern', [0.0]) length = dxfattribs.pop('length', 0.) else: pattern = [0.0] length = 0. entity = super(Linetype, cls).new(handle, dxfattribs, dxffactory) entity._setup_pattern(pattern, length) return entity def _setup_pattern(self, pattern: Sequence[float], length: float) -> None: # length parameter is required for complex line types # pattern: [2.0, 1.25, -0.25, 0.25, -0.25] - 1. element is total pattern length # pattern elements: >0 line, <0 gap, =0 point self.tags.noclass.append(DXFTag(73, len(pattern) - 1)) self.tags.noclass.append(DXFTag(40, float(pattern[0]))) self.tags.noclass.extend((DXFTag(49, float(p)) for p in pattern[1:]))
class Body(ModernGraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_BODY_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass) def get_acis_data(self): modeler_geometry = self.tags.subclasses[2] text_lines = convert_tags_to_text_lines(tag for tag in modeler_geometry if tag.code in (1, 3)) return crypt.decode(text_lines) def set_acis_data(self, text_lines): def cleanup(lines): for line in lines: yield line.rstrip().replace('\n', '') modeler_geometry = self.tags.subclasses[2] # remove existing text modeler_geometry[:] = (tag for tag in modeler_geometry if tag.code not in (1, 3)) modeler_geometry.extend( convert_text_lines_to_tags(crypt.encode(cleanup(text_lines)))) @contextmanager def edit_data(self): data = ModelerGeometryData(self) yield data self.set_acis_data(data.text_lines)
def test_set_flag_state(self): tags = ExtendedTags.from_text("70\n7\n10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) point.set_flag_state(1, state=False, name='flags') assert point.dxf.flags == 6 point.set_flag_state(1, state=True, name='flags') assert point.dxf.flags == 7
def test_packed_points_to_dxf_tags(): tags = ExtendedTags.from_text(LWPOLYLINE1) packed_points = LWPolylinePoints.from_tags(tags) tags = list(packed_points.dxftags()) assert len(tags) == 2 # just the points assert tags[0] == (10, (-.5, -.5)) assert tags[1] == (10, (.5, .5))
def test_get_flag_state(self): tags = ExtendedTags.from_text("70\n7\n10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) assert point.get_flag_state(1, name='flags') is True assert point.get_flag_state(2) is True assert point.get_flag_state(4) is True assert point.get_flag_state(16) is False
def test_callback(self): tags = ExtendedTags.from_text("10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) assert point.dxf.counter == 0 point.dxf.counter = 7 assert point.dxf.counter == 7 assert point.has_dxf_default_value('counter') is False
def test_error_set_point_with_wrong_axis_count(self): tags = ExtendedTags.from_text("13\n1.0\n23\n2.0\n40\n0.0\n") point = PointAccessor(tags) with pytest.raises(DXFValueError): point.dxf.flex = (3., 4., 5., 6.) with pytest.raises(DXFValueError): point.dxf.flex = (3., )
def test_safe_init(): tags = ExtendedTags.from_text("""0 DXFENTITY 1001 MOZMAN 1000 DataStr1 1000 DataStr2 1007 XXX 1040 3.14 1001 ACAD 1000 AutoDesk 1000 Revit""") xdata = XData.safe_init(tags.xdata) data = xdata.get("MOZMAN") assert (any(tag.code == 1007 for tag in data) is False), "invalid group code 1007 should be removed" assert "ACAD" in xdata
class Linetype(legacy.Linetype): __slots__ = () TEMPLATE = ExtendedTags.from_text(_LTYPETEMPLATE) DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, linetype_subclass) def _setup_pattern(self, pattern, length): complex_line_type = True if isstring(pattern) else False if complex_line_type: # a .lin like line type definition string self._setup_complex_pattern(pattern, length) else: # pattern: [2.0, 1.25, -0.25, 0.25, -0.25] - 1. element is total pattern length # pattern elements: >0 line, <0 gap, =0 point subclass = self.tags.get_subclass('AcDbLinetypeTableRecord') subclass.append(DXFTag(73, len(pattern) - 1)) subclass.append(DXFTag(40, float(pattern[0]))) for element in pattern[1:]: subclass.append(DXFTag(49, float(element))) subclass.append(DXFTag(74, 0)) def _setup_complex_pattern(self, pattern, length): tokens = lin_compiler(pattern) subclass = self.tags.get_subclass('AcDbLinetypeTableRecord') subclass.append(DXFTag(73, 0)) # temp length of 0 subclass.append(DXFTag(40, length)) count = 0 for token in tokens: if isinstance(token, DXFTag): if subclass[-1].code == 49: # useless 74 only after 49 :)) subclass.append(DXFTag(74, 0)) subclass.append(token) count += 1 else: # TEXT or SHAPE subclass.extend(token.complex_ltype_tags(self.drawing)) subclass.append(DXFTag(74, 0)) # useless 74 at the end :)) subclass.update(DXFTag(73, count))
class Line(GraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_LINE_TPL) DXFATTRIBS = make_attribs({ 'start': DXFAttr(10, xtype='Point2D/3D'), 'end': DXFAttr(11, xtype='Point2D/3D'), })
class Circle(GraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_CIRCLE_TPL) DXFATTRIBS = make_attribs({ 'center': DXFAttr(10, xtype='Point2D/3D'), 'radius': DXFAttr(40), })
def test_set_flag_state_none_existing_flags(self): tags = ExtendedTags.from_text("10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) point.set_flag_state(1, state=True, name='flags') assert point.dxf.flags == 1 with pytest.raises(DXFAttributeError): point.set_flag_state(1, state=True, name='plot_flags')
class MText(ModernGraphicEntity ): # MTEXT will be extended in DXF version AC1021 (ACAD 2007) __slots__ = () TEMPLATE = ExtendedTags.from_text(_MTEXT_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mtext_subclass) def get_text(self): tags = self.tags.get_subclass('AcDbMText') tail = "" parts = [] for tag in tags: if tag.code == 1: tail = tag.value if tag.code == 3: parts.append(tag.value) parts.append(tail) return "".join(parts) def set_text(self, text): tags = self.tags.get_subclass('AcDbMText') tags.remove_tags(codes=(1, 3)) str_chunks = split_string_in_chunks(text, size=250) if len(str_chunks) == 0: str_chunks.append("") while len(str_chunks) > 1: tags.append(DXFTag(3, str_chunks.pop(0))) tags.append(DXFTag(1, str_chunks[0])) return self def get_rotation(self): try: vector = self.dxf.text_direction except DXFValueError: rotation = self.get_dxf_attrib('rotation', 0.0) else: radians = math.atan2(vector[1], vector[0]) # ignores z-axis rotation = math.degrees(radians) return rotation def set_rotation(self, angle): del self.dxf.text_direction # *text_direction* has higher priority than *rotation*, therefore delete it self.dxf.rotation = angle return self def set_location(self, insert, rotation=None, attachment_point=None): self.dxf.insert = Vector(insert) if rotation is not None: self.set_rotation(rotation) if attachment_point is not None: self.dxf.attachment_point = attachment_point return self @contextmanager def edit_data(self): buffer = MTextData(self.get_text()) yield buffer self.set_text(buffer.text) buffer = edit_data # alias
class Layer(DXFEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_LAYERTEMPLATE) DXFATTRIBS = DXFAttributes( DefSubclass( None, { 'handle': DXFAttr(5), 'name': DXFAttr(2), 'flags': DXFAttr(70), 'color': DXFAttr(62), # dxf color index, if < 0 layer is off 'linetype': DXFAttr(6), })) FROZEN = 0b00000001 THAW = 0b11111110 LOCK = 0b00000100 UNLOCK = 0b11111011 def post_new_hook(self) -> None: if not is_valid_layer_name(self.dxf.name): raise DXFInvalidLayerName("Invalid layer name '{}'".format( self.dxf.name)) def is_frozen(self) -> bool: return self.dxf.flags & Layer.FROZEN > 0 def freeze(self) -> None: self.dxf.flags = self.dxf.flags | Layer.FROZEN def thaw(self) -> None: self.dxf.flags = self.dxf.flags & Layer.THAW def is_locked(self) -> bool: return self.dxf.flags & Layer.LOCK > 0 def lock(self) -> None: self.dxf.flags = self.dxf.flags | Layer.LOCK def unlock(self) -> None: self.dxf.flags = self.dxf.flags & Layer.UNLOCK def is_off(self) -> bool: return self.dxf.color < 0 def is_on(self) -> bool: return not self.is_off() def on(self) -> None: self.dxf.color = abs(self.dxf.color) def off(self) -> None: self.dxf.color = -abs(self.dxf.color) def get_color(self) -> int: return abs(self.dxf.color) def set_color(self, color: int) -> None: color = abs(color) if self.is_on() else -abs(color) self.dxf.color = color
def test_set_2d_point(self): point = PointAccessor( ExtendedTags.from_text("11\n1.0\n21\n2.0\n40\n3.0\n")) point.dxf.flat = (4, 5) assert 2 == len( point.tags.noclass ), 'points represented by just one tag since v0.6 (code, (x, y[, z]))' assert (4., 5.) == point.dxf.flat
def test_set_point(self): tags = ExtendedTags.from_text("10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) point.dxf.point = (7, 8, 9) assert 1 == len( tags.noclass ), 'points represented by just one tag since v0.6 (code, (x, y[, z]))' assert (7., 8., 9.) == point.dxf.point
def test_vertex_array_to_dxf_tags(): tags = ExtendedTags.from_text(SPLINE) vertices = VertexArray.from_tags(tags.get_subclass('AcDbSpline')) tags = list(vertices.dxftags()) assert len(tags) == 7 assert tags[0] == (10, (0., 0., 0.)) assert tags[1] == (10, (10., 10., 10.)) assert tags[-1] == (10, (60., 60., 60.))
class SeqEnd(GraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(" 0\nSEQEND\n 5\n0\n") DXFATTRIBS = DXFAttributes( DefSubclass(None, { 'handle': DXFAttr(5), 'paperspace': DXFAttr(67, default=0), }))
def test_set_2d_point_at_existing_3d_point(self): tags = ExtendedTags.from_text("13\n1.0\n23\n2.0\n33\n3.0\n") point = PointAccessor(tags) point.dxf.flex = (3., 4.) assert 1 == len( tags.noclass ), 'points represented by just one tag since v0.6 (code, (x, y[, z]))' assert (3., 4.) == point.dxf.flex
def test_vertex_array_to_dxf_tags(): tags = ExtendedTags.from_text(SPLINE) vertices = VertexArray.from_tags(tags.get_subclass('AcDbSpline')) tags = TagCollector.dxftags(vertices) assert len(tags) == 7 * 3 assert tags[0] == (10, 0.) assert tags[3] == (10, 10.) assert tags[-1] == (30, 60.)
def test_legacy_mode(): """Legacy mode does the same job as filter_subclass_markers().""" tags = ExtendedTags.from_text(LEICA_DISTO_TAGS, legacy=True) assert 9 == len(tags.noclass) assert 1 == len(tags.subclasses) assert tags.noclass[0] == (0, "LINE") assert tags.noclass[1] == (8, "LEICA_DISTO_3D") assert tags.noclass[-1] == (210, (0, 0, 1))
def test_getdxfattr_no_DXF_default_value_at_wrong_dxf_version(self): tags = ExtendedTags.from_text("10\n1.0\n20\n2.0\n30\n3.0\n") point = PointAccessor(tags) # just_AC1015 has a DXF default value, but the drawing has an insufficient DXF version with pytest.raises(DXFValueError): point.get_dxf_attrib('just_AC1015') # except the USER defined default value assert point.get_dxf_attrib('just_AC1015', 17) == 17
class Viewport(ModernGraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_VIEWPORT_TPL) DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, viewport_subclass) viewport_id = 2 # notes to id: # The id of the first viewport has to be 1, which is the definition of # paper space. For the following viewports it seems only important, that # the id is greater than 1. @property def AcDbViewport(self)->'Tags': return self.tags.subclasses[2] def get_next_viewport_id(self) -> int: current_id = Viewport.viewport_id Viewport.viewport_id += 1 return current_id def get_frozen_layer_handles(self) -> Iterable[str]: """ Returns generator of layer handels. """ return (tag.value for tag in self.AcDbViewport if tag.code == 331) def get_frozen_layer_entities(self) -> Iterable['Layer']: """ Returns generator of Layer() objects. """ if self.drawing is None: raise DXFAttributeError("'drawing attribute is None, can not build DXF entities.") wrapper = self.dxffactory.wrap_handle return (wrapper(handle) for handle in self.get_frozen_layer_handles()) def set_frozen_layers(self, layers: Iterable[Union[str, 'Layer']]) -> None: """ Set frozen layers for this viewport. Args: layers: list or generator of layer handles or Layer() objects. """ def layer_handles(): for layer in layers: if hasattr(layer, 'dxf'): yield layer.dxf.handle else: yield layer self.AcDbViewport.remove_tags([331]) # remove existing frozen layer tags frozen_layer_tags = [DXFTag(331, handle) for handle in layer_handles()] try: # insert frozen layer tags in front of the flags-tag # try to create order like in the DXF standard, because order is sometimes important insert_pos = self.AcDbViewport.tag_index(90) self.AcDbViewport[insert_pos:insert_pos] = frozen_layer_tags except DXFValueError: # flags-tag not found, just append frozen layer tags self.AcDbViewport.extend(frozen_layer_tags)
class AppID(DXFEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_APPIDTEMPLATE) DXFATTRIBS = DXFAttributes( DefSubclass(None, { 'handle': DXFAttr(5), 'name': DXFAttr(2), 'flags': DXFAttr(70), }))
class Arc(GraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_ARC_TPL) DXFATTRIBS = make_attribs({ 'center': DXFAttr(10, xtype='Point2D/3D'), 'radius': DXFAttr(40), 'start_angle': DXFAttr(50), 'end_angle': DXFAttr(51), })
def test_factory_load(): tags = ExtendedTags.from_text(POINT) e = factory.load(tags) assert e.dxftype() == 'POINT' assert e.doc is None assert e.dxf.handle == 'FEFE' assert e.dxf.owner is None assert e.is_alive is True assert e.is_virtual is True