About creation of an arc object

230 views
Skip to first unread message

Can Telkenaroglu

unread,
Oct 7, 2019, 2:56:52 PM10/7/19
to python-ezdxf
Hello,

I am parsing a DXF file and trying to convert an SVG file. The following method work for other types of entities except Arc. I have two problems:
1) Why can not I init an Arc object by passing all relevant parameters (center, radius, start_angle, end_angle) but have to use methods of ConstructionArc? Are they able to produce both circlic and elliptic arcs?
 
2) When I try to add the arc to the drawing, I receive the following error. How can I resolve this issue. Thank you for your help.

class ARC(dxfEntity):

 
def create_svg_entity(self, dwg, x_offset=0, y_offset=0):
 cpx
= self.entity.dxf.center[0]
 cpy
= self.entity.dxf.center[1]
 r
= self.entity.dxf.radius
 sa
= self.entity.dxf.start_angle
 ea
= self.entity.dxf.end_angle
 clr
= self.entity.dxf.color
 spx
= r * math.cos(math.radians(sa)) + cpx
 spy
= r * math.sin(math.radians(sa)) + cpy
 epx
= r * math.cos(math.radians(ea)) + cpx
 epy
= r * math.sin(math.radians(ea)) + cpy
 CCW
= ea < sa
 
if ea < sa:
 ea
+= 360
 angleInRadians
= (ea-sa) * math.pi / 180
 c
= Vec2([cpx, cpy])
 saInRadians
= sa * math.pi / 180
 eaInRadians
= ea * math.pi / 180


 
# cArc = ConstructionArc(center=c, radius=r, start_angle=saInRadians, end_angle=eaInRadians, is_counter_clockwise= CCW)
 arc
= ConstructionArc.from_2p_radius(start_point=Vec2([spx, spy]), end_point=([epx, epy]), radius=r, ccw= CCW)
 arcc
= Arc(arc)
 dwg
.add(arcc) #Here I receive the following error:
 
return

Error

Traceback (most recent call last):
 
File "main.py", line 36, in <module>
...
 
File "/home/Git/plan-conversion/svgwrite/drawing.py", line 74, in get_xml
   
return super(Drawing, self).get_xml()
 
File "/home/Git/plan-conversion/svgwrite/base.py", line 218, in get_xml
    xml
.append(element.get_xml())
AttributeError: 'Arc' object has no attribute 'get_xml'



Can Telkenaroglu

unread,
Oct 9, 2019, 3:04:01 PM10/9/19
to python-ezdxf
I modified math/arc.py and entities/arc.py as follows:


# Copyright (c) 2018 Manfred Moitzi
# License: MIT License
from typing import TYPE_CHECKING, Tuple

from .vector import Vec2
from .bbox import BoundingBox2d
from .construct2d import ConstructionTool, enclosing_angles
from .circle import ConstructionCircle
from .ucs import OCS, UCS
import math

if TYPE_CHECKING:
   
from ezdxf.eztypes import Vertex, BaseLayout
   
from ezdxf.eztypes import Arc as DXFArc

QUARTER_ANGLES
= [0, math.pi * .5, math.pi, math.pi * 1.5]


class ConstructionArc(ConstructionTool):
   
"""
    This is a helper class to create parameters for the DXF :class:`~ezdxf.entities.Arc` class.

    Args:
        center: center point as :class:`Vec2` compatible object
        radius: radius
        start_angle: start angle in degrees
        end_angle: end angle in degrees
        is_counter_clockwise: swaps start- and end angle if ``False``

    """
    center = Vec2([0,0])
    radius
= 0.0
    start_angle = 0.0
    end_angle = 0.0
    is_counter_clockwise = True

    def __init__(self,
                 center: 'Vertex' = (0, 0),
                 radius: float = 1,
                 start_angle: float = 0,
                 end_angle: float = 360,
                 is_counter_clockwise: bool = True):

       
self.center = Vec2(center)
       
ConstructionArc.center = self.center

       
self.radius = radius
       
