def test_vertices(): angles = [0, 45, 90, 135, -45, -90, -135, 180] arc = ConstructionArc(center=(1, 1)) vertices = list(arc.vertices(angles)) for v, a in zip(vertices, angles): a = math.radians(a) assert v.isclose(Vec2((1 + math.cos(a), 1 + math.sin(a))))
def from_arc(cls, arc: ConstructionArc, start_width: float, end_width: float, segments: int = 64) -> 'CurvedTrace': """ Create curved trace from an arc. Args: arc: :class:`~ezdxf.math.ConstructionArc` object start_width: start width end_width: end width segments: count of segments for full circle (360 degree) approximation, partial arcs have proportional less segments, but at least 3 Raises: ValueError: if arc.radius <= 0 """ if arc.radius <= 0: raise ValueError(f'Invalid radius: {arc.radius}.') curve_trace = cls() count = max(math.ceil(arc.angle_span / 360.0 * segments), 3) + 1 center = Vec2(arc.center) for point, width in zip(arc.vertices(arc.angles(count)), linspace(start_width, end_width, count)): curve_trace._append(point, point - center, width) return curve_trace
def test_point_is_not_in_arc_range(p): """ Test if the angle defined by arc.center and point "p" is NOT in the range arc.start_angle to arc.end_angle: """ arc = ConstructionArc((0, 0), 1, -90, 90) assert arc._is_point_in_arc_range(Vec2(p)) is False
def test_angle_span(): assert ConstructionArc(start_angle=30, end_angle=270).angle_span == 240 # crossing 0-degree: assert ConstructionArc(start_angle=30, end_angle=270, is_counter_clockwise=False).angle_span == 120 # crossing 0-degree: assert ConstructionArc(start_angle=300, end_angle=60).angle_span == 120 assert ConstructionArc(start_angle=300, end_angle=60, is_counter_clockwise=False).angle_span == 240
def test_tangents(): angles = [0, 45, 90, 135, -45, -90, -135, 180] sin45 = math.sin(math.pi / 4) result = [(0, 1), (-sin45, sin45), (-1, 0), (-sin45, -sin45), (sin45, sin45), (1, 0), (sin45, -sin45), (0, -1)] arc = ConstructionArc(center=(1, 1)) vertices = list(arc.tangents(angles)) for v, r in zip(vertices, result): assert v.isclose(Vec2(r))
def test_bounding_box(): bbox = ConstructionArc(center=(0, 0), radius=1, start_angle=0, end_angle=90).bounding_box assert bbox.extmin == (0, 0) assert bbox.extmax == (1, 1) bbox = ConstructionArc(center=(0, 0), radius=1, start_angle=0, end_angle=180).bounding_box assert bbox.extmin == (-1, 0) assert bbox.extmax == (1, 1) bbox = ConstructionArc(center=(0, 0), radius=1, start_angle=270, end_angle=90).bounding_box assert bbox.extmin == (0, -1) assert bbox.extmax == (1, 1)
def test_spatial_arc_from_3p(): start_point_wcs = Vector(0, 1, 0) end_point_wcs = Vector(1, 0, 0) def_point_wcs = Vector(0, 0, 1) ucs = UCS.from_x_axis_and_point_in_xy(origin=def_point_wcs, axis=end_point_wcs - def_point_wcs, point=start_point_wcs) start_point_ucs = ucs.from_wcs(start_point_wcs) end_point_ucs = ucs.from_wcs(end_point_wcs) def_point_ucs = Vector(0, 0) arc = ConstructionArc.from_3p(start_point_ucs, end_point_ucs, def_point_ucs) dwg = ezdxf.new('R12') msp = dwg.modelspace() dxf_arc = arc.add_to_layout(msp, ucs) assert dxf_arc.dxftype() == 'ARC' assert isclose(dxf_arc.dxf.radius, 0.81649658, abs_tol=1e-9) assert isclose(dxf_arc.dxf.start_angle, -30) # ??? assert isclose(dxf_arc.dxf.end_angle, -150) # ??? assert is_close_points(dxf_arc.dxf.extrusion, (0.57735027, 0.57735027, 0.57735027), abs_tol=1e-9)
def test_rational_spline_from_circular_arc_has_same_end_points(): arc = ConstructionArc( start_angle=30, end_angle=330) spline = rational_bspline_from_arc( start_angle=arc.start_angle, end_angle=arc.end_angle) assert arc.start_point.isclose(spline.control_points[0]) assert arc.end_point.isclose(spline.control_points[-1])
def test_arc_from_3p(): p1 = (-15.73335, 10.98719) p2 = (-12.67722, 8.76554) p3 = (-8.00817, 12.79635) arc = ConstructionArc.from_3p(start_point=p1, end_point=p2, def_point=p3) arc_result = ConstructionArc( center=(-12.08260, 12.79635), radius=4.07443, start_angle=-153.638906, end_angle=-98.391676, ) assert arc.center.isclose(arc_result.center, abs_tol=1e-5) assert isclose(arc.radius, arc_result.radius, abs_tol=1e-5) assert isclose(arc.start_angle, arc_result.start_angle, abs_tol=1e-4) assert isclose(arc.end_angle, arc_result.end_angle, abs_tol=1e-4)
def construction_tool(self) -> ConstructionArc: """Returns ConstructionArc() for the OCS representation.""" return ConstructionArc( center=self.center, radius=self.radius, start_angle=self.start_angle, end_angle=self.end_angle, )
def test_angles(): arc = ConstructionArc(radius=1, start_angle=30, end_angle=60) assert tuple(arc.angles(2)) == (30, 60) assert tuple(arc.angles(3)) == (30, 45, 60) arc.start_angle = 180 arc.end_angle = 0 assert tuple(arc.angles(2)) == (180, 0) assert tuple(arc.angles(3)) == (180, 270, 0) arc.start_angle = -90 arc.end_angle = -180 assert tuple(arc.angles(2)) == (270, 180) assert tuple(arc.angles(4)) == (270, 0, 90, 180)
def test_arc_from_2p_radius(): p1 = (2, 1) p2 = (0, 3) radius = 2 arc = ConstructionArc.from_2p_radius(start_point=p1, end_point=p2, radius=radius) assert arc.center == (0, 1) assert isclose(arc.radius, radius) assert isclose(arc.start_angle, 0) assert isclose(arc.end_angle, 90) arc = ConstructionArc.from_2p_radius(start_point=p2, end_point=p1, radius=radius) assert arc.center == Vector(2, 3) assert isclose(arc.radius, radius) assert isclose(arc.start_angle, 180) assert isclose(arc.end_angle, -90)
def test_arc_from_2p_angle_simple(): p1 = (2, 1) p2 = (0, 3) angle = 90 arc = ConstructionArc.from_2p_angle(start_point=p1, end_point=p2, angle=angle) assert arc.center == (0, 1) assert isclose(arc.radius, 2) assert isclose(arc.start_angle, 0, abs_tol=1e-12) assert isclose(arc.end_angle, 90) arc = ConstructionArc.from_2p_angle(start_point=p2, end_point=p1, angle=angle) assert arc.center == (2, 3) assert isclose(arc.radius, 2) assert isclose(arc.start_angle, 180) assert isclose(arc.end_angle, -90)
def test_arc_from_2p_angle_complex(): p1 = (-15.73335, 10.98719) p2 = (-12.67722, 8.76554) angle = 55.247230 arc = ConstructionArc.from_2p_angle(start_point=p1, end_point=p2, angle=angle) arc_result = ConstructionArc( center=(-12.08260, 12.79635), radius=4.07443, start_angle=-153.638906, end_angle=-98.391676, ) assert arc.center.isclose(arc_result.center, abs_tol=1e-5) assert isclose(arc.radius, arc_result.radius, abs_tol=1e-5) assert isclose(arc.start_angle, arc_result.start_angle, abs_tol=1e-4) assert isclose(arc.end_angle, arc_result.end_angle, abs_tol=1e-4)
def test_rational_spline_curve_points_by_nurbs_python(): arc = ConstructionArc(end_angle=90) spline = rational_bspline_from_arc(end_angle=arc.end_angle) curve = spline.to_nurbs_python_curve() t = list(linspace(0, 1, 10)) points = list(spline.points(t)) expected = list(curve.evaluate_list(t)) for p, e in zip(points, expected): assert p.isclose(e)
def test_rational_spline_derivatives_by_nurbs_python(): arc = ConstructionArc(end_angle=90) spline = rational_bspline_from_arc(end_angle=arc.end_angle) curve = spline.to_nurbs_python_curve() t = list(linspace(0, 1, 10)) derivatives = list(spline.derivatives(t, n=1)) expected = [curve.derivatives(u, 1) for u in t] for (p, d1), (e, ed1) in zip(derivatives, expected): assert p.isclose(e) assert d1.isclose(ed1)
def construction_tool(self) -> ConstructionArc: """ Returns 2D construction tool :class:`ezdxf.math.ConstructionArc`, ignoring the extrusion vector. """ dxf = self.dxf return ConstructionArc( dxf.center, dxf.radius, dxf.start_angle, dxf.end_angle, )
def circular_arc_3p(self, data: bytes): bs = ByteStream(data) attribs = self._build_dxf_attribs() p1 = Vec3(bs.read_vertex()) p2 = Vec3(bs.read_vertex()) p3 = Vec3(bs.read_vertex()) arc_type = bs.read_struct('L')[0] arc = ConstructionArc.from_3p(p1, p3, p2) attribs['center'] = arc.center attribs['radius'] = arc.radius attribs['start_angle'] = arc.start_angle attribs['end_angle'] = arc.end_angle return self._factory('ARC', dxfattribs=attribs)
def dim_arc_3d(): doc = ezdxf.new(DXFVERSION, setup=True) msp = doc.modelspace() for center, radius, sa, ea, distance in [[Vec3(0, 0), 5, 60, 90, 2]]: arc = ConstructionArc(center, radius, sa, ea) ucs = UCS(origin=center + (5, 5)).rotate_local_x(math.radians(45)) msp.add_line(arc.center, arc.start_point).transform(ucs.matrix) msp.add_line(arc.center, arc.end_point).transform(ucs.matrix) dim = msp.add_arc_dim_arc(arc=arc, distance=distance, dimstyle="EZ_CURVED") dim.render(discard=BRICSCAD, ucs=ucs) doc.set_modelspace_vport(height=30) doc.saveas(OUTDIR / f"dim_arc_3d_{DXFVERSION}.dxf")
def test_rational_spline_from_circular_arc_has_expected_parameters(): arc = ConstructionArc(end_angle=90) spline = rational_bspline_from_arc(end_angle=arc.end_angle) assert spline.degree == 2 cpoints = spline.control_points assert len(cpoints) == 3 assert cpoints[0].isclose((1, 0, 0)) assert cpoints[1].isclose((1, 1, 0)) assert cpoints[2].isclose((0, 1, 0)) weights = spline.weights() assert len(weights) == 3 assert weights[0] == 1.0 assert weights[1] == math.cos(math.pi / 4) assert weights[2] == 1.0 # as BSpline constructor() s2 = BSpline.from_arc(arc) assert spline.control_points == s2.control_points
def cut_dxf(dxf_file: Drawing, center: Vector, side: Vector): """ 切割dxf文件. Args: dxf_file: dxf文件路径, R12格式推荐. center: 切割线起点 side: 切割线终点 Returns: (float, Vector) : 交点至起点距离, 交点坐标. 如无交点则返回 None,None. """ cutLine = ConstructionLine(center, side) pts = [] # doc = ezdxf.readfile(dxf_file) doc = dxf_file msp = doc.modelspace() for e in msp: if e.dxftype() == 'LINE': # byLine = ConstructionLine(e.dxf.start, e.dxf.end) coord = intersection_seg_seg(list(e.dxf.start), list(e.dxf.end), list(cutLine.start), list(cutLine.end)) # pt = cutLine.intersect(byLine) if coord != None and len(coord) != 0: pt = Vec2(*coord) pts.append(pt) elif e.dxftype() == 'ARC': byArc = ConstructionArc(e.dxf.center, e.dxf.radius, e.dxf.start_angle, e.dxf.end_angle) coord = intersection(cutLine, byArc) if coord != None and len(coord) != 0: pt = Vec2(*coord) pts.append(pt) if len(pts) != 0: pts.sort(key=lambda x: x.distance(center)) return pts[0].distance(center), pts[0] else: return None, None
def from_polyline(cls, polyline: 'DXFGraphic', segments: int = 64) -> 'TraceBuilder': """ Create a complete trace from a LWPOLYLINE or a 2D POLYLINE entity, the trace consist of multiple sub-traces if :term:`bulge` values are present. Args: polyline: :class:`~ezdxf.entities.LWPolyline` or 2D :class:`~ezdxf.entities.Polyline` segments: count of segments for bulge approximation, given count is for a full circle, partial arcs have proportional less segments, but at least 3 """ dxftype = polyline.dxftype() if dxftype == 'LWPOLYLINE': polyline = cast('LWPOLYLINE', polyline) const_width = polyline.dxf.const_width points = [] for x, y, start_width, end_width, bulge in polyline.lwpoints: location = Vec2(x, y) if const_width: # This is AutoCAD behavior, BricsCAD uses const width # only for missing width values. start_width = const_width end_width = const_width points.append((location, start_width, end_width, bulge)) closed = polyline.closed elif dxftype == 'POLYLINE': polyline = cast('POLYLINE', polyline) if not polyline.is_2d_polyline: raise TypeError('2D POLYLINE required') closed = polyline.is_closed default_start_width = polyline.dxf.default_start_width default_end_width = polyline.dxf.default_end_width points = [] for vertex in polyline.vertices: location = Vec2(vertex.dxf.location) if vertex.dxf.hasattr('start_width'): start_width = vertex.dxf.start_width else: start_width = default_start_width if vertex.dxf.hasattr('end_width'): end_width = vertex.dxf.end_width else: end_width = default_end_width bulge = vertex.dxf.bulge points.append((location, start_width, end_width, bulge)) else: raise TypeError(f'Invalid DXF type {dxftype}') if closed and not points[0][0].isclose(points[-1][0]): # close polyline explicit points.append(points[0]) trace = cls() store_bulge = None store_start_width = None store_end_width = None store_point = None linear_trace = LinearTrace() for point, start_width, end_width, bulge in points: if store_bulge: center, start_angle, end_angle, radius = bulge_to_arc( store_point, point, store_bulge) if radius > 0: arc = ConstructionArc( center, radius, math.degrees(start_angle), math.degrees(end_angle), is_counter_clockwise=True, ) if arc.start_point.isclose(point): sw = store_end_width ew = store_start_width else: ew = store_end_width sw = store_start_width trace.append(CurvedTrace.from_arc(arc, sw, ew, segments)) store_bulge = None if bulge != 0: # arc from prev_point to point if linear_trace.is_started: linear_trace.add_station(point, start_width, end_width) trace.append(linear_trace) linear_trace = LinearTrace() store_bulge = bulge store_start_width = start_width store_end_width = end_width store_point = point continue linear_trace.add_station(point, start_width, end_width) if linear_trace.is_started: trace.append(linear_trace) if closed and len(trace) > 1: # This is required for traces with multiple paths to create the correct # miter at the closing point. (only linear to linear trace). trace.close() return trace
def test_arc_does_not_intersect_arc(c, r, s, e): arc = ConstructionArc((0, 0), 1, -90, 90) assert len(arc.intersect_arc(ConstructionArc(c, r, s, e))) == 0
def test_flattening(r, s, e, sagitta, count): arc = ConstructionArc((0, 0), r, s, e) assert len(list(arc.flattening(sagitta))) == count
def test_arc_intersect_circle_in_two_points(c, r): arc = ConstructionArc((0, 0), 1, -90, 90) assert len(arc.intersect_circle(ConstructionCircle(c, r))) == 2
def test_arc_does_not_intersect_line(s, e): arc = ConstructionArc((0, 0), 1, 0, 90) assert len(arc.intersect_line(ConstructionLine(s, e))) == 0
def test_arc_does_not_intersect_circle(c, r): arc = ConstructionArc((0, 0), 1, -90, 90) assert len(arc.intersect_circle(ConstructionCircle(c, r))) == 0
def test_arc_intersect_arc_in_two_points(c, r, s, e): arc = ConstructionArc((0, 0), 1, -90, 90) assert len(arc.intersect_arc(ConstructionArc(c, r, s, e))) == 2
spline.apply_construction_tool(bspline) # Recreate ARC from SPLINE, if you ASSUME or KNOW it is an ARC: # for spline in msp.query("SPLINE): # ... # 1. get the B-spline construction tool from the SPLINE entity bspline = spline.construction_tool() max_t = bspline.max_t # calculate 3 significant points and 2 check points of the SPLINE: start, chk1, middle, chk2, end = bspline.points([ 0, max_t * 0.25, max_t * 0.5, max_t * 0.75, max_t ]) # create an arc from 3 points: arc_tool = ConstructionArc.from_3p(start, end, middle) arc_tool.add_to_layout(msp, dxfattribs={ "layer": "recreated arc", "color": ezdxf.const.MAGENTA, }) # This only works for flat B-splines in the xy-plane, a.k.a. 2D splines! # Check the assumption: center = arc_tool.center radius = arc_tool.radius err = max(abs(radius - p.distance(center)) for p in (chk1, chk2)) print(f"max error: {err:.6f}") # Warning: this does not proof that the assumption was correct, it is always # possible to create a diverging B-spline which matches the check points:
# create a 3D arc from 3 points in WCS start_point_wcs = Vec3(3, 0, 0) end_point_wcs = Vec3(0, 3, 0) def_point_wcs = Vec3(0, 0, 3) # create UCS ucs = UCS.from_x_axis_and_point_in_xy(origin=def_point_wcs, axis=start_point_wcs - def_point_wcs, point=end_point_wcs) start_point_ucs = ucs.from_wcs(start_point_wcs) end_point_ucs = ucs.from_wcs(end_point_wcs) def_point_ucs = Vec3(0, 0) # origin of UCS # create arc in the xy-plane of the UCS arc = ConstructionArc.from_3p(start_point_ucs, end_point_ucs, def_point_ucs) arc.add_to_layout(modelspace, ucs, dxfattribs={'color': 1}) # red arc arc = ConstructionArc.from_3p(end_point_ucs, start_point_ucs, def_point_ucs) arc.add_to_layout(modelspace, ucs, dxfattribs={'color': 2}) # yellow arc p1 = Vec3(0, -18) p2 = Vec3(0, +18) arc = ConstructionArc.from_2p_angle(p1, p2, 90) arc.add_to_layout(modelspace, dxfattribs={'color': 1}) arc = ConstructionArc.from_2p_angle(p1, p2, 90, ccw=False) arc.add_to_layout(modelspace, dxfattribs={'color': 2}) p1 = Vec3(20, -18) p2 = Vec3(20, +18)