
    h5                       % S r SSKJr  SSKJrJrJrJrJrJ	r	J
r
Jr  SSKJrJr  SSKJr  SSKrSSKrSSKrSSKJrJrJrJr  SSKJr  / S	QrS
rS
rSr " S S\5      r " S S5      r  " S S\
5      r!SNSOS jjr"\"" 5       r# SPSS.         SQS jjjr$\S.SRS jjr% " S S5      r&\S.SSS jjr'\S.STS jjr(\S.STS jjr)\S.STS jjr*SUS jr+SVS jr,SVS jr-SWS  jr.\S.SXS! jjr/\S.   SYS" jjr0\S#.SZS$ jjr1\S#.S[S% jjr2\S#.     S\S& jjr3\4   S]S' jjr4S^S( jr5SXS) jr6 " S* S+\5      r7S_S, jr8 " S- S.5      r9\\	\:S/4   \\!   4   r;S0\<S1'    " S2 S35      r=S4S5.S`S6 jjr>S]S7 jr?SaS8 jr@SbS9 jrAScS: jrB\S.SdS; jjrCSeS< jrD " S= S>5      rESfS? jrFSeS@ jrGSgSA jrHShSB jrISiS`SC jjrJ\4     S\SD jjrK " SE SF5      rLSjSG jrM\4SH jrN\N" 5       4     SkSI jjrOSlSJ jrP\S.     SmSK jjrQSnSaSL jjrRSoSM jrSg)pa  
EdgeMiner
=========

A module for detecting linked edges.

The complementary module ezdxf.edgesmith can create entities from the output of this 
module.

Terminology
-----------

I try to use the terminology of `Graph Theory`_ but there are differences where I think
a different term is better suited for this module like loop for cycle.

Edge (in this module)
    Edge is an immutable class:
        - unique id
        - 3D start point (vertex)
        - 3D end point (vertex)
        - optional length
        - optional payload (arbitrary data)

    The geometry of an edge is not known.
    Intersection points of edges are not known and cannot be calculated.

Vertex
    A connection point of two or more edges. 
    The degree of a vertex is the number of connected edges.

Leaf
    A leaf is a vertex of degree 1.
    A leaf is a loose end of an edge, which is not connected to other edges.

Junction
    A junction is a vertex of degree greater 2.
    A junction has more than two adjacent edges.
    A junction is an ambiguity when searching for open chains or closed loops.
    Graph Theory: multiple adjacency

Chain
    A chain has sequential connected edges. 
    The end point of an edge is connected to the start point of the following edge. 
    A chain has unique edges, each edge appears only once in the chain.
    A chain can contain vertices of degree greater 2.
    A solitary edge is also a chain.
    Chains are represented as Sequence[Edge].
    Graph Theory: Trail - no edge is repeated, vertex is repeated

Simple Chain (special to this module)
    A simple chain contains only vertices of degree 2, except the start- and end vertex.
    The start- and end vertices are leafs (degree of 1) or junctions (degree greater 2).
    
Open Chain
    An open chain is a chain which starts and ends with at leaf.
    A solitary edge is also an open chain.
    Graph Theory: Path - no edge is repeated, no vertex is repeated, endings not connected

Loop
    A loop is a simple chain with connected start- and end vertices.
    A loop has two or more edges.
    A loop contains only vertices of degree 2.
    Graph Theory: Cycle - no edge is repeated, no vertex is repeated, endings connected; 
    a loop in Graph Theory is something different

Network
    A network has two or more edges that are directly and indirectly connected. 
    The edges in a network have no order.
    A network can contain vertices of degree greater 2 (junctions).
    A solitary edge is not a network. 
    A chain with two or more edges is a network. 
    Networks are represented as Sequence[Edge].
    Graph Theory: multigraph; a network in Graph Theory is something different

Gap Tolerance
    Maximum vertex distance to consider two edges as connected

Forward Connection
    An edge is forward connected when the end point of the edge is connected to the 
    start point of the following edge.

.. important::
    
    THIS MODULE IS WORK IN PROGRESS (ALPHA VERSION), EVERYTHING CAN CHANGE UNTIL 
    THE RELEASE IN EZDXF V1.4.

.. _Graph Theory: https://en.wikipedia.org/wiki/Glossary_of_graph_theory
.. _GeeksForGeeks: https://www.geeksforgeeks.org/graph-data-structure-and-algorithms/?ref=shm

    )annotations)AnySequenceIteratorIterableDictTuple
NamedTupleCallable)Self	TypeAlias)CounterN)UVecVec2Vec3distance_point_line_3d)rtree)DepositEdgeTimeoutErrorfind_all_loopsfind_all_open_chainsfind_all_sequential_chainsfind_all_simple_chains	find_loopfind_loop_by_edgefind_sequential_chainflattenis_chainis_looplengthlongest_chainreverse_chainshortest_chainsubtract_edgesunique_chainsg&.>g      N@c                  @   ^  \ rS rSrSr\" 5       4SU 4S jjjrSrU =r$ )r      zB
Attributes:
    solutions: solutions found until time out occur

c                0   > [         TU ]  U5        X l        g N)super__init__	solutions)selfmsgr-   	__class__s      A/var/www/html/env/lib/python3.13/site-packages/ezdxf/edgeminer.pyr,   TimeoutError.__init__   s    "    r-   )r/   strr-   Sequence[Sequence[Edge]]returnNone)	__name__
__module____qualname____firstlineno____doc__tupler,   __static_attributes____classcell__)r0   s   @r1   r   r      s     HMw # #r3   r   c                  B    \ rS rSr\4SS jjrSS jr\S	S j5       rSr	g)
Watchdog   c                D    Xl         [        R                  " 5       U l        g r*   timeouttimeperf_counter
start_timer.   rF   s     r1   r,   Watchdog.__init__   s    %!%!2!2!4r3   c                D    Xl         [        R                  " 5       U l        g r*   rE   rJ   s     r1   startWatchdog.start   s    ++-r3   c                `    [         R                  " 5       U R                  -
  U R                  :  $ r*   )rG   rH   rI   rF   r.   s    r1   has_timed_outWatchdog.has_timed_out   s#      "T__4t||CCr3   )rI   rF   N)r7   r8   )rF   floatr7   bool)
r9   r:   r;   r<   TIMEOUTr,   rM   propertyrQ   r?    r3   r1   rB   rB      s%    & 5. D Dr3   rB   c                      \ rS rSr% SrS\S'   S\S'   S\S'   SrS	\S
'   SrS\S'   SrS\S'   SS jr	SS jr
SS jrSS jrSrg)r      a   Represents an immutable edge.

