diff --git a/docs/source/tutorials/entity_selection.rst b/docs/source/tutorials/entity_selection.rst index c21835bb7..3774c6790 100644 --- a/docs/source/tutorials/entity_selection.rst +++ b/docs/source/tutorials/entity_selection.rst @@ -7,6 +7,10 @@ This tutorial shows how to use the :mod:`ezdxf.select` module, which provides fu to select entities based on various shapes. These selection functions offer a way to filter entities based on their spatial location. +This is the base document for this tutorial: + +.. figure:: gfx/select-base.png + Why Bounding Boxes? ------------------- @@ -14,6 +18,11 @@ The :mod:`~ezdxf.select` module primarily relies on bounding boxes to perform se Bounding boxes offer a fast way to identify potential overlaps between entities and the selection shape. This approach prioritizes performance over absolute accuracy. +.. note:: + + The bounding boxes for text-based entities and entities containing curves are not + accurate! For more information read the docs for the :mod:`ezdxf.bbox` module. + Source of Entities ------------------ @@ -53,11 +62,47 @@ Bounding Box Inside Selection Selects entities which bounding boxes are completely within the selection shape. +Example to select entities inside a window: + +.. figure:: gfx/select-inside-window.png + + +.. code-block:: Python + + import ezdxf + from ezdxf import select + + doc = ezdxf.readfile("base.dxf") + msp 0 doc.modelspace() + + window = select.Window((150, 105), (280, 240)) + for entity in select.bbox_inside(window, msp): + print(str(entity)) + +output:: + + CIRCLE(#9D) + LWPOLYLINE(#9E) + Bounding Box Outside Selection ------------------------------ Selects entities whose bounding box is completely outside the selection shape. +.. figure:: gfx/select-outside-window.png + +.. code-block:: Python + + window = select.Window((185, 105), (245, 240)) + for entity in select.bbox_outside(window, msp): + print(str(entity)) + +output:: + + TEXT(#9F) + SPLINE(#A0) + LINE(#A1) + Bounding Box Overlap Selection ------------------------------ @@ -69,19 +114,134 @@ selection shape. This will also select elements where all of the entity geometr outside the selection shape, but the bounding box overlaps the selection shape, e.g. border polylines. +.. figure:: gfx/select-inside-window.png + +.. code-block:: Python + + window = select.Window((150, 105), (280, 240)) + for entity in select.bbox_overlap(window, msp): + print(str(entity)) + +output:: + + CIRCLE(#9D) + LWPOLYLINE(#9E) + TEXT(#9F) + SPLINE(#A0) + LINE(#A1) + LWPOLYLINE(#A2) + Bounding Box Chained Selection ------------------------------ Selects elements that are directly or indirectly connected to each other by overlapping bounding boxes. The selection begins at the specified starting element. +.. figure:: gfx/select-chained.png + +.. code-block:: Python + + # choose entity for the beginning of the chain: + line = msp.query("LINE").first + for entity in select.bbox_chained(line, msp): + print(str(entity)) + +output:: + + LINE(#A1) + CIRCLE(#9D) + LWPOLYLINE(#9E) + SPLINE(#A0) + Bounding Box Crosses Fence -------------------------- Selects entities whose bounding box intersects an open polyline. +.. figure:: gfx/select-fence.png + +.. code-block:: Python + + for entity in select.bbox_crosses_fence([(83, 101), (186, 193), (300, 107)], msp): + print(str(entity)) + +output:: + + CIRCLE(#9D) + LWPOLYLINE(#9E) + SPLINE(#A0) + LINE(#A1) + +.. note:: + + The polyline does not cross the entity geometry itself! + Point In Bounding Box Selection ------------------------------- Selects entities where the selection point lies within the bounding box. +.. figure:: gfx/select-point.png + +.. code-block:: Python + + for entity in select.bbox_point((264, 140), msp): + print(str(entity)) + +output:: + + LWPOLYLINE(#9E) + SPLINE(#A0) + +Circle Selection +---------------- + +For the circle shape, the selection tests are carried out on the real circlar area. + +This example selects all entities around the CIRCLE entity within a 60 unit radius +whose bounding box overlaps the circle selection: + +.. figure:: gfx/select-by-circle.png + +.. code-block:: Python + + entity = msp.query("CIRCLE").first + circle = select.Circle(entity.dxf.center, radius=60) + for entity in select.bbox_overlap(circle, msp): + print(str(entity)) + +output:: + + CIRCLE(#9D) + LWPOLYLINE(#9E) + TEXT(#9F) + SPLINE(#A0) + +Polygon Selection +----------------- + +As for the circle shape, the polygon selection tests are carried out on the real polygon +area. + +.. note:: + + This may not work 100% correctly if the selection polygon has a complex convex shape! + +This example selects all entities whose bounding box lies entirely within the selection +polygon: + +.. figure:: gfx/select-by-polygon.png + +.. code-block:: Python + + + polygon = select.Polygon([(110, 168), (110, 107), (316, 107), (316, 243), (236, 243)]) + for entity in select.bbox_inside(polygon, msp): + print(str(entity)) + +output:: + + LWPOLYLINE(#9E) + SPLINE(#A0) + LINE(#A1) + diff --git a/docs/source/tutorials/gfx/select-base.png b/docs/source/tutorials/gfx/select-base.png new file mode 100644 index 000000000..1d75773fc Binary files /dev/null and b/docs/source/tutorials/gfx/select-base.png differ diff --git a/docs/source/tutorials/gfx/select-by-circle.png b/docs/source/tutorials/gfx/select-by-circle.png new file mode 100644 index 000000000..86cc2e0e1 Binary files /dev/null and b/docs/source/tutorials/gfx/select-by-circle.png differ diff --git a/docs/source/tutorials/gfx/select-by-polygon.png b/docs/source/tutorials/gfx/select-by-polygon.png new file mode 100644 index 000000000..5e23d7394 Binary files /dev/null and b/docs/source/tutorials/gfx/select-by-polygon.png differ diff --git a/docs/source/tutorials/gfx/select-chained.png b/docs/source/tutorials/gfx/select-chained.png new file mode 100644 index 000000000..1e16832e0 Binary files /dev/null and b/docs/source/tutorials/gfx/select-chained.png differ diff --git a/docs/source/tutorials/gfx/select-fence.png b/docs/source/tutorials/gfx/select-fence.png new file mode 100644 index 000000000..41927aa78 Binary files /dev/null and b/docs/source/tutorials/gfx/select-fence.png differ diff --git a/docs/source/tutorials/gfx/select-inside-window.png b/docs/source/tutorials/gfx/select-inside-window.png new file mode 100644 index 000000000..e25dfb9ee Binary files /dev/null and b/docs/source/tutorials/gfx/select-inside-window.png differ diff --git a/docs/source/tutorials/gfx/select-outside-window.png b/docs/source/tutorials/gfx/select-outside-window.png new file mode 100644 index 000000000..758121a65 Binary files /dev/null and b/docs/source/tutorials/gfx/select-outside-window.png differ diff --git a/docs/source/tutorials/gfx/select-point.png b/docs/source/tutorials/gfx/select-point.png new file mode 100644 index 000000000..1c260da37 Binary files /dev/null and b/docs/source/tutorials/gfx/select-point.png differ diff --git a/docs/source/tutorials/src/select.py b/docs/source/tutorials/src/select.py new file mode 100644 index 000000000..fe57dc7e8 --- /dev/null +++ b/docs/source/tutorials/src/select.py @@ -0,0 +1,110 @@ +# Copyright (c) 2024, Manfred Moitzi +# License: MIT License +from pathlib import Path +import ezdxf +from ezdxf import bbox, select + +CWD = Path("~/Desktop/Now/ezdxf/select").expanduser() +if not CWD.exists(): + CWD = Path(".") + +BASE = "base.dxf" + + +def select_inside_window(): + print("\nselect inside window:") + doc = ezdxf.readfile(CWD / BASE) + + window = select.Window((150, 105), (280, 240)) + for entity in select.bbox_inside(window, doc.modelspace()): + print(str(entity)) + + +def select_outside_window(): + print("\nselect outside window:") + doc = ezdxf.readfile(CWD / BASE) + + window = select.Window((185, 105), (245, 240)) + for entity in select.bbox_outside(window, doc.modelspace()): + print(str(entity)) + + +def select_overlap_window(): + print("\nselect overlap window:") + doc = ezdxf.readfile(CWD / BASE) + + window = select.Window((150, 105), (280, 240)) + for entity in select.bbox_overlap(window, doc.modelspace()): + print(str(entity)) + + +def select_crosses_fence(): + print("\nselect crossing fence:") + doc = ezdxf.readfile(CWD / BASE) + msp = doc.modelspace() + + for entity in select.bbox_crosses_fence( + [(83, 101), (186, 193), (300, 107)], msp.query("*").layer == "Entities" + ): + print(str(entity)) + + +def select_chained(): + print("\nselect chained entities:") + doc = ezdxf.readfile(CWD / "chained.dxf") + msp = doc.modelspace() + line = msp.query("LINE").first + for entity in select.bbox_chained(line, msp.query("*").layer == "Entities"): + print(str(entity)) + + +def select_point(): + print("\nselect entities by point in bbox:") + doc = ezdxf.readfile(CWD / "point.dxf") + msp = doc.modelspace() + for entity in select.point_in_bbox((264, 140), msp.query("*").layer == "Entities"): + print(str(entity)) + + +def select_by_circle(): + print("\nselect by circle:") + doc = ezdxf.readfile(CWD / BASE) + msp = doc.modelspace() + entity = msp.query("CIRCLE").first + circle = select.Circle(entity.dxf.center, radius=60) + for entity in select.bbox_overlap(circle, msp.query("*").layer == "Entities"): + print(str(entity)) + + +def select_by_polygon(): + print("\nselect by polygon:") + doc = ezdxf.readfile(CWD / BASE) + msp = doc.modelspace() + + vertices = [(110, 168), (110, 107), (316, 107), (316, 243), (236, 243)] + polygon = select.Polygon(vertices) + for entity in select.bbox_inside(polygon, msp): + print(str(entity)) + + +def draw_bboxes(filename: str) -> None: + doc = ezdxf.readfile(CWD / filename) + msp = doc.modelspace() + for entity in msp.query("*").layer == "Entities": + box = bbox.extents((entity,)) + msp.add_lwpolyline( + box.rect_vertices(), close=True, dxfattribs={"layer": "BoundingBox"} + ) + doc.saveas(CWD / "bboxes.dxf") + + +if __name__ == "__main__": + draw_bboxes(BASE) + select_inside_window() + select_outside_window() + select_overlap_window() + select_crosses_fence() + select_chained() + select_point() + select_by_circle() + select_by_polygon()