Skip to content

Commit

Permalink
add sequential search
Browse files Browse the repository at this point in the history
  • Loading branch information
mozman committed May 26, 2024
1 parent 5ce7f67 commit aa7c8a4
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 2 deletions.
45 changes: 43 additions & 2 deletions src/ezdxf/edgeminer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""
from __future__ import annotations
from typing import Any, Sequence, Iterator, Iterable, Dict, Tuple
from typing import Any, Sequence, Iterator, Iterable, Dict, Tuple, Callable
from typing_extensions import Self, TypeAlias
from collections import defaultdict
import time
Expand All @@ -27,6 +27,7 @@
"find_first_loop",
"find_shortest_loop",
"find_longest_loop",
"sequential_search",
"length",
"filter_short_edges",
"Edge",
Expand Down Expand Up @@ -111,13 +112,53 @@ def reversed(self) -> Self:
return edge


def isclose(a: Vec3, b: Vec3, gap_tol: float = GAP_TOL) -> bool:
def isclose(a: Vec3, b: Vec3, gap_tol=GAP_TOL) -> bool:
"""This function should be used to test whether two vertices are close to each other
to get consistent results.
"""
return a.distance(b) < gap_tol


def is_forward_connected(a: Edge, b: Edge, gap_tol=GAP_TOL) -> bool:
"""Returns ``True`` if the edges have a forward connection."""
return isclose(a.end, b.start, gap_tol)


def is_backwards_connected(a: Edge, b: Edge, gap_tol=GAP_TOL) -> bool:
"""Returns ``True`` if the edges have a backward connection."""
return isclose(a.start, b.end, gap_tol)


def is_loop(edges: Sequence[Edge], gap_tol=GAP_TOL, full=True) -> bool:
if full:
if not all(
is_forward_connected(a, b, gap_tol) for a, b in zip(edges, edges[1:])
):
return False
return is_forward_connected(edges[-1], edges[0])


def sequential_search(edges: Sequence[Edge], gap_tol=GAP_TOL) -> Sequence[Edge]:
"""Returns all consecutive connected edges starting from the first edge.
The search stops at the first edge without a connection.
"""
if len(edges) < 2:
return edges
chain = [edges[0]]
for edge in edges[1:]:
last = chain[-1]
if is_forward_connected(last, edge, gap_tol):
chain.append(edge)
continue
reversed_edge = edge.reversed()
if is_forward_connected(last, reversed_edge, gap_tol):
chain.append(reversed_edge)
continue
break
return chain


class Watchdog:
def __init__(self, timeout=TIMEOUT) -> None:
self.timeout: float = timeout
Expand Down
14 changes: 14 additions & 0 deletions src/ezdxf/edgesmith.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"""
from __future__ import annotations
from typing import Iterator, Iterable
import math

from ezdxf.edgeminer import Edge, GAP_TOL, ABS_TOL
Expand Down Expand Up @@ -134,3 +135,16 @@ def edge_from_entity(entity: et.DXFEntity, gap_tol=GAP_TOL) -> Edge | None:
if edge.length < gap_tol:
return None
return edge


def edges_from_entities(
entities: Iterable[et.DXFEntity], gap_tol=GAP_TOL
) -> Iterator[Edge]:
"""Yields all DXF entities as edges.
Skips all entities which can not be represented as edge.
"""
for entity in entities:
edge = edge_from_entity(entity, gap_tol)
if edge is not None:
yield edge
33 changes: 33 additions & 0 deletions tests/test_05_tools/test_546_edgeminer.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,40 @@ def collect_payload(edges: Sequence[em.Edge]) -> str:
return ",".join([e.payload for e in loop.ordered()])


class TestSequentialSearch:
# 0 1 2
# 1 +-E-+-D-+
# | |
# F C
# | |
# 0 +-A-+-B-+

A = em.Edge((0, 0), (1, 0), payload="A")
B = em.Edge((1, 0), (2, 0), payload="B")
C = em.Edge((2, 0), (2, 1), payload="C")
D = em.Edge((2, 1), (1, 1), payload="D")
E = em.Edge((1, 1), (0, 1), payload="E")
F = em.Edge((0, 1), (0, 0), payload="F")

def test_is_forward_connected(self):
assert em.is_forward_connected(self.A, self.B) is True
assert em.is_forward_connected(self.A, self.F) is False

def test_is_backwards_connected(self):
assert em.is_backwards_connected(self.A, self.F) is True
assert em.is_backwards_connected(self.A, self.B) is False
assert em.is_backwards_connected(self.D, self.F) is False

def test_sequential_forward_search(self):
edges = [self.A, self.B, self.C, self.D, self.E, self.F]
result = em.sequential_search(edges)
assert len(result) == 6
assert result[0] is self.A
assert result[-1] is self.F


class SimpleLoops:

# 0 1 2
# 1 +-C-+-G-+
# | | |
Expand Down

0 comments on commit aa7c8a4

Please sign in to comment.