ConstructionArc.radius = self.radius

       
self.is_counter_clockwise = is_counter_clockwise
       
ConstructionArc.is_counter_clockwise = self.is_counter_clockwise

       
if is_counter_clockwise:
           
self.start_angle = start_angle
           
self.end_angle = end_angle
       
else:
           
self.start_angle = end_angle
           
self.end_angle = start_angle

       
ConstructionArc.start_angle = self.start_angle
       
ConstructionArc.end_angle = self.end_angle


   
class __metaclass__(type):
       
@property
        def center(cls):
           
return cls.center

       
@center.setter
        def center(cls, value):
           
cls.center = value

       
@property
        def radius(cls):
           
return cls.raius

       
@radius.setter
        def radius(cls, value):
           
cls.radius = value

       
@property
        def start_angle(cls):
           
return cls.start_angle

       
@start_angle.setter
        def start_angle(cls, value):
           
cls.start_angle = value

       
@property
        def end_angle(cls):
           
return cls.end_angle

       
@end_angle.setter
        def end_angle(cls, value):
           
cls.end_angle = value

       
@property
        def is_counter_clockwise(cls):
           
return cls.is_counter_clockwise

       
@is_counter_clockwise.setter
        def is_counter_clockwise(cls, value):
           
cls.is_counter_clockwise = value

   
@property
    def start_point(self) -> 'Vec2':
       
""" start point of arc as :class:`Vec2`. """
        return self.center + Vec2.from_deg_angle(self.start_angle, self.radius)

   
@property
    def end_point(self) -> 'Vec2':
       
""" end point of arc as :class:`Vec2`. """
        return self.center + Vec2.from_deg_angle(self.end_angle, self.radius)

   
@property
    def bounding_box(self) -> 'BoundingBox2d':
       
""" bounding box of arc as :class:`BoundingBox2d`. """
        bbox = BoundingBox2d((self.start_point, self.end_point))
        bbox
.extend(self.main_axis_points())
       
return bbox

   
@staticmethod
    def get_arc_def(cls):
        data
= [cls.center, cls.radius, cls.start_angle, cls.end_angle, cls.is_counter_clockwise]
       
# print(data)
        return data



   
def main_axis_points(self):
        center
= self.center
        radius
= self.radius
        start
= math.radians(self.start_angle)
       
end = math.radians(self.end_angle)
       
for angle in QUARTER_ANGLES:
           
if enclosing_angles(angle, start, end):
               
yield center + Vec2.from_angle(angle, radius)

   
def move(self, dx: float, dy: float) -> None:
       
"""
        Move arc about `dx` in x-axis and about `dy` in y-axis.

        Args:
            dx: translation in x-axis
            dy: translation in y-axis

        """
        self.center += Vec2((dx, dy))

   
@property
    def start_angle_rad(self) -> float:
       
""" start angle in radians. """
        return math.radians(self.start_angle)

   
@property
    def end_angle_rad(self) -> float:
       
""" end angle in radians. """
        return math.radians(self.end_angle)

   
@staticmethod
    def validate_start_and_end_point(start_point: 'Vertex', end_point: 'Vertex') -> Tuple[Vec2, Vec2]:
        start_point
= Vec2(start_point)
        end_point
= Vec2(end_point)
       
if start_point == end_point:
           
raise ValueError("start- and end point has to be different points.")
       
return start_point, end_point

   
@classmethod
    def from_2p_angle(cls, start_point: 'Vertex', end_point: 'Vertex', angle: float,
                      ccw: bool = True) -> 'ConstructionArc':
       
"""
        Create arc from two points and enclosing angle. Additional precondition: arc goes by default in counter
        clockwise orientation from `start_point` to `end_point`, can be changed by `ccw` = ``False``.

        Args:
            start_point: start point as :class:`Vec2` compatible object
            end_point: end point as :class:`Vec2` compatible object
            angle: enclosing angle in degrees
            ccw: counter clockwise direction if ``True``

        """
        start_point, end_point = cls.validate_start_and_end_point(start_point, end_point)
        angle