An edge can represent any linear curve (line, elliptical arc, spline, ...), but it
does not know this shape; only the start and end vertices are stored, not the shape
itself.  Therefore, the length of the edge must be specified if the length
calculation for a sequence of edges should be possible.  Intersection points between
edges are not known and cannot be calculated.

.. Important::

    Use only the :func:`make_edge` function to create new edges to get unique ids!

Attributes:
    id: unique id
    start (Vec3): start vertex
    end (Vec3): end vertex
    is_reverse: flag to indicate that the edge is reversed compared to its initial state
    length: length of the edge, default is 1.0
    payload: arbitrary data attached to the edge, default is ``None``

intidr   rM   endFrU   
is_reverseg      ?rS   r!   Nr   payloadc                `    [        U[        5      (       a  U R                  UR                  :H  $ g)zpReturn ``True`` if the ids of the edges are equal.

.. important::

    An edge is equal to its reversed copy!

F)
isinstancer   r\   )r.   others     r1   __eq__Edge.__eq__   s'     eT""77ehh&&r3   c                    U R                   nUc  [        U R                  5      nOI[        U[        5      (       a)  SSR                  S UR                   5       5      -   S-   nO[        U5      nSU S3$ )N[,c              3  8   #    U  H  n[        U5      v   M     g 7fr*   )repr.0es     r1   	<genexpr> Edge.__repr__.<locals>.<genexpr>   s     %E}!d1gg}   ]zEdge())r_   r5   r\   ra   EdgeWrapperjoinedges)r.   r_   contents      r1   __repr__Edge.__repr__   sd    ,,?$''lG--SXX%Ew}}%EEFLG'lGwiq!!r3   c                    U R                   $ )zThe edge :attr:`id` is used as hash value.

An Edge and its reversed edge have the same hash value and cannot both
exist in the same :class:`set`.
r\   rP   s    r1   __hash__Edge.__hash__   s     wwr3   c                    U R                  U R                  U R                  U R                  U R                  (       + U R
                  U R                  5      $ )z~Returns a reversed copy.

The reversed edge has the same :attr:`id` as the source edge, because they
represent the same edge.
)r0   r\   r]   rM   r^   r!   r_   rP   s    r1   reversedEdge.reversed   sB     ~~GGHHJJKKLL
 	
r3   rX   rT   )r7   r5   r7   r[   )r7   r   )r9   r:   r;   r<   r=   __annotations__r^   r!   r_   rc   rv   rz   r}   r?   rX   r3   r1   r   r      sJ    , 	GK	IJFEGS
"
r3   r   c                   ^ U mSU4S jjnU$ )Nc                    > T S-  m T $ )N   rX   )next_edge_ids   r1   next_id"make_id_generator.<locals>.next_id   s    r3   r   rX   )rM   r   r   s     @r1   make_id_generatorr      s    L
 Nr3   r_   c                   [        U 5      n [        U5      nUS:  a  U R                  U5      n[        [        5       XSX#5      $ )zCreates a new :class:`Edge` with a unique id.

Args:
    start: start point
    end: end point
    length: default is the distance between start and end
    payload: arbitrary data attached to the edge

g        F)r   distancer   id_generator)rM   r]   r!   r_   s       r1   	make_edger      s>     KE
s)C|$E6CCr3   gap_tolc               *    U R                  U5      U:*  $ )zmThis function should be used to test whether two vertices are close to each other
to get consistent results.
)r   abr   s      r1   iscloser   
  s     ::a=G##r3   c                      \ rS rSrSr\S.SS jjr\SS j5       rSS jr	\SS j5       r
SS jrSS	 jrSS
 jrSSS jjrSS jrSS jrSS jrSS jrSrg)r   i  a  The edge deposit stores all available edges for further searches.

The edges and the search index are immutable after instantiation.
The :attr:`gap_tol` attribute is mutable.

Args:
    edges: sequence of :class:`Edge`
    gap_tol: maximum vertex distance to consider two edges as connected

Attributes:
    gap_tol: maximum vertex distance to consider two edges as connected (mutable)

r   c               d    X l         [        U5      U l        [        U R                  5      U l        g r*   )r   
type_check_edges_SpatialSearchIndex_search_index)r.   rt   r   s      r1   r,   Deposit.__init__   s$    %&0&70=r3   c                    U R                   $ )z)Sequence of edges stored in this deposit.)r   rP   s    r1   rt   Deposit.edges%  s     {{r3   c           	        [        5       n[        R                  " U R                  R                  U R
                  S9nU R                   HO  nU[        U" UR                  5      5      ==   S-  ss'   U[        U" UR                  5      5      ==   S-  ss'   MQ     [        UR                  5        VVs0 s H
  u  pEXEU-  _M     snn5      $ s  snnf )ad  Returns a :class:`Counter` for the degree of all vertices.

- :code:`Counter[degree]` returns the count of vertices of this degree.
- :code:`Counter.keys()` returns all existing degrees in this deposit

A new counter will be created for every method call! The :attr:`gap_tol`
attribute is mutable and different gap tolerances may yield different results.

radiusr   )r   	functoolspartialr   vertices_in_spherer   rt   lenrM   r]   items)r.   countersearchedgekvs         r1   degree_counterDeposit.degree_counter*  s     !(	""11$,,
 JJDCtzz*+,1,Ctxx()*a/*  gmmo>oda6	o>??>s   5C
c                P    [        U R                  5       R                  5       5      $ )z;Returns the maximum degree of all vertices in this deposit.)maxr   keysrP   s    r1   
max_degreeDeposit.max_degree?  s!     4&&(--/00r3   c                r    [        U R                  R                  [        U5      U R                  5      5      $ )a,  Returns the degree of the given vertex.

- degree of 0: not in this deposit
- degree of 1: one edge is connected to this vertex
- degree of 2: two edges are connected to this vertex
- degree of 3: three edges ... and so on


Check if a vertex exist in a deposit::

    if deposit.degree(vertex): ...
)r   r   r   r   r   )r.   vertexs     r1   degreeDeposit.degreeD  s*     4%%88ft||TUUr3   c                   ^ [         R                  " U R                  R                  U R                  S9m[        U4S j[        R                  " U5       5       5      $ )z)Returns the degree of the given vertices.r   c              3  F   >#    U  H  n[        T" U5      5      v   M     g 7fr*   r   )rk   r   r   s     r1   rm   "Deposit.degrees.<locals>.<genexpr>X  s     O7NVS((7Ns   !)r   r   r   r   r   r>   r   generate)r.   verticesr   s     @r1   degreesDeposit.degreesS  sD    ""11$,,
 Ot}}X7NOOOr3   c                R    [        U R                  R                  U R                  S9$ )aB  Returns all unique vertices from this deposit.

Ignores vertices that are close to another vertex (within the range of gap_tol).
It is not determined which of the close vertices is returned.

e.g. if the vertices a, b are close together, you don't know if you get a or b,
but it's guaranteed that you only get one of them
r   )filter_close_verticesr   r   r   rP   s    r1   unique_verticesDeposit.unique_verticesZ  s!     %T%7%7%=%=t||TTr3   c                    US:  a  U R                   nU R                  R                  [        U5      U5      n[	        S U 5       5      $ )zReturns all edges linked to `vertex` in range of `radius`.

Args:
    vertex: 3D search location
    radius: search range, default radius is :attr:`Deposit.gap_tol`

r   c              3  8   #    U  H  oR                   v   M     g 7fr*   r   )rk   r   s     r1   rm   *Deposit.edges_linked_to.<locals>.<genexpr>p  s     .XVVXro   )r   r   r   r   r>   )r.   r   r   r   s       r1   edges_linked_toDeposit.edges_linked_toe  sA     A:\\F%%88fvN.X...r3   c                   ^ SU4S jjn[        T5      mU R                  nUR                  T5      nU R                  U5      nU(       a	  [	        XRS9$ g)zReturn the nearest edge to the given vertex.

The distance is measured to the connection line from start to end of the edge.
This is not correct for edges that represent arcs or splines.
c                   >  [        TU R                  U R                  5      $ ! [         a    U R                  R	                  T5      s $ f = fr*   )r   rM   r]   ZeroDivisionErrorr   )r   r   s    r1   r   +Deposit.find_nearest_edge.<locals>.distancey  sC    3-fdjj$((KK$ 3zz**6223s    $ %AAkeyNr   r   r7   rS   )r   r   nearest_vertexr   min)r.   r   r   sir   rt   s    `    r1   find_nearest_edgeDeposit.find_nearest_edger  sO    	3 f**62$$^4u++r3   c                   ^ ^^ SUU U4S jjnU/m[        T5      mT(       a=  TR                  5       nU" UR                  5        U" UR                  5        T(       a  M=  [	        T5      S:  a  T$ [        5       $ )zReturns the network of all edges that are directly and indirectly linked to
`edge`.  A network has two or more edges, a solitary edge is not a network.

c                   > [        TR                  U 5      5      T-
  nU(       a#  TR                  U5        TR                  U5        g g r*   )setr   updateextend)r   linked_edgesnetworkr.   todos     r1   process%Deposit.find_network.<locals>.process  s=    t33F;<wFL|,L) r3   r   )r   r   r7   r8   )r   poprM   r]   r   )r.   r   r   r   r   s   `  @@r1   find_networkDeposit.find_network  sf    	* 	* !6 Y88:DDJJDHH d w<!Nur3   c                $   [        U R                  5      n/ nU(       aa  UR                  5       nU R                  U5      n[	        U5      (       a  UR                  U5        X-  nOUR                  U5        U(       a  Ma  UR                  S S9  U$ )zRReturns all separated networks in this deposit in ascending order of edge
count.

c                    [        U 5      $ r*   r   )ns    r1   <lambda>+Deposit.find_all_networks.<locals>.<lambda>  s    CFr3   r   )r   rt   r   r   r   appenddiscardsort)r.   rt   networksr   r   s        r1   find_all_networksDeposit.find_all_networks  sz    
 DJJ$&99;D''-G7||( d# e 	*+r3   c              #     #    U R                    H_  n[        U R                  UR                  5      5      S:X  a  Uv   M1  [        U R                  UR                  5      5      S:X  d  M[  Uv   Ma     g7f)zUYields all edges that have at least one end point without connection to other
edges.
r   N)rt   r   r   rM   r]   )r.   r   s     r1   
find_leafsDeposit.find_leafs  sW      JJD4''

349
T))$((349
	 s   A$A3*	A3)r   r   r   Nrt   Sequence[Edge]r7   r8   )r7   r   )r7   zCounter[int]r   )r   r   r7   r[   )r   zIterable[UVec]r7   zSequence[int])r7   	set[Vec3]))r   r   r   rS   r7   r   )r   r   r7   Edge | None)r   r   r7   z	set[Edge])r7   zSequence[set[Edge]])r7   Iterator[Edge])r9   r:   r;   r<   r=   GAP_TOLr,   rW   rt   r   r   r   r   r   r   r   r   r   r   r?   rX   r3   r1   r   r     sk     :A >
  @* 1 1VP	U/*,&r3   r   c               @    [        U R                  UR                  US9$ )zReturns ``True`` if the edges have a forward connection.

Forward connection: distance from :attr:`a.end` to :attr:`b.start` <= gap_tol

Args:
    a: first edge
    b: second edge
    gap_tol: maximum vertex distance to consider two edges as connected
r   r   r]   rM   r   s      r1   is_forward_connectedr     s     155!''733r3   c          	     F   ^ [        U4S j[        X SS 5       5       5      $ )zReturns ``True`` if all edges in the sequence have a forward connection.

Args:
    edges: sequence of edges
    gap_tol: maximum vertex distance to consider two edges as connected
c              3  <   >#    U  H  u  p[        XTS 9v   M     g7fr   N)r   )rk   r   r   r   s      r1   rm   is_chain.<locals>.<genexpr>  s      @UQ73@Us   r   N)allziprt   r   s    `r1   r   r     s,      @CEQRQS9@U  r3   c               j    [        XS9(       d  g[        U S   R                  U S   R                  US9$ )zReturn ``True`` if the sequence of edges is a closed loop.

Args:
    edges: sequence of edges
    gap_tol: maximum vertex distance to consider two edges as connected
r   Fr   r   )r   r   r]   rM   r   s     r1   r    r      s.     E+59==%(..'BBr3   c               L    [        U S   R                  U S   R                  US9$ )zInternal fast loop check.r   r   r   r   r   s     r1   is_loop_fastr    s!    59==%(..'BBr3   c                &    [        S U  5       5      $ )z*Returns the length of a sequence of edges.c              3  8   #    U  H  oR                   v   M     g 7fr*   )r!   rj   s     r1   rm   length.<locals>.<genexpr>  s     'Axxro   )sumrt   s    r1   r!   r!     s    ''''r3   c                J    [        U [        S9nU(       a  US   $ [        5       $ )zReturns the shortest chain of connected edges.

.. note::

    This function does not verify if the input sequences are connected edges!

r   r   sortedr!   r>   chainssorted_chainss     r1   r$   r$     s%     6v.MQ7Nr3   c                J    [        U [        S9nU(       a  US   $ [        5       $ )zReturns the longest chain of connected edges.

.. Note::

    This function does not verify if the input sequences are connected edges!

r   r   r  r
  s     r1   r"   r"     s%     6v.MR  7Nr3   c                    [        U 5      nUR                  5         U Vs/ s H  o"R                  5       PM     sn$ s  snf )zReturns the reversed chain.

The sequence order of the edges will be reversed as well as the start- and end
points of the edges.
)listreverser}   )chainrt   r   s      r1   r#   r#     s2     KE	MMO(-.MMO...s   <c                  [        U 5      n [        U 5      S:  a  U $ U S   /nU SS  H]  nUS   n[        XCUS9(       a  UR                  U5        M*  UR	                  5       n[        XEUS9(       a  UR                  U5        M\    U$    U$ )ao  Returns a simple chain beginning at the first edge.

The search stops at the first edge without a forward connection from the previous
edge.  Edges will be reversed if required to create connection.

Args:
    edges: edges to be examined
    gap_tol: maximum vertex distance to consider two edges as connected

Raises:
    TypeError: invalid data in sequence `edges`
   r   r   Nr   r   )r   r   r   r   r}   )rt   r   r  r   lastreversed_edges         r1   r   r     s     uE
5zA~1XJEab	RyG<LLWELL'L  Lr3   c             #  d   #    U (       a%  [        XS9nU [        U5      S n Uv   U (       a  M$  gg7f)a  Yields all simple chains from sequence `edges`.

The search progresses strictly in order of the input sequence. The search starts a
new chain at every edge without a forward connection from the previous edge.
Edges will be reversed if required to create connection.
Each chain has one or more edges.

Args:
    edges: sequence of edges
    gap_tol: maximum vertex distance to consider two edges as connected

Raises:
    TypeError: invalid data in sequence `edges`
r   N)r   r   )rt   r   r  s      r1   r   r   /  s0     " %e=c%jl# %s   *00rF   c          	        [        U 5      nU(       d
  [        5       $ U R                  n/ nU HT  n[        U5      S:  a.  [	        XSS9(       a  Us  $ UR                  [        U5      5        M@  UR                  US   5        MV     [        XCS9n [        U R                  5      S:  a
  [        5       $ [        [        [        XS95      5      $ )an  Returns the first closed loop in `deposit`.

Returns only simple loops, where all vertices have a degree of 2 (only two adjacent
edges).

.. note::

    This is a recursive backtracking algorithm with time complexity of O(n!).

Args:
    deposit (Deposit): edge deposit
    timeout (float): timeout in seconds

Raises:
    TimeoutError: search process has timed out
r   r   r   r  r  )r   r>   r   r   r  r   _wrap_simple_chainr   rt   r   _find_loop_in_deposit)depositrF   r  r   packed_edgesr  s         r1   r   r   F  s    " $G,FwooG!Lu:>E3 25 9:a)  l4G
7==Aw.wHIJJr3   c                   [        U R                  5      S:  a
  [        5       $ [        XS9nUR	                  5       nU(       a  U$ [        5       $ )Nr  r  )r   rt   r>   
LoopFinderfind_any_loop)r  rF   finderloops       r1   r  r  j  sA    
7==Aw1F!D7Nr3   c               l   [        U 5      nU(       d
  [        5       $ U R                  n/ n/ nU Hc  n[        U5      S:  a=  [	        XcS9(       a  UR                  U5        M3  UR                  [        U5      5        MO  UR                  US   5        Me     U(       d  U$ [        XSS9n [        U R                  5      S:  a
  [        5       $  [        XS9nUR                  U5        [        U5      $ ! [         a8  nUR                  (       a!  UR                  UR                  5        XHl        e SnAff = f)a  Returns all closed loops from `deposit`.

Returns only simple loops, where all vertices have a degree of 2 (only two adjacent
edges).  The result does not include reversed solutions.

.. note::

    This is a recursive backtracking algorithm with time complexity of O(n!).

Args:
    deposit (Deposit): edge deposit
    timeout (float): timeout in seconds

Raises:
    TimeoutError: search process has timed out
r   r   r   r  r  N)r   r>   r   r   r  r   r  r   rt   _find_all_loops_in_depositr   r-   r   _unwrap_simple_chains)	r  rF   r  r   r-   r  r  resulterrs	            r1   r   r   u  s   & $G,FwooG&(I!Lu:>E3  '##$6u$=>a)  l4G
7==Aw+GE V ++  ==S]]+%M	s   	C1 1
D3;3D..D3c                    / n[        XS9nU R                   H  nUR                  U5        M     UR                  U5        U$ )Nr  )r  rt   r   r   )r  rF   r-   r   r   s        r1   r#  r#    sA     ')I1Fd Vr3   c              #     #    [        5       nU  H1  n[        S U 5       5      nX1;  d  M  Uv   UR                  U5        M3     g7f)zFilter duplicate chains and yields only unique chains.

Yields the first chain for chains which have the same set of edges. The order of the
edges is not important.
c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   rk   r   s     r1   rm    unique_chains.<locals>.<genexpr>  s     2EDEro   N)r   	frozensetadd)r  seenr  r   s       r1   r&   r&     s=      !$D2E22?KHHSM	 s
   %AAc           	         U  H8  n[        U[        5      (       a  M  [        S[        [	        U5      5       35      e   U $ )Nzexpected type <Edge>, got )ra   r   	TypeErrorr5   typert   r   s     r1   r   r     s<    $%%8T$Z8IJKK  Lr3   c                  $    \ rS rSr% SrS\S'   Srg)_Vertexi  r   r   r   rX   N)r9   r:   r;   r<   	__slots__r   r?   rX   r3   r1   r4  r4    s    I Jr3   r4  c                (    [        U 5      nXl        U$ r*   )r4  r   )locationr   r   s      r1   make_edge_vertexr8    s    XFKMr3   c                  J    \ rS rSrSrS	S jr\S
S j5       rSS jrSS jr	Sr
g)r   i  z=Spatial search index of all edge vertices.

(internal class)
c                    / nU HM  nUR                  [        UR                  U5      5        UR                  [        UR                  U5      5        MO     [        R
                  " U5      U l        g r*   )r   r8  rM   r]   r   RTree_search_tree)r.   rt   r   r   s       r1   r,   _SpatialSearchIndex.__init__  sT    "$DOO,TZZ>?OO,TXXt<=  "KK1r3   c                    U R                   $ r*   r<  rP   s    r1   r   _SpatialSearchIndex.rtree  s       r3   c                J    [        U R                  R                  X5      5      $ )zNReturns all vertices located around `center` with a max. distance of `radius`.)r>   r<  points_in_sphere)r.   centerr   s      r1   r   &_SpatialSearchIndex.vertices_in_sphere  s    T&&77GHHr3   c                @    U R                   R                  U5      u  p#U$ )z1Returns the nearest vertex to the given location.)r<  nearest_neighbor)r.   r7  r   _s       r1   r   "_SpatialSearchIndex.nearest_vertex  s    %%66x@	r3   r?  Nr   )r7   rtree.RTree[Vec3])rC  r   r   rS   r7   zSequence[_Vertex])r7  r   r7   r4  )r9   r:   r;   r<   r=   r,   rW   r   r   r   r?   rX   r3   r1   r   r     s+    
2 ! !Ir3   r   .r   SearchSolutionsc                  x    \ rS rSrSr\S.SS jjr\SS j5       rSS jr	SS jr
SSS	 jjrSSS
 jjrSS jrSrg)r  i  zFind closed loops in a :class:`Deposit` by a recursive backtracking algorithm.

Finds only simple loops, where all vertices have only two adjacent edges.

(internal class)
r  c               r    [        UR                  5      S:  a  [        S5      eXl        X l        0 U l        g )Nr  ztwo or more edges required)r   rt   
ValueError_deposit_timeout
_solutionsr.   r  rF   s      r1   r,   LoopFinder.__init__  s0    w}}!9::+-r3   c                .    U R                   R                  $ r*   )rN  r   rP   s    r1   r   LoopFinder.gap_tol  s    }}$$$r3   c                H    [        U R                  R                  5       5      $ r*   )iterrP  valuesrP   s    r1   __iter__LoopFinder.__iter__  s    DOO**,--r3   c                ,    [        U R                  5      $ r*   )r   rP  rP   s    r1   __len__LoopFinder.__len__  s    4??##r3   Nc                    Uc  U R                   R                  S   nU R                  USS9   [        [	        U R
                  R                  5       5      5      $ ! [         a    [        5       s $ f = f)zjReturns the first loop found beginning with the given start edge or an
arbitrary edge if `start` is None.
r   T)stop_at_first_loop)	rN  rt   r   nextrV  rP  rW  StopIterationr>   )r.   rM   s     r1   r  LoopFinder.find_any_loop  se     =MM''*EEd3	T__335677 	7N	s   +A A10A1c                  ^^ U R                   nU R                  mUR                  n[        U R                  5      nU4/nU(       Ga0  UR
                  (       a+  [        S[        U R                  R                  5       5      S9eUR                  5       nUS   nUR                  n	UR                  U	TS9n
[        U
5      [        U5      -
  nU H  n[        XR                  TS9(       a  UnOUR                  5       nUR                  m[        TUTS9(       a  U R!                  X}4-   5        U(       a    gMj  [#        UU4S jU 5       5      (       a  M  UR%                  X}4-   5        M     U(       a  GM/  gg)a  Searches for all loops that begin at the given start edge and contain
only vertices of degree 2.

These are not all possible loops in the edge deposit!

Raises:
    TimeoutError: search process has timed out, intermediate results are attached
        TimeoutError.data

zsearch process has timed outr4   r   r   r   Nc              3  N   >#    U  H  n[        TUR                  TS 9v   M     g7fr   r   r]   rk   rl   r   
last_points     r1   rm   $LoopFinder.search.<locals>.<genexpr>D  s!      INAGJw?   "%)rN  r   rM   rB   rO  rQ   r   r>   rP  rW  r   r]   r   r   r   r}   add_solutionanyr   )r.   rM   r^  r  start_pointwatchdogr   r  	last_edge	end_point
candidates	survivorsr   	next_edger   rf  s                 @@r1   r   LoopFinder.search  s?    --,,kkDMM*).z%%"2#DOO$:$:$<=  HHJEb	I!I 0070KJJ#e*4I!9jj'B $I $I&]]
:{GD%%el&:;) *
  IN   KK 45! " dr3   c                b    U R                   n[        U5      nX2;   d  [        USS9U;   a  g XU'   g NTr  )rP  loop_key)r.   r!  r-   r   s       r1   ri  LoopFinder.add_solutionI  s2    OO	tnxd;yH#r3   )rN  rP  rO  )r  r   r7   r8   )r7   rS   )r7   Iterator[Sequence[Edge]]r   r*   )rM   r   r7   r   F)rM   r   r^  rU   r7   r8   )r!  r   r7   r8   )r9   r:   r;   r<   r=   rV   r,   rW   r   rX  r[  r  r   ri  r?   rX   r3   r1   r  r    s?     5< . % %.$,6\r3   r  Fru  c                   U(       a  [        S [        U 5       5       5      nO[        S U  5       5      nUR                  [        U5      5      nU(       a
  X#S USU -   nU$ )zRReturns a normalized key.

The key is rotated to begin with the smallest edge id.
c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   r*  s     r1   rm   loop_key.<locals>.<genexpr>W  s     8GGro   c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   r*  s     r1   rm   r|  Y  s     .GGro   N)r>   r}   indexr   )rt   r  idsr~  s       r1   rv  rv  Q  sY    
 888...IIc#hE&kCK'Jr3   c                
   [        U R                  5      S:  a
  [        5       $ / n[        U R                  5      nU(       aA  [	        XR                  5       5      nUR                  U5        U[        U5      -  nU(       a  MA  U$ )zReturns all simple chains from `deposit`.

Each chains starts and ends at a leaf (degree of 1) or a junction (degree greater 2).
All vertices between the start- and end vertex have a degree of 2.
The result doesn't include reversed solutions.
r   )r   rt   r>   r   find_simple_chainr   r   )r  r-   rt   r  s       r1   r   r   `  sm     7==Aw&(IE
!'99;7U % r3   c                0   [        X5      n[        X R                  S9(       a  U$ [        XR                  5       5      n[	        U5      S:X  a  U$ UR                  5         UR                  5         U Vs/ s H  oDR                  5       PM     snU-   $ s  snf )a  Returns a simple chain containing `start` edge.

A simple chain start and ends at a leaf or a junction.

All connected edges have vertices of degree 2, except the first and last vertex.
The first and the last vertex have a degree of 1 (leaf) or greater 2 (junction).
r   r   )_simple_forward_chainr  r   r}   r   r  r   )r  rM   forward_chainbackwards_chainr   s        r1   r  r  r  s}     *'9MM??;+G^^5EFO
?q (78MMO8=HH8s   4Bc                |   U R                   nU/n US   nU R                  UR                  U5      n[        U5      S:w  a  U$ US   U:X  a  US   nOUS   n[	        UR                  UR
                  US9(       a  UR                  U5        OUR                  UR                  5       5        [        X2S9(       a  U$ M  )zReturns a simple chain beginning with `edge`.

All connected edges have vertices of degree 2, expect for the last edge.
The last edge has an end-vertex of degree 1 (leaf) or greater 2 (junction).
r   r  r   r   r   )	r   r   r]   r   r   rM   r   r}   r  )r  r   r   r  r  linkeds         r1   r  r    s     ooGFE
Ry((7;v;!L!9!9D!9D488TZZ9LLLL)/L r3   c                6    [        U R                  [        5      $ )z9Returns ``True`` if `edge` is a wrapper for linked edges.)ra   r_   rr   r   s    r1   is_wrapped_chainr    s    dllK00r3   c                   [        U 5      S:  a  [        S5      e[        XS9(       a$  [        XS9(       a  [        S5      e[	        U 5      $ [        S5      e)zWraps a sequence of linked edges (simple chain) into a single edge.

Two or more linked edges required. Closed loops cannot be wrapped into a single
edge.

Raises:
    ValueError: less than two edges; not a chain; chain is a closed loop

r  z!two or more linked edges requiredr   z0closed loop cannot be wrapped into a single edgezedges are not connected)r   rM  r   r  r  )r  r   s     r1   wrap_simple_chainr    sN     5zA~<=='/OPP!%((
.
//r3   c                \    [        U R                  [        5      (       a  [        U 5      $ U 4$ )z;Unwraps a simple chain which is wrapped into a single edge.)ra   r_   rr   _unwrap_simple_chainr   s    r1   unwrap_simple_chainr    s%    $,,,,#D))7Nr3   c                  &    \ rS rSrSrSrSS jrSrg)rr   i  z2Internal class to wrap a sequence of linked edges.r  c                $    [        U5      U l        g r*   )r>   rt   )r.   rt   s     r1   r,   EdgeWrapper.__init__  s    %*5\
r3   Nr   )r9   r:   r;   r<   r=   r5  r,   r?   rX   r3   r1   rr   rr     s    <I2r3   rr   c                r    [        U S   R                  U S   R                  [        U 5      [	        U 5      S9$ )Nr   r   r   )r   rM   r]   r!   rr   r  s    r1   r  r    s1    ab	ve}k%>P r3   c                    U R                   n[        U[        5      (       d   eU R                  (       a%  [	        S [        UR                  5       5       5      $ UR                  $ )Nc              3  @   #    U  H  oR                  5       v   M     g 7fr*   )r}   rj   s     r1   rm   '_unwrap_simple_chain.<locals>.<genexpr>  s     C+BaZZ\\+Bs   )r_   ra   rr   r^   r>   r}   rt   )r   wrappers     r1   r  r    sI    llGg{++++C8GMM+BCCC}}r3   c                &    [        S U  5       5      $ )Nc              3  J   #    U  H  n[        [        U5      5      v   M     g 7fr*   )r>   r   )rk   r  s     r1   rm   (_unwrap_simple_chains.<locals>.<genexpr>  s     ;F5wu~&&Fs   !#)r>   )r  s    r1   r$  r$    s    ;F;;;r3   c              #     #    [        U [        5      (       d  U  H  n[        U5       Sh  vN   M     gU n[        U5      (       a  [        [	        U5      5       Sh  vN   gUv   g N= N7f)zNYields all edges from any nested structure of edges as a flat stream of edges.N)ra   r   r   r  r  r2  s     r1   r   r     s`      eT""Dt}$$  D!!3D9:::J % ;s!   )A-A)2A-A+A-+A-c                j    U(       a  [        S [        U 5       5       5      $ [        S U  5       5      $ )zReturns a normalized key.c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   r*  s     r1   rm   chain_key.<locals>.<genexpr>  s     9WWro   c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   r*  s     r1   rm   r    s     /WWro   )r>   r}   )rt   r  s     r1   	chain_keyr    s+    9%999////r3   c                    [        X5      nU R                  5        H  nUR                  U5        M     UR                  nUR	                  S S9  U$ )a  Returns all open chains from `deposit`.

Returns only simple chains ending on both sides with a leaf.
A leaf is a vertex of degree 1 without further connections.
All vertices have a degree of 2 except for the leafs at the start and end.
The result does not include reversed solutions or closed loops.

.. note::

    This is a recursive backtracking algorithm with time complexity of O(n!).

Args:
    deposit (Deposit): edge deposit
    timeout (float): timeout in seconds

Raises:
    TimeoutError: search process has timed out
c                    [        U 5      $ r*   r   )xs    r1   r   &find_all_open_chains.<locals>.<lambda>	  s    Qr3   r   )OpenChainFinderr   r   r-   r   )r  rF   r   r   r-   s        r1   r   r     sM    , W.F""$d %  INN'N(r3   c                  L    \ rS rSr\4S	S jjrS
S jrSS jrSS jrSS jr	Sr
g)r  i  c                \    Xl         [        5       U l        / U l        [	        U5      U l        g r*   )r  r   solution_keysr-   rB   rl  rQ  s      r1   r,   OpenChainFinder.__init__  s$    365/1 )r3   c                H    U R                  U5      nU R                  U5        g r*   )forward_searchreverse_search)r.   r   forward_chainss      r1   r   OpenChainFinder.search  s     ,,T2N+r3   c                T  ^
^ U R                   nUR                  m
U R                  n/ nU4/nU(       a  UR                  (       a  [	        S5      eUR                  5       nUS   R                  nUR                  U5      n[        U5      [        U5      -
  n	U	(       ap  U	 Hi  n[        XqR                  T
S9(       d  UR                  5       nUR                  m[        U
U4S jU 5       5      (       a  MU  UR                  Xa4-   5        Mk     OUR                  U5        U(       a  M  U$ )Nsearch has timed outr   r   c              3  N   >#    U  H  n[        TUR                  TS 9v   M     g7fr   rd  re  s     r1   rm   1OpenChainFinder.forward_search.<locals>.<genexpr>.  s!      MR
AEE7CUrh  )r  r   rl  rQ   r   r   r]   r   r   r   rM   r}   rj  r   )r.   r   r  rl  r  r   r  rk  ro  backwards_edgesr   rf  s             @@r1   r  OpenChainFinder.forward_search  s    ,,//==13)-y%%"#9::HHJE)--K 00=J!*oE
:O+D";

GL#}} "&J MR   EGO4 , %%e,) d* r3   c                `  ^
^ U R                   nUR                  m
U R                  nUnU(       a  UR                  (       a  [	        SU R
                  S9eUR                  5       nUS   R                  nUR                  U5      n[        U5      [        U5      -
  nU(       aq  U Hj  n	[        XiR                  T
S9(       d  U	R                  5       n	U	R                  m[        U
U4S jU 5       5      (       a  MU  UR                  U	4U-   5        Ml     OU R                  U5        U(       a  M  g g )Nr  r4   r   r   c              3  N   >#    U  H  n[        TUR                  TS 9v   M     g7fr   rd  )rk   rl   r   new_start_points     r1   rm   1OpenChainFinder.reverse_search.<locals>.<genexpr>J  s"      RWQHRWrh  )r  r   rl  rQ   r   r-   r   rM   r   r   r   r]   r}   rj  r   ri  )r.   r  r  rl  r   r  rk  ro  r  r   r   r  s             @@r1   r  OpenChainFinder.reverse_search6  s    ,,//==%%"#9T^^TTHHJE(..K 00=J!*oE
:O+D";'J#}} '+jjO RW   TGeO4 , !!%() dr3   c                    U R                   n[        U5      nX2;   a  g UR                  U5        [        USS9nX2;   a  g UR                  U5        U R                  R	                  U5        g rt  )r  r  r-  r-   r   )r.   solutionr   r   s       r1   ri  OpenChainFinder.add_solutionQ  s[    !!!;$/;h'r3   )r  r  r-   rl  N)r  r   )r   r   r7   r8   )r   r   r7   list[tuple[Edge, ...]])r  r  r7   r8   )r  r   r7   r8   )r9   r:   r;   r<   rV   r,   r   r  r  ri  r?   rX   r3   r1   r  r    s    18 *,<)6
(r3   r  c                   ^  SU 4S jjnU$ )z[Returns a function that checks if a given sequence of edges has at least `count`
vertices.
c                    > [        U 5      T:  $ r*   r   )rt   counts    r1   has_min_edge_count)count_checker.<locals>.has_min_edge_countc  s    5zU""r3   rt   r   r7   rU   rX   )r  r  s   ` r1   count_checkerr  ^  s    
# r3   c                   ^  SU 4S jjnU$ )z@Returns a function that checks if two input edges are congruent.c                N  > U R                   R                  UR                   TS9=(       a$    U R                  R                  UR                  TS9=(       dO    U R                   R                  UR                  TS9=(       a$    U R                  R                  UR                   TS9$ )N)abs_tol)rM   r   r]   )r   r   r  s     r1   is_congruent_line'line_checker.<locals>.is_congruent_linel  sy    GGOOAGGWO5 6aeeW5
 GGOOAEE7O3 8aggw7	
r3   r   r   r   r   r7   rU   rX   )r  r  s   ` r1   line_checkerr  i  s    
 r3   c                   [        U R                  5      n/ nU(       a  UR                  5       nUR                  U5        [        U R	                  UR
                  5      5      nUR                  U R	                  UR                  5      5        UR                  U5        U H#  nU" XF5      (       d  M  UR                  U5        M%     U(       a  M  U$ )zReturns all edges from deposit that are not coincident to any other edge in the
deposit. Coincident edges are detected by the given `eq_fn` function.
)	r   rt   r   r   r   rM   r   r]   r   )r  eq_fnrt   unique_edgesr   ro  	candidates          r1   filter_coincident_edgesr  x  s     E!L
yy{D!00<=
'11$((;<4 #IT%%i( $ % r3   c                 ^^ [        U 5      n[        UR                  5       /5      nU HR  nU R                  UT5       H9  mTU;   a  M  [        UU4S jU 5       5      (       a  M(  UR	                  T5        M;     MT     U$ )aD  Returns all vertices from a :class:`RTree` and filters vertices that are closer
than radius `gap_tol` to another vertex.

Vertices that are close to another vertex are filtered out, so none of the returned
vertices has another vertex within the range of `gap_tol`.  It is not determined
which of close vertices is returned.
c              3  :   >#    U  H  n[        TUTS 9v   M     g7fr   )r   )rk   r   r  r   s     r1   rm   (filter_close_vertices.<locals>.<genexpr>  s     Nv!wy!W=vs   )r   r   rB  rj  r-  )rtr   r   mergedr   r  s    `   @r1   r   r     sp     r7DTXXZL)F,,VW=IF"NvNNN

9%	 >  Mr3   c                  ^^^ SUUU4S jjn[        UR                  5      m[        UR                  5      T-
  R                  m[	        XS9$ )a  Returns a list of `edges` sorted in counter-clockwise order in relation to the
`base` edge.

The `base` edge represents zero radians.
All edges have to be connected to end-vertex of the `base` edge.
This is a pure 2D algorithm and ignores all z-axis values.

Args:
    edges: list of edges to sort
    base: base edge for sorting, represents 0-radian
    gap_tol: maximum vertex distance to consider two edges as connected

Raises:
    ValueError: edge is not connected to center

c                4  > [        U R                  5      n[        U R                  5      nTR                  U5      T::  a  X!-
  R                  nO4TR                  U5      T::  a  X-
  R                  nO[        SU < S35      eUT-
  [        R                  -  $ )Nzedge z not connected to center)r   rM   r]   r   anglerM  mathtau)r   srl   
edge_angle
base_angleconnection_pointr   s       r1   r  !sort_edges_to_base.<locals>.angle  s    N$$Q'72%J&&q)W4%JuTH,DEFFZ'48833r3   r   r   )r   r]   rM   r  r	  )rt   baser   r  r  r  s     ` @@r1   sort_edges_to_baser    sB    (	4 	4 DHH~tzz"%55<<J%##r3   c                ^   [        U R                  5      S:  a
  [        5       $ U R                  nU/n[	        U5      nUR
                  n US   nUR                  nU R                  XS9n	[	        U	5      U-
  n
[        U
5      nUS:X  a
  [        5       $ US:  a  [        XUS9nU(       a  US   nOUS   nOU
R                  5       n[        UR
                  XS9(       d  UR                  5       nUR                  U5        [        UR                  XcS9(       a  U$ UR                  U5        M  )a  Returns the first loop found including the given `start` edge.

Returns an empty sequence if no loop was found.

Args:
    deposit (Deposit): edge deposit
    start (Edge): starting edge of the search
    clockwise (bool): search orientation, counter-clockwise when ``False``

r  r   r   r   r   r   )r   rt   r>   r   r   rM   r]   r   r  r   r   r}   r   r-  )r  rM   	clockwiser   r  	chain_setrk  rm  rn  ro  rp  r  sorted_edgesrq  s                 r1   r   r     s    7==AwooGGEE
I++K
"I	MM	,,Y,G

Oi/	IA:7N19-iGTL(,	(O	!Iy	C!**,IY9==+?Li - r3   c                x    [        S U 5       5      nU  Vs/ s H  o3R                  U;  d  M  UPM     sn$ s  snf )zReturns all edges from the iterable `base` that do not exist in the iterable
`edges` e.g. remove solutions from the search space.
c              3  8   #    U  H  oR                   v   M     g 7fr*   ry   r*  s     r1   rm   !subtract_edges.<locals>.<genexpr>  s     -ut77uro   )r   r\   )r  rt   edge_idsr   s       r1   r%   r%     s5     -u--H!=TTWWH%<DT===s   77)r   )rM   r[   r7   zCallable[[], int])g      )
rM   r   r]   r   r!   rS   r_   r   r7   r   )r   r   r   r   r7   rU   r  r  )rt   r   r7   rS   )r  zIterable[Sequence[Edge]]r7   r   )r  r   r7   
list[Edge])rt   r   r7   r   )rt   r   r7   rx  )r  r   rF   rS   r7   r   )r  r   r7   r   )r  r   rF   rS   r7   r6   )r  r   r7   r6   )r  r6   r7   rx  )r7  r   r   r   r7   r4  )rt   r   r7   ztuple[int, ...])r  r   rM   r   r7   r   )r  r   r   r   r7   r  )r   r   r7   rU   )r  r   r7   r   )r   r   r7   r   )rt   r   r7   r   )r  zIterable[Iterable[Edge]]r7   r6   )rt   zEdge | Iterable[Edge]r7   r   ry  )r  r[   )r  r   r  zCallable[[Edge, Edge], bool]r7   r   )r  rI  r   rS   r7   r   )rt   Iterable[Edge]r  r   r7   r  )T)r  r  rt   r  r7   r  )Tr=   
__future__r   typingr   r   r   r   r   r	   r
   r   typing_extensionsr   r   collectionsr   r   rG   r  
ezdxf.mathr   r   r   r   r   __all__r   ABS_TOLrV   	Exceptionr   rB   r   r   r   r   r   r   r   r   r    r  r!   r$   r"   r#   r   r   r   r  r   r#  r&   r   r4  r8  r   r[   rJ  r   r  rv  r   r  r  r  r  r  rr   r  r  r$  r   r  r   r  r  r  r  r   r  r   r%   rX   r3   r1   <module>r     s  Yt # W W W -     ? ? * 

	#9 	#D DI
: I
X !" -1DDHDDD$)D>AD	D& *1 $g gT 7> 
4 07 	 /6 	C 4; C
(
/ =D > '.. 5< !KH 8?  +22,2,"'2,2,l &d  6 "%S/8D>"AB BY Yx 05 $I&21
 9@ 0&2 2<0 (/$<N( N(b !   =IN9(* 3:!$!$!%!$!$H(!V>r3   