def test_do_not_intersect_or_overlap(self): bbox1 = BoundingBox([(0, 0, 0), (3, 3, 3)]) bbox2 = BoundingBox([(4, 4, 4), (9, 9, 9)]) bbox3 = BoundingBox([(-2, -2, -2), (-1, -1, -1)]) for a, b in permutations([bbox1, bbox2, bbox3], 2): assert a.has_intersection(b) is False assert a.has_overlap(b) is False
def multi_recursive(entities: Iterable['DXFEntity'], cache: Cache = None) -> Iterable[BoundingBox]: """ Yields all bounding boxes for the given `entities` **or** all bounding boxes for their sub entities. If an entity (INSERT) has sub entities, only the bounding boxes of these sub entities will be yielded, **not** the bounding box of entity (INSERT) itself. """ flat_entities = disassemble.recursive_decompose(entities) primitives = disassemble.to_primitives(flat_entities) for primitive in primitives: if primitive.is_empty: continue entity = primitive.entity if cache is not None: box = cache.get(entity) if box is None: box = BoundingBox(primitive.vertices()) if box.has_data: cache.store(entity, box) else: box = BoundingBox(primitive.vertices()) if box.has_data: yield box
def test_smaller_inside_bigger_intersection(self): b1 = BoundingBox([(0, 0, 0), (3, 3, 3)]) b2 = BoundingBox([(1, 1, 1), (2, 2, 2)]) b = b1.intersection(b2) assert b.size.isclose((1, 1, 1)) assert b.extmin.isclose((1, 1, 1)) assert b.extmax.isclose((2, 2, 2))
def test_do_intersect_and_overlap(self): bbox1 = BoundingBox([(0, 0, 0), (10, 10, 10)]) bbox2 = BoundingBox([(1, 1, 1), (9, 9, 9)]) bbox3 = BoundingBox([(-1, -1, -1), (1.001, 1.001, 1.001)]) for a, b in permutations([bbox1, bbox2, bbox3], 2): assert a.has_intersection(b) is True assert a.has_overlap(b) is True
def intersection_line_line_3d( line1: Sequence[Vec3], line2: Sequence[Vec3], virtual: bool = True, abs_tol: float = 1e-10, ) -> Optional[Vec3]: """ Returns the intersection point of two 3D lines, returns ``None`` if lines do not intersect. Args: line1: first line as tuple of two points as :class:`Vec3` objects line2: second line as tuple of two points as :class:`Vec3` objects virtual: ``True`` returns any intersection point, ``False`` returns only real intersection points abs_tol: absolute tolerance for comparisons .. versionadded:: 0.17.2 """ from ezdxf.math import intersection_ray_ray_3d, BoundingBox res = intersection_ray_ray_3d(line1, line2, abs_tol) if len(res) != 1: return None point = res[0] if virtual: return point if BoundingBox(line1).inside(point) and BoundingBox(line2).inside(point): return point return None
def extents(entities: Iterable['DXFEntity'], cache: Cache = None) -> BoundingBox: """ Returns a single bounding box for all given `entities`. """ _extends = BoundingBox() for box in multi_flat(entities, cache): _extends.extend(box) return _extends
def bbox(self) -> BoundingBox: if self.acdb_ole2frame is not None: v10 = self.acdb_ole2frame.get_first_value(10, None) v11 = self.acdb_ole2frame.get_first_value(11, None) if v10 is not None and v11 is not None: return BoundingBox([Vec3(v10), Vec3(v11)]) return BoundingBox()
def test_any_inside(self): bbox1 = BoundingBox([(0, 0, 0), (7, 7, 7)]) bbox2 = BoundingBox([(1, 1, 1), (9, 9, 9)]) empty = BoundingBox() assert bbox1.any_inside(bbox2) is True assert bbox2.any_inside(bbox1) is True assert bbox1.any_inside(empty) is False assert empty.any_inside(bbox1) is False
def test_init_none(self): bbox = BoundingBox() assert bbox.is_empty is True assert bbox.has_data is False bbox.extend([(0, 0, 0), (10, 10, 10)]) assert bbox.size == (10, 10, 10) assert bbox.is_empty is False assert bbox.has_data is True
def test_do_not_intersect_or_overlap_empty(self): bbox = BoundingBox([(0, 0, 0), (3, 3, 3)]) empty = BoundingBox() assert bbox.has_intersection(empty) is False assert bbox.has_overlap(empty) is False assert empty.has_intersection(bbox) is False assert empty.has_overlap(bbox) is False assert empty.has_intersection(empty) is False assert empty.has_overlap(empty) is False
def test_nearly_vertical_parallel_lines_in_linear_trace_builder(): width = 30 trace = LinearTrace() trace.add_station((0, 100), width, width) trace.add_station((0, 100_000), width, width) trace.add_station((0.001, 0), width, width) bbox = BoundingBox() for face in trace.faces(): bbox.extend(face) assert bbox.extmin.y > -100
def box_split(points: List[AnyVec], max_size: int) -> Sequence[Node]: n = len(points) size = BoundingBox(points).size.xyz dim = size.index(max(size)) points.sort(key=lambda vec: vec[dim]) k = math.ceil(n / max_size) return tuple( make_node(points[i : i + k], max_size, box_split) for i in range(0, n, k) )
def extends(entities: Iterable['DXFEntity'], cache: Cache = None) -> BoundingBox: """ Returns a single bounding box for the given `entities` and their sub entities. Good caching behavior! """ _extends = BoundingBox() for box in multi_flat(entities, cache): _extends.extend(box) return _extends
def test_init(self): bbox = BoundingBox([(0, 0, 0), (10, 10, 10)]) assert bbox.extmin == (0, 0, 0) assert bbox.extmax == (10, 10, 10) bbox = BoundingBox([(10, 10, 10), (0, 0, 0)]) assert bbox.extmin == (0, 0, 0) assert bbox.extmax == (10, 10, 10) bbox = BoundingBox([(7, -2, 9), (-1, 8, -3)]) assert bbox.extmin == (-1, -2, -3) assert bbox.extmax == (7, 8, 9)
def test_cube_vertices_returns_vertices_in_counter_clockwise_order(self): bbox = BoundingBox([(0, 0, 0), (1, 2, 3)]) assert bbox.cube_vertices() == Vec3.tuple([ (0, 0, 0), # bottom layer (1, 0, 0), (1, 2, 0), (0, 2, 0), (0, 0, 3), # top layer (1, 0, 3), (1, 2, 3), (0, 2, 3), ])
def extents(entities: Iterable['DXFEntity'], *, flatten: float = MAX_FLATTENING_DISTANCE, cache: Cache = None) -> BoundingBox: """ Returns a single bounding box for all given `entities`. Calculate bounding boxes from flattened curves, if argument `flatten` is not 0 (max flattening distance), else from control points. """ _extends = BoundingBox() for box in multi_flat(entities, flatten=flatten, cache=cache): _extends.extend(box) return _extends
def extents(entities: Iterable['DXFEntity'], *, flatten: bool = True, cache: Cache = None) -> BoundingBox: """ Returns a single bounding box for all given `entities`. Calculate bounding boxes from flattened curves, if argument `flatten` is ``True``, else from control points. """ _extends = BoundingBox() for box in multi_flat(entities, flatten=flatten, cache=cache): _extends.extend(box) return _extends
def test_contains_other_bounding_box(self): box_a = BoundingBox([(0, 0, 0), (10, 10, 10)]) box_b = BoundingBox([(1, 1, 1), (9, 9, 9)]) box_c = BoundingBox([(1, 1, 1), (11, 11, 11)]) assert box_a.contains(box_b) is True assert box_a.contains(box_a) is True # self contained assert box_b.contains(box_a) is False assert box_a.contains(box_c) is False
def test_find_points_in_bbox(self, tree): bbox = BoundingBox([(45, 0, 0), (55, 0, 0)]) points = list(tree.points_in_bbox(bbox)) assert len(points) == 11 expected_x_coords = set(range(45, 56)) x_coords = set(int(p.x) for p in points) assert x_coords == expected_x_coords
def test_2d_polyline_including_width_to_primitive(): from ezdxf.layouts import VirtualLayout vl = VirtualLayout() p1 = (0, 0, 1, 1, 0) p2 = (2, 0, 0, 0, 0) lwp = vl.add_lwpolyline([p1, p2], dxfattribs={"elevation": 1}) p2d = vl.add_polyline2d([p1, p2], format="xyseb", dxfattribs={"elevation": (0, 0, 1)}) for e in [lwp, p2d]: p = disassemble.make_primitive(e) assert p.is_empty is False assert p.path is None assert ( p.mesh is not None), "2D polylines including width should create a mesh" vertices = list(p.vertices()) assert len(vertices) == 4 box = BoundingBox(vertices) assert box.extmin.isclose((0, -0.5, 1)), "vertices should be in WCS" assert box.extmax.isclose((2, 0.5, 1)), "vertices should be in WCS"
def transform(self, m: Matrix44): self.bounding_box = BoundingBox([ m.transform(self.bounding_box.extmin), m.transform(self.bounding_box.extmax), ]) for e in self.entities: e.transform(m)
def test_Vec2_compatibility(): tree = RTree([Vec2(x, 0) for x in range(100)], max_node_size=5) bbox = BoundingBox([(45, 0, 0), (55, 0, 0)]) points = list(tree.points_in_bbox(bbox)) assert len(points) == 11 expected_x_coords = set(range(45, 56)) x_coords = set(int(p.x) for p in points) assert x_coords == expected_x_coords assert any(isinstance(p, Vec2) for p in points)
def export_path(path): doc = ezdxf.new() msp = doc.modelspace() bbox = BoundingBox(path) msp.add_polyline3d(path, dxfattribs={'layer': 'Path', 'color': 2}) spline = msp.add_spline(dxfattribs={'layer': 'B-spline', 'color': 1}) curve = global_bspline_interpolation(path) spline.apply_construction_tool(curve) doc.set_modelspace_vport(center=bbox.center, height=bbox.size[1]) doc.saveas(DIR / 'path1.dxf')
def test_inside(self): bbox = BoundingBox([(0, 0, 0), (10, 10, 10)]) assert bbox.inside((0, 0, 0)) is True assert bbox.inside((-1, 0, 0)) is False assert bbox.inside((0, -1, 0)) is False assert bbox.inside((0, 0, -1)) is False assert bbox.inside((5, 5, 5)) is True assert bbox.inside((10, 10, 10)) is True assert bbox.inside((11, 10, 10)) is False assert bbox.inside((10, 11, 10)) is False assert bbox.inside((10, 10, 11)) is False
def test_extend(self): bbox = BoundingBox([(0, 0, 0), (10, 10, 10)]) bbox.extend([(5, 5, 5)]) assert bbox.extmin == (0, 0, 0) assert bbox.extmax == (10, 10, 10) bbox.extend([(15, 16, 17)]) assert bbox.extmin == (0, 0, 0) assert bbox.extmax == (15, 16, 17) bbox.extend([(-15, -16, -17)]) assert bbox.extmin == (-15, -16, -17) assert bbox.extmax == (15, 16, 17)
def multi_recursive( entities: Iterable["DXFEntity"], *, flatten: float = MAX_FLATTENING_DISTANCE, cache: Cache = None, ) -> Iterable[BoundingBox]: """Yields all bounding boxes for the given `entities` **or** all bounding boxes for their sub entities. If an entity (INSERT) has sub entities, only the bounding boxes of these sub entities will be yielded, **not** the bounding box of entity (INSERT) itself. Calculate bounding boxes from flattened curves, if argument `flatten` is not 0 (max flattening distance), else from control points. """ def vertices(p: disassemble.Primitive) -> Iterable[Vec3]: if flatten: primitive.max_flattening_distance = abs(flatten) return primitive.vertices() else: return disassemble.to_control_vertices([p]) flat_entities = disassemble.recursive_decompose(entities) primitives = disassemble.to_primitives(flat_entities) for primitive in primitives: if primitive.is_empty: continue entity = primitive.entity if cache is not None: box = cache.get(entity) if box is None: box = BoundingBox(vertices(primitive)) if box.has_data: cache.store(entity, box) else: box = BoundingBox(vertices(primitive)) if box.has_data: yield box
def build_bundles(paths: Iterable[Path]) -> Iterable[Bundle]: def append_holes(holes): for hole in holes: if isinstance(hole, Path): # just for edge cases, in general: # holes should be inside of the contour! box.extend(hole.control_vertices()) entities.append(hole.user_data) else: append_holes(hole) # the fast bbox detection algorithm is not very accurate! for polygon in nesting.fast_bbox_detection(paths): contour = polygon[0] box = BoundingBox(contour.control_vertices()) # optional: add some spacing between items if required: box.grow(0.5) entities = [contour.user_data] for hole in polygon[1:]: append_holes(hole) yield Bundle(entities, box)
def test_3d_box_contains_2d_box(self): box_a = BoundingBox2d([(1, 1), (9, 9)]) # lives in the xy-plane, z-axis is 0 box_b = BoundingBox([(0, 0, 0), (10, 10, 10)]) assert box_b.contains(box_a) is True, "xy-plane is included" box_c = BoundingBox([(0, 0, 1), (10, 10, 10)]) assert box_c.contains(box_a) is False, "xy-plane is not included"
def export_path(path): doc = ezdxf.new() msp = doc.modelspace() bbox = BoundingBox(path) msp.add_polyline3d(path, dxfattribs={"layer": "Path", "color": 2}) for curve in cubic_bezier_interpolation(path): msp.add_polyline3d(curve.approximate(20), dxfattribs={ "layer": "Bézier", "color": 1 }) doc.set_modelspace_vport(center=bbox.center, height=bbox.size[1]) doc.saveas(DIR / "path1.dxf")
def multi_recursive(entities: Iterable['DXFEntity'], *, flatten: bool = True, cache: Cache = None) -> Iterable[BoundingBox]: """ Yields all bounding boxes for the given `entities` **or** all bounding boxes for their sub entities. If an entity (INSERT) has sub entities, only the bounding boxes of these sub entities will be yielded, **not** the bounding box of entity (INSERT) itself. Calculate bounding boxes from flattened curves, if argument `flatten` is ``True``, else from control points. """ def vertices(p: disassemble.Primitive) -> Iterable[Vec3]: if not flatten: return disassemble.to_control_vertices([p]) return primitive.vertices() flat_entities = disassemble.recursive_decompose(entities) primitives = disassemble.to_primitives(flat_entities) for primitive in primitives: if primitive.is_empty: continue entity = primitive.entity if cache is not None: box = cache.get(entity) if box is None: box = BoundingBox(vertices(primitive)) if box.has_data: cache.store(entity, box) else: box = BoundingBox(vertices(primitive)) if box.has_data: yield box