def test_ray2d_intersect_with_vertical_and_horizontal(self): ray1 = ConstructionRay((-10, 10), (10, 10)) ray2 = ConstructionRay((5, 0), (5, 20)) point = ray1.intersect(ray2) assert point.y == 10 assert point.x == 5 assert point.isclose(Vec3(5.0, 10.0), abs_tol=1e-4)
def test_ray2d_intersect(self): ray1 = ConstructionRay((10, 1), (20, 10)) ray2 = ConstructionRay((17, -7), (-10, 3)) point = ray1.intersect(ray2) assert point.isclose(Vec3(5.7434, -2.8309), abs_tol=1e-4) assert ray1.is_parallel(ray2) is False
def test_ray2d_angle(self): ray = ConstructionRay((10, 10), angle=HALF_PI) assert ray._is_vertical is True ray = ConstructionRay((10, 10), angle=0) assert ray._is_horizontal is True ray = ConstructionRay((10, 10), angle=math.pi / 4) assert math.isclose(ray._slope, 1.0)
def test_ray2d_intersect_with_vertical(self): ray1 = ConstructionRay((10, 1), (10, -7)) ray2 = ConstructionRay((-10, 3), (17, -7)) point = ray1.intersect(ray2) assert point.x == 10 assert point.isclose(Vec3(10.0, -4.4074), abs_tol=1e-4) with pytest.raises(ArithmeticError): _ = ray1.yof(1)
def ray(v1, v2): if v1.isclose(v2): # vertices too close to define a ray, offset ray is parallel to segment: angle = (stations[segment].vertex - stations[segment + 1].vertex).angle return ConstructionRay(v1, angle) else: return ConstructionRay(v1, v2)
def test_init_with_angle(self): point = (10, 10) ray = ConstructionRay(point, angle=0) ray_normal = ray.orthogonal(point) assert ray_normal._is_vertical is True ray = ConstructionRay(point, angle=-HALF_PI) assert ray._is_horizontal is False assert ray._is_vertical is True
def test_intersect_ray_pass(): circle = ConstructionCircle((10.0, 10.0), 3) ray1_hor = ConstructionRay((10.0, 15.0), angle=0) ray2_hor = ConstructionRay((10.0, 5.0), angle=0) ray1_vert = ConstructionRay((5.0, 10.0), angle=HALF_PI) ray2_vert = ConstructionRay((15.0, 10.0), angle=-HALF_PI) ray3 = ConstructionRay((13.24, 14.95), angle=0.3992) assert len(circle.intersect_ray(ray1_hor)) == 0 assert len(circle.intersect_ray(ray2_hor)) == 0 assert len(circle.intersect_ray(ray1_vert)) == 0 assert len(circle.intersect_ray(ray2_vert)) == 0 assert len(circle.intersect_ray(ray3)) == 0
def test_two_close_horizontal_rays(self): p1 = (39340.75302672016, 32489.73349764998) p2 = (39037.75302672119, 32489.73349764978) p3 = (38490.75302672015, 32489.73349764997) ray1 = ConstructionRay(p1, p2) ray2 = ConstructionRay(p2, p3) assert ray1.is_horizontal is True assert ray2.is_horizontal is True assert ray1.is_parallel(ray2) is True assert ( math.isclose(ray1.slope, ray2.slope) is False ), "Only slope testing is not sufficient"
def test_ray2d_parallel(self): ray1 = ConstructionRay((17, -8), (-10, 2)) ray2 = ConstructionRay((-10, 3), (17, -7)) ray3 = ConstructionRay((-10, 4), (17, -6)) assert ray2.is_parallel(ray3) is True assert ray1.is_parallel(ray3) is True with pytest.raises(ParallelRaysError): _ = ray2.intersect(ray3)
def intersect(ray1: ConstructionRay, ray2: ConstructionRay, default: Vec2) -> Vec2: """ Intersect two rays but take parallel rays into account. """ try: v = ray1.intersect(ray2) except ParallelRaysError: v = default return v
def test_diagonal_ray(self, circle): ray_slope = ConstructionRay((5, 5), (16, 12)) cross_points = circle.intersect_ray(ray_slope) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 assert p1.isclose((8.64840, 7.3217), abs_tol=1e-4) assert p2.isclose((12.9986, 10.0900), abs_tol=1e-4)
def test_vertical_ray(self, circle): ray_vert = ConstructionRay((8.5, 10.0), angle=HALF_PI) cross_points = circle.intersect_ray(ray_vert) assert len(cross_points) == 2 p1, p2 = cross_points if p1[1] > p2[1]: p1, p2 = p2, p1 assert p1.isclose((8.5, 7.4019), abs_tol=1e-4) assert p2.isclose((8.5, 12.5981), abs_tol=1e-4)
def test_horizontal_ray(self, circle): ray_hor = ConstructionRay((10, 8.5), angle=0.0) cross_points = circle.intersect_ray(ray_hor) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 assert p1.isclose((7.4019, 8.5), abs_tol=1e-4) assert p2.isclose((12.5981, 8.5), abs_tol=1e-4)
def intersect(ray1: ConstructionRay, ray2: ConstructionRay, default: Vec2) -> Vec2: """Intersect two rays but take parallel rays into account.""" # check for nearly parallel rays pi/100 ~1.8 degrees if ray1.direction.angle_between(ray2.direction) < 0.031415: return default try: return ray1.intersect(ray2) except ParallelRaysError: return default
def test_horizontal_ray_through_mid_point(self, circle): ray_hor = ConstructionRay((10, 10), angle=0) cross_points = circle.intersect_ray(ray_hor) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert p1.isclose((7, 10), abs_tol=1e-5) assert p2.isclose((13, 10), abs_tol=1e-5)
def test_vertical_ray_through_mid_point(self, circle): ray_vert = ConstructionRay((10, 10), angle=HALF_PI) cross_points = circle.intersect_ray(ray_vert) assert len(cross_points) == 2 p1, p2 = cross_points if p1[1] > p2[1]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert p1.isclose((10, 7), abs_tol=1e-5) assert p2.isclose((10, 13), abs_tol=1e-5)
def test_diagonal_ray_through_mid_point(self, circle): ray_slope = ConstructionRay((10, 10), angle=HALF_PI / 2) cross_points = circle.intersect_ray(ray_slope) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert p1.isclose((7.8787, 7.8787), abs_tol=1e-4) assert p2.isclose((12.1213, 12.1213), abs_tol=1e-4)
def _setup(self) -> None: """ Calc setup values and determines the point order of the dimension line points. """ self.measure_points = [Vec3(point) for point in self.measure_points] dimlineray = ConstructionRay(self.dimlinepos, angle=radians(self.angle)) self.dimline_points = [ self._get_point_on_dimline(point, dimlineray) for point in self.measure_points ] self.point_order = self._indices_of_sorted_points(self.dimline_points) self._build_vectors()
def test_touch(testnum, x, y, _angle, abs_tol=1e-6): result = True ray = ConstructionRay((x, y), angle=_angle) points = circle.intersect_ray(ray, abs_tol=abs_tol) if len(points) != 1: result = False else: point = points[0] # print ("{0}: x= {1:.{places}f} y= {2:.{places}f} : x'= {3:.{places}f} y' = {4:.{places}f}".format(testnum, x, y, point[0], point[1], places=places)) if not isclose(point[0], x, abs_tol=abs_tol): result = False if not isclose(point[1], y, abs_tol=abs_tol): result = False return result
def test_bisectrix(self): ray1 = ConstructionRay((10, 10), angle=math.pi / 3) ray2 = ConstructionRay((3, -5), angle=math.pi / 2) ray3 = ConstructionRay((1, 1), angle=math.pi / 3) a = ray1.bisectrix(ray2) assert math.isclose(a._angle, 1.309, abs_tol=1e-4) assert math.isclose(a.yof(7), 12.80385, abs_tol=1e-4) with pytest.raises(ParallelRaysError): _ = ray1.bisectrix(ray3)
def test_intersect_ray_intersect(): circle = ConstructionCircle((10., 10.), 3) ray_vert = ConstructionRay((8.5, 10.), angle=HALF_PI) cross_points = circle.intersect_ray(ray_vert) assert len(cross_points) == 2 p1, p2 = cross_points if p1[1] > p2[1]: p1, p2 = p2, p1 assert is_close_points(p1, (8.5, 7.4019), abs_tol=1e-4) is True assert is_close_points(p2, (8.5, 12.5981), abs_tol=1e-4) is True ray_hor = ConstructionRay((10, 8.5), angle=0.) cross_points = circle.intersect_ray(ray_hor) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 assert is_close_points(p1, (7.4019, 8.5), abs_tol=1e-4) is True assert is_close_points(p2, (12.5981, 8.5), abs_tol=1e-4) is True ray_slope = ConstructionRay((5, 5), (16, 12)) cross_points = circle.intersect_ray(ray_slope) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 assert is_close_points(p1, (8.64840, 7.3217), abs_tol=1e-4) is True assert is_close_points(p2, (12.9986, 10.0900), abs_tol=1e-4) is True # ray with slope through midpoint ray_slope = ConstructionRay((10, 10), angle=HALF_PI / 2) cross_points = circle.intersect_ray(ray_slope) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert is_close_points(p1, (7.8787, 7.8787), abs_tol=1e-4) is True assert is_close_points(p2, (12.1213, 12.1213), abs_tol=1e-4) is True # horizontal ray through midpoint ray_hor = ConstructionRay((10, 10), angle=0) cross_points = circle.intersect_ray(ray_hor) assert len(cross_points) == 2 p1, p2 = cross_points if p1[0] > p2[0]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert is_close_points(p1, (7, 10), abs_tol=1e-5) is True assert is_close_points(p2, (13, 10), abs_tol=1e-5) is True # vertical ray through midpoint ray_vert = ConstructionRay((10, 10), angle=HALF_PI) cross_points = circle.intersect_ray(ray_vert) assert len(cross_points) == 2 p1, p2 = cross_points if p1[1] > p2[1]: p1, p2 = p2, p1 # print (p1[0], p1[1], p2[0], p2[1]) assert is_close_points(p1, (10, 7), abs_tol=1e-5) is True assert is_close_points(p2, (10, 13), abs_tol=1e-5) is True
def center_of_3points_arc(point1: "Vertex", point2: "Vertex", point3: "Vertex") -> Vec3: """ Calc center point of 3 point arc. ConstructionCircle is defined by 3 points on the circle: point1, point2 and point3. """ ray1 = ConstructionRay(point1, point2) ray2 = ConstructionRay(point1, point3) midpoint1 = lerp(point1, point2) midpoint2 = lerp(point1, point3) center_ray1 = ray1.orthogonal(midpoint1) center_ray2 = ray2.orthogonal(midpoint2) return center_ray1.intersect(center_ray2)
def _get_point_on_dimline(point: "Vertex", dimray: ConstructionRay) -> Vec3: """get the measure target point projection on the dimension line""" return dimray.intersect(dimray.orthogonal(point))
def __init__(self, dimension: 'Dimension', ucs: 'UCS' = None, override: 'DimStyleOverride' = None): super().__init__(dimension, ucs, override) if self.text_movement_rule == 0: # moves the dimension line with dimension text, this makes no sense for ezdxf (just set `base` argument) self.text_movement_rule = 2 self.oblique_angle = self.dimension.get_dxf_attrib( 'oblique_angle', 90) # type: float self.dim_line_angle = self.dimension.get_dxf_attrib('angle', 0) # type: float self.dim_line_angle_rad = math.radians( self.dim_line_angle) # type: float self.ext_line_angle = self.dim_line_angle + self.oblique_angle # type: float self.ext_line_angle_rad = math.radians( self.ext_line_angle) # type: float # text is aligned to dimension line self.text_rotation = self.dim_line_angle # type: float if self.text_halign in ( 3, 4 ): # text above extension line, is always aligned with extension lines self.text_rotation = self.ext_line_angle self.ext1_line_start = Vec2(self.dimension.dxf.defpoint2) self.ext2_line_start = Vec2(self.dimension.dxf.defpoint3) ext1_ray = ConstructionRay(self.ext1_line_start, angle=self.ext_line_angle_rad) ext2_ray = ConstructionRay(self.ext2_line_start, angle=self.ext_line_angle_rad) dim_line_ray = ConstructionRay(self.dimension.dxf.defpoint, angle=self.dim_line_angle_rad) self.dim_line_start = dim_line_ray.intersect(ext1_ray) # type: Vec2 self.dim_line_end = dim_line_ray.intersect(ext2_ray) # type: Vec2 self.dim_line_center = self.dim_line_start.lerp( self.dim_line_end) # type: Vec2 if self.dim_line_start == self.dim_line_end: self.dim_line_vec = Vec2.from_angle(self.dim_line_angle_rad) else: self.dim_line_vec = (self.dim_line_end - self.dim_line_start).normalize() # type: Vec2 # set dimension defpoint to expected location - 3D vertex required! self.dimension.dxf.defpoint = Vector(self.dim_line_start) self.measurement = (self.dim_line_end - self.dim_line_start).magnitude # type: float self.text = self.text_override( self.measurement * self.dim_measurement_factor) # type: str # only for linear dimension in multi point mode self.multi_point_mode = override.pop('multi_point_mode', False) # 1 .. move wide text up # 2 .. move wide text down # None .. ignore self.move_wide_text = override.pop('move_wide_text', None) # type: bool # actual text width in drawing units self.dim_text_width = 0 # type: float # arrows self.required_arrows_space = 2 * self.arrow_size + self.text_gap # type: float self.arrows_outside = self.required_arrows_space > self.measurement # type: bool # text location and rotation if self.text: # text width and required space self.dim_text_width = self.text_width(self.text) # type: float if self.dim_tolerance: self.dim_text_width += self.tol_text_width elif self.dim_limits: # limits show the upper and lower limit of the measurement as stacked values # and with the size of tolerances measurement = self.measurement * self.dim_measurement_factor self.measurement_upper_limit = measurement + self.tol_maximum self.measurement_lower_limit = measurement - self.tol_minimum self.tol_text_upper = self.format_tolerance_text( self.measurement_upper_limit) self.tol_text_lower = self.format_tolerance_text( self.measurement_lower_limit) self.tol_text_width = self.tolerance_text_width( max(len(self.tol_text_upper), len(self.tol_text_lower))) # only limits are displayed so: self.dim_text_width = self.tol_text_width if self.multi_point_mode: # ezdxf has total control about vertical text position in multi point mode self.text_vertical_position = 0. if self.text_valign == 0 and abs( self.text_vertical_position) < 0.7: # vertical centered text needs also space for arrows required_space = self.dim_text_width + 2 * self.arrow_size else: required_space = self.dim_text_width self.is_wide_text = required_space > self.measurement if not self.force_text_inside: # place text outside if wide text and not forced inside self.text_outside = self.is_wide_text elif self.is_wide_text and self.text_halign < 3: # center wide text horizontal self.text_halign = 0 # use relative text shift to move wide text up or down in multi point mode if self.multi_point_mode and self.is_wide_text and self.move_wide_text: shift_value = self.text_height + self.text_gap if self.move_wide_text == 1: # move text up self.text_shift_v = shift_value if self.vertical_placement == -1: # text below dimension line # shift again self.text_shift_v += shift_value elif self.move_wide_text == 2: # move text down self.text_shift_v = -shift_value if self.vertical_placement == 1: # text above dimension line # shift again self.text_shift_v -= shift_value # get final text location - no altering after this line self.text_location = self.get_text_location() # type: Vec2 # text rotation override rotation = self.text_rotation # type: float if self.user_text_rotation is not None: rotation = self.user_text_rotation elif self.text_outside and self.text_outside_horizontal: rotation = 0 elif self.text_inside and self.text_inside_horizontal: rotation = 0 self.text_rotation = rotation self.text_box = TextBox(center=self.text_location, width=self.dim_text_width, height=self.text_height, angle=self.text_rotation, gap=self.text_gap * .75) if self.text_has_leader: p1, p2, *_ = self.text_box.corners self.leader1, self.leader2 = order_leader_points( self.dim_line_center, p1, p2) # not exact what BricsCAD (AutoCAD) expect, but close enough self.dimension.dxf.text_midpoint = self.leader1 else: # write final text location into DIMENSION entity self.dimension.dxf.text_midpoint = self.text_location
def profile_construction_ray_init_once(count=COUNT): ray1 = ConstructionRay(p1=P1, p2=P2) ray2 = ConstructionRay(p1=P3, p2=P4) for _ in range(count): ray1.intersect(ray2)
def test_ray2d_parallel_vertical(self): ray1 = ConstructionRay((10, 1), (10, -7)) ray2 = ConstructionRay((11, 0), angle=HALF_PI) ray3 = ConstructionRay((12, -10), (12, 7)) ray4 = ConstructionRay((0, 0), (1, 1)) ray5 = ConstructionRay((0, 0), angle=0) with pytest.raises(ParallelRaysError): _ = ray1.intersect(ray3) assert ray1.is_parallel(ray3) is True assert ray1.is_parallel(ray2) is True assert ray2.is_parallel(ray2) is True assert ray1.is_parallel(ray4) is False assert ray2.is_parallel(ray4) is False assert ray3.is_parallel(ray4) is False assert ray1.is_parallel(ray5) is False assert ray2.is_parallel(ray5) is False assert ray3.is_parallel(ray5) is False # vertical rays can't calc a y-value with pytest.raises(ArithmeticError): _ = ray1.yof(-1.0)
def test_ray2d_normal_horizontal(self): ray = ConstructionRay((10, 10), (20, 10)) # horizontal line ortho = ray.orthogonal((3, 3)) point = ray.intersect(ortho) assert point.isclose(Vec3(3, 10))
def test_ray2d_normal_vertical(self): ray = ConstructionRay((10, 1), (10, -7)) # vertical line ortho = ray.orthogonal((3, 3)) point = ray.intersect(ortho) assert point.isclose(Vec3(10, 3))
def __init__( self, dimension: "Dimension", ucs: "UCS" = None, override: "DimStyleOverride" = None, ): super().__init__(dimension, ucs, override) measurement = self.measurement if measurement.text_movement_rule == 0: # moves the dimension line with dimension text, this makes no sense # for ezdxf (just set `base` argument) measurement.text_movement_rule = 2 self.oblique_angle: float = self.dimension.get_dxf_attrib( "oblique_angle", 90) self.dim_line_angle: float = self.dimension.get_dxf_attrib("angle", 0) self.dim_line_angle_rad: float = math.radians(self.dim_line_angle) self.ext_line_angle: float = self.dim_line_angle + self.oblique_angle self.ext_line_angle_rad: float = math.radians(self.ext_line_angle) # text is aligned to dimension line measurement.text_rotation = self.dim_line_angle # text above extension line, is always aligned with extension lines if measurement.text_halign in (3, 4): measurement.text_rotation = self.ext_line_angle self.ext1_line_start = Vec2(self.dimension.dxf.defpoint2) self.ext2_line_start = Vec2(self.dimension.dxf.defpoint3) ext1_ray = ConstructionRay(self.ext1_line_start, angle=self.ext_line_angle_rad) ext2_ray = ConstructionRay(self.ext2_line_start, angle=self.ext_line_angle_rad) dim_line_ray = ConstructionRay(self.dimension.dxf.defpoint, angle=self.dim_line_angle_rad) self.dim_line_start: Vec2 = dim_line_ray.intersect(ext1_ray) self.dim_line_end: Vec2 = dim_line_ray.intersect(ext2_ray) self.dim_line_center: Vec2 = self.dim_line_start.lerp( self.dim_line_end) if self.dim_line_start == self.dim_line_end: self.dim_line_vec = Vec2.from_angle(self.dim_line_angle_rad) else: self.dim_line_vec = (self.dim_line_end - self.dim_line_start).normalize() # set dimension defpoint to expected location - 3D vertex required! self.dimension.dxf.defpoint = Vec3(self.dim_line_start) raw_measurement = (self.dim_line_end - self.dim_line_start).magnitude measurement.update(raw_measurement) # only for linear dimension in multi point mode self.multi_point_mode = self.dim_style.pop("multi_point_mode", False) # 1 .. move wide text up # 2 .. move wide text down # None .. ignore self.move_wide_text: Optional[bool] = self.dim_style.pop( "move_wide_text", None) # actual text width in drawing units self._total_text_width: float = 0 # arrows self.required_arrows_space: float = (2 * self.arrows.arrow_size + measurement.text_gap) self.arrows_outside: bool = (self.required_arrows_space > raw_measurement) # text location and rotation if measurement.text: # text width and required space self._total_text_width = self.total_text_width() if self.tol.has_limits: # limits show the upper and lower limit of the measurement as # stacked values and with the size of tolerances self.tol.update_limits(self.measurement.value) if self.multi_point_mode: # ezdxf has total control about vertical text position in multi # point mode measurement.text_vertical_position = 0.0 if (measurement.text_valign == 0 and abs(measurement.text_vertical_position) < 0.7): # vertical centered text needs also space for arrows required_space = (self._total_text_width + 2 * self.arrows.arrow_size) else: required_space = self._total_text_width measurement.is_wide_text = required_space > raw_measurement if not measurement.force_text_inside: # place text outside if wide text and not forced inside measurement.text_is_outside = measurement.is_wide_text elif measurement.is_wide_text and measurement.text_halign < 3: # center wide text horizontal measurement.text_halign = 0 # use relative text shift to move wide text up or down in multi # point mode if (self.multi_point_mode and measurement.is_wide_text and self.move_wide_text): shift_value = measurement.text_height + measurement.text_gap if self.move_wide_text == 1: # move text up measurement.text_shift_v = shift_value if (measurement.vertical_placement == -1 ): # text below dimension line # shift again measurement.text_shift_v += shift_value elif self.move_wide_text == 2: # move text down measurement.text_shift_v = -shift_value if (measurement.vertical_placement == 1 ): # text above dimension line # shift again measurement.text_shift_v -= shift_value # get final text location - no altering after this line measurement.text_location = self.get_text_location() # text rotation override rotation: float = measurement.text_rotation if measurement.user_text_rotation is not None: rotation = measurement.user_text_rotation elif (measurement.text_is_outside and measurement.text_outside_horizontal): rotation = 0.0 elif (measurement.text_is_inside and measurement.text_inside_horizontal): rotation = 0.0 measurement.text_rotation = rotation text_box = self.init_text_box() self.geometry.set_text_box(text_box) if measurement.has_leader: p1, p2, *_ = text_box.corners self.leader1, self.leader2 = order_leader_points( self.dim_line_center, p1, p2) # not exact what BricsCAD (AutoCAD) expect, but close enough self.dimension.dxf.text_midpoint = self.leader1 else: # write final text location into DIMENSION entity self.dimension.dxf.text_midpoint = measurement.text_location
def test_ray2d_normal(self): ray = ConstructionRay((-10, 3), (17, -7)) ortho = ray.orthogonal((3, 3)) point = ray.intersect(ortho) assert point.isclose(Vec3(1.4318, -1.234), abs_tol=1e-4)