= math.radians(angle)
       
if angle == 0:
           
raise ValueError("angle can not be 0.")
       
if ccw is False:
            start_point
, end_point = end_point, start_point
        alpha2
= angle / 2.
        distance = end_point.distance(start_point)
        distance2
= distance / 2.
        radius = distance2 / math.sin(alpha2)
        height
= distance2 / math.tan(alpha2)
        mid_point
= end_point.lerp(start_point, factor=.5)

        distance_vector
= end_point - start_point
        height_vector
= distance_vector.orthogonal().normalize(height)
        center
= mid_point + height_vector
       
return ConstructionArc(
           
center=center,
            radius=radius,
            start_angle=(start_point - center).angle_deg,
            end_angle=(end_point - center).angle_deg,
            is_counter_clockwise=True,
        )

   
@classmethod
    def from_2p_radius(cls, start_point: 'Vertex', end_point: 'Vertex', radius: float, ccw: bool = True,
                       center_is_left: bool = True) -> 'ConstructionArc':
       
"""
        Create arc from two points and arc radius. Additional precondition: arc goes by default in counter clockwise
        orientation from `start_point` to `end_point` can be changed by `ccw` = ``False``.

        The parameter `center_is_left` defines if the center of the arc is left or right of the line from `start_point`
        to `end_point`. Parameter `ccw` = ``False`` swaps start- and end point, which inverts the meaning of
        ``center_is_left``.

        Args:
            start_point: start point as :class:`Vec2` compatible object
            end_point: end point as :class:`Vec2` compatible object
            radius: arc radius
            ccw: counter clockwise direction if ``True``
            center_is_left: center point of arc is left of line from start- to end point if ``True``

        """
        start_point, end_point = cls.validate_start_and_end_point(start_point, end_point)
        radius
= float(radius)
       
if radius <= 0:
           
raise ValueError("radius has to be > 0.")
       
if ccw is False:
            start_point
, end_point = end_point, start_point

        mid_point
= end_point.lerp(start_point, factor=.5)
        distance
= end_point.distance(start_point)
        distance2
= distance / 2.
        val = radius ** 2 - distance2 ** 2
        height = math.sqrt(val)

        center
= mid_point + (end_point - start_point).orthogonal(ccw=center_is_left).normalize(height)

       
return ConstructionArc(
           
center=center,
            radius=radius,
            start_angle=(start_point - center).angle_deg,
            end_angle=(end_point - center).angle_deg,
            is_counter_clockwise=True,
        )

   
@classmethod
    def from_3p(cls, start_point: 'Vertex', end_point: 'Vertex', def_point: 'Vertex',
                ccw: bool = True) -> 'ConstructionArc':
       
"""
        Create arc from three points. Additional precondition: arc goes in counter clockwise
        orientation from `start_point` to `end_point`.

        Args:
            start_point: start point as :class:`Vec2` compatible object
            end_point: end point as :class:`Vec2` compatible object
            def_point: additional definition point as :class:`Vec2` compatible object
            ccw: counter clockwise direction if ``True``

        """
        start_point, end_point = cls.validate_start_and_end_point(start_point, end_point)
        def_point
= Vec2(def_point)
       
if def_point == start_point or def_point == end_point:
           
raise ValueError("def point has to be different to start- and end point")

        circle
= ConstructionCircle.from_3p(start_point, end_point, def_point)
        center
= Vec2(circle.center)
       
return ConstructionArc(
           
center=center,
            radius=circle.radius,
            start_angle=(start_point - center).angle_deg,
            end_angle=(end_point - center).angle_deg,
            is_counter_clockwise=ccw,
        )

   
def add_to_layout(self, layout: 'BaseLayout', ucs: UCS = None, dxfattribs: dict = None) -> 'DXFArc':
       
"""
        Add arc as DXF :class:`~ezdxf.entities.Arc` entity to a layout.

        Supports 3D arcs by using an :ref:`UCS`. An :class:`ConstructionArc` is always defined in the xy-plane, but by
        using an arbitrary UCS, the arc can be placed in 3D space, automatically OCS transformation included.

        Args:
            layout: destination layout as :class:`~ezdxf.layouts.BaseLayout` object
            ucs: place arc in 3D space by :class:`~ezdxf.math.UCS` object
            dxfattribs: additional DXF attributes for DXF :class:`~ezdxf.entities.Arc` entity

        """

        if ucs is not None:
           
if dxfattribs is None:
                dxfattribs
= {}
            dxfattribs
['extrusion'] = ucs.uz
            ocs
= OCS(ucs.uz)
            wcs_center
= ucs.to_wcs(self.center)
            ocs_center
= ocs.from_wcs(wcs_center)
            arc
= self.__class__(radius=self.radius)
            arc
.center = ocs_center
            arc
.start_angle = ucs.to_ocs_angle_deg(self.start_angle)
            arc
.end_angle = ucs.to_ocs_angle_deg(self.end_angle)
       
else:
            arc
= self

        return layout.add_arc(
           
center=arc.center,
            radius=arc.radius,
            start_angle=arc.start_angle,
            end_angle=arc.end_angle,
            dxfattribs=dxfattribs,
        )


and


# Copyright (c) 2019 Manfred Moitzi
# License: MIT License
# Created 2019-02-15
from typing import TYPE_CHECKING
from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass
from ezdxf.lldxf.const import DXF12, SUBCLASS_MARKER
from .dxfentity import base_class, SubclassProcessor
from .dxfgfx import acdb_entity
from .circle import acdb_circle, Circle
from .factory import register_entity
from ezdxf.math import ConstructionArc
from xml.etree.ElementTree import Element
import math

if TYPE_CHECKING:
   
from ezdxf.eztypes import TagWriter, DXFNamespace

__all__
= ['Arc']


acdb_arc
= DefSubclass('AcDbArc', {
   
'start_angle': DXFAttr(50, default=0),
    'end_angle': DXFAttr(51, default=360),
})


@register_entity
class Arc(Circle):
   
""" DXF ARC entity """
    DXFTYPE = 'ARC'
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_circle, acdb_arc)

   
def load_dxf_attribs(self, processor: SubclassProcessor = None) -> 'DXFNamespace':
        dxf
= super().load_dxf_attribs(processor)
       
if processor:
            tags
= processor.load_dxfattribs_into_namespace(dxf, acdb_arc)
           
if len(tags) and not processor.r12:
                processor
.log_unprocessed_tags(tags, subclass=acdb_arc.name)
               
print(dxf)
       
return dxf

   
def get_xml(self):
        data
= ConstructionArc.get_arc_def(ConstructionArc)
       
print(data)

        center
= data[0]
        cx
= str(center[0])
        cy
= str(center[1])
        r
= str(data[1])
        start_angle
= str(data[2])
        end_angle
= str(data[3])
        is_counter_clockwise
= str(data[4])

        tags
= "Arc cx=\"" + cx + "\" cy=\"" + cy + "\" radius=\"" + r + "\" start_angle=\"" + start_angle
        tags
= tags + "\" end_angle=\"" + end_angle + "\" CCW=\"" + is_counter_clockwise + "\""
        elem = Element(tags)
       
# print(elem)
        return elem

   
def export_entity(self, tagwriter: 'TagWriter') -> None:
       
""" Export entity specific data as DXF tags. """
        # base class export is done by parent class
        super().export_entity(tagwriter)
       
# AcDbEntity export is done by parent class
        # AcDbCircle export is done by parent class
        if tagwriter.dxfversion > DXF12:
            tagwriter
.write_tag2(SUBCLASS_MARKER, acdb_arc.name)
       
# for all DXF versions
        self.dxf.export_dxf_attribs(tagwriter, ['start_angle', 'end_angle'])



All seems to be finely working.
Reply all
Reply to author
Forward
0 new messages