
    ^jh                   j   U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	 ddl
mZ ddlmZ ddlmZmZmZmZ dd	lmZmZmZmZmZ ddlZdd
lmZ ddlmZmZ ddl m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& ddl'm(Z(m)Z) ddl*m+Z+ ddl,m-Z-m.Z.m/Z/m0Z0 dZ1 ed      Z2e3e3e4e4e4f   e3e4e4e4f   f   Z5de6d<    ed       G d d             Z7 ed       G d d             Z8 ed       G d d             Z9 ed       G d d             Z:e8e9z  e:z  Z;e G d d             Z<dsdtd Z=dud!Z>dvd"Z?dvd#Z@dvd$ZAdwd%ZBdxd&ZCdyd'ZD	 	 	 	 	 	 	 	 dzd(ZEd{d)ZFd|d*ZGd}d+ZHd~d,ZI	 	 	 	 	 	 	 	 dd-ZJ	 	 	 	 dd.ZK	 	 	 	 	 	 	 	 dd/ZL	 	 	 	 	 	 	 	 dd0ZMdd1ZNdd2ZOdd3ZPdd4ZQ	 	 	 	 	 	 	 	 	 	 dd5ZR	 d	 	 	 	 	 	 	 dd6ZSdd7ZT	 	 	 	 dd8ZUdd9ZVdd:ZWdd;ZXdd<ZY	 	 	 	 	 	 	 	 	 	 dd=ZZ	 	 	 	 	 	 dd>Z[	 d	 	 	 	 	 	 	 dd?Z\	 	 	 	 	 	 	 	 dd@Z]	 	 	 	 	 	 	 	 ddAZ^dddBZ_e	 	 	 	 	 	 	 	 	 	 ddC       Z`e	 	 	 	 	 	 	 	 	 	 ddD       Z`	 	 	 	 	 	 	 	 	 	 ddEZ`	 d	 	 	 	 	 	 	 ddFZa	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddGZb	 	 d	 	 	 	 	 	 	 	 	 	 	 ddHZcddIZd	 	 	 	 	 	 	 	 	 	 ddJZe	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 ddKZf	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddLZg	 	 d	 	 	 	 	 	 	 	 	 ddMZh	 	 	 	 	 	 	 	 	 	 ddNZi	 	 	 	 ddOZj	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 ddPZk	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddQZl	 	 d	 	 	 	 	 	 	 	 	 ddRZm	 	 d	 	 	 	 	 	 	 	 	 ddSZn	 d	 	 	 	 	 	 	 	 	 ddTZo	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddUZp	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddVZq	 	 d	 	 	 	 	 	 	 	 	 ddWZr	 	 d	 	 	 	 	 	 	 	 	 ddXZsdYZtddZZudd[Zv e&ddd\       e&d\dd      d]d\d^ d_ f e&ddd`       e&dd\d      dad`db dc f e&dd\d       e&ddd\      ddd\de df f e&dd`d       e&d\dd      dgd`dh di f e&d\dd       e&dd\d      djd\dk dl f e&d`dd       e&ddd\      dmd`dn do fgZwddpZxddqZy	 	 	 	 ddrZzy)a  
build123d BREP from STL

name: brep_from_stl.py
by:   gumyr with codex gpt-5.4
date: April 8th, 2026

desc:
    This python module reconstructs approximate analytic BREP primitives from a
    triangulated STL-like surface mesh.

    The user-facing entry point is ``detect_primitives``. The reconstruction
    pipeline first builds a mesh index of face centers, normals, and adjacency.
    It then searches for clean proxy planes, spheres, cylinders, and fallback
    normal-grouped planes in that order so stronger primitive evidence claims
    faces before more ambiguous regions are processed.

    Each detector uses a broad classification step to identify candidate faces,
    sews or connects them into regions, fits a local analytic primitive, and
    grows the region across adjacent compatible faces while validating the fit.
    The resulting primitive faces are then converted into build123d code strings
    aligned with the returned primitives.

license:

    Copyright 2026 gumyr

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    )annotationsN)defaultdict)IterableSequence)	dataclass)combinations)acosceillog10sqrt)AnyLiteral	TypeAliasTypeVaroverload)DBSCAN)AlignGeomType)
TOL_DIGITSAxisLocationPlanePosVector)CylinderSphere)	Rectangle)FaceShape	ShapeListVertexg&.>Tr   EdgeKeyT)frozenc                  :    e Zd ZU dZded<   ded<   ded<   ded<   y	)

FaceSamplez&Cached geometric data for a mesh face.intindexr   facer   centernormalN__name__
__module____qualname____doc____annotations__     P/opt/ringagent/.cad-venv/lib/python3.12/site-packages/build123d/brep_from_stl.pyr&   r&   D   s    0J
JNNr3   r&   c                  l    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   d	ed<   d	ed<   d	ed<   d	ed<   y)
PlanePatchz.Plane primitive fitted to a set of mesh faces.zLiteral['plane']kindfrozenset[int]face_indicesr   originr+   floatu_minu_maxv_minv_maxresidualNr,   r2   r3   r4   r6   r6   N   s4    8
  NNLLLLOr3   r6   c                  j    e Zd ZU dZded<   ded<   ded<   ded<   d	ed
<   ded<   d	ed<   edd       Zy)CylinderPatchz1Cylinder primitive fitted to a set of mesh faces.zLiteral['cylinder']r7   r8   r9   r   
axis_pointaxis_directionr;   radiuszLiteral[-1, 1]normal_signr@   c                B    t        | j                  | j                        S )zAxis property)r   rC   rD   )selfs    r4   axiszCylinderPatch.axisi   s     DOOT%8%899r3   N)returnr   )r-   r.   r/   r0   r1   propertyrI   r2   r3   r4   rB   rB   ]   s?    ;
  MO: :r3   rB   c                  D    e Zd ZU dZded<   ded<   ded<   ded	<   ded
<   y)SpherePatchz/Sphere primitive fitted to a set of mesh faces.zLiteral['sphere']r7   r8   r9   r   r*   r;   rE   r@   Nr,   r2   r3   r4   rM   rM   o   s     9
  NMOr3   rM   c                  `    e Zd ZU dZded<   ded<   ded<   dZd	ed
<   edd       ZddZddZ	y)	MeshIndexz4Indexed mesh data reused by the primitive detectors.
list[Face]faceslist[FaceSample]face_samplesz1dict[tuple[tuple[float, float, float], ...], int]face_key_lookupNzdict[int, set[int]] | Noneadjacent_face_indicesc                J   t        |j                               } | |t        |      D cg c]9  \  }}t        |||j	                         t        |j                                     ; c}}t        |      D ci c]  \  }}t        |      | c}}      S c c}}w c c}}w )z.Create a mesh index from all faces of a shape.)r(   r)   r*   r+   )rQ   rS   rT   )listrQ   	enumerater&   r*   _normalized	normal_at	_face_key)clsshaperQ   r(   r)   s        r4   
from_shapezMeshIndex.from_shape   s     U[[]# $-U#3  E4 ;;=&t~~'78	 ;DE:J+65$	$&
 	
s   >B:Bc                   | j                   yt        t              }t        | j                        D ]7  \  }}|j                         D ]  }|t        |         j                  |       ! 9 t        t        | j                              D ci c]  }|t                }}|j                         D ]!  }|D ]  }||   j                  ||hz
          # || _         yc c}w )z*Build the face adjacency map on first use.N)rU   r   setrX   rQ   edges	_edge_keyaddrangelenvaluesupdate)rH   edge_to_face_indicesr(   r)   edge	adjacencyr9   
face_indexs           r4   ensure_adjacencyzMeshIndex.ensure_adjacency   s     %%1>I#>N$TZZ0 	AKE4

 A$Yt_599%@A	A @ES_?U$VeUCE\$V	$V0779 	JL* J
*%,,\ZL-HIJ	J &/"	 %Ws   Cc                F    |D cg c]  }| j                   |    c}S c c}w )z:Return faces by index while preserving the provided order.)rQ   )rH   r9   r(   s      r4   face_setzMeshIndex.face_set   s!     0<<e

5!<<<s   )rJ   z'MeshIndex')rJ   None)r9   zIterable[int]rJ   rP   )
r-   r.   r/   r0   r1   rU   classmethodr^   rl   rn   r2   r3   r4   rO   rO   }   s?    >""FF8<5<
 
(/ =r3   rO   c                ,    t        fd| D              S )z.Convert a vector into a rounded, hashable key.c              3  6   K   | ]  }t        |        y wNround).0valuedigitss     r4   	<genexpr>z&_rounded_vertex_key.<locals>.<genexpr>   s     :%uf%:   )tuple)vectorrx   s    `r4   _rounded_vertex_keyr}      s     :6:::r3   c                p    t        j                  | D cg c]  }t        |       c}t              S c c}w )z(Convert vectors into a NumPy row matrix.dtype)npasarrayr{   r;   )vectorsr|   s     r4   _vector_rowsr      s'     ::7;uV};5II;s   3c                0    t        |       t        |       z  S )z,Return the arithmetic mean of scalar values.)sumre   )rf   s    r4   _mean_scalarr      s     v;V$$r3   c                |    t        |       }t        |      dz  }t        |      dz  r||   S ||dz
     ||   z   dz  S )z#Return the median of scalar values.             @)sortedre   )rf   orderedmiddles      r4   _median_scalarr      sN     VnG\QF
7|avFQJ'&/1S88r3   c                l    t        |       t        t        fd| D              t        |       z        S )z:Return the population standard deviation of scalar values.c              3  .   K   | ]  }|z
  d z    yw)r   Nr2   )rv   rw   means     r4   ry   z_std_scalar.<locals>.<genexpr>   s     <EUT\a'<s   )r   r   r   re   )rf   r   s    @r4   _std_scalarr      s.     D<V<<s6{JKKr3   c                J    t               }| D ]  }||z  }	 |t        |       z  S )z&Return the arithmetic mean of vectors.)r   re   )r   totalr|   s      r4   _mean_vectorr      s2     HE 3w<r3   c                8    t        j                  | t              S )z2Convert point coordinates into a NumPy row matrix.r   )r   r   r;   )pointss    r4   _point_rowsr      s     ::fE**r3   c           	         |dk  rg S t        |       |k  rt        |       S |dk(  r| d   gS t        |       dz
  }t        |      D cg c]  }| t        ||z  |dz
  z            c}S c c}w )z:Return a deterministic evenly spaced subset of a sequence.r   r   )re   rW   rd   ru   )rf   	max_count
last_indexis       r4   _evenly_spaced_subsetr      sv     A~	
6{iF|A~q	{VqJEJ9EUVF5Z9q=9:;VVVs   A(c                   t        |       |k  rg S t        ||      j                  t        |             j                  }t        t        |            D cg c]   }|dk7  s	t        j                  ||k(        " c}S c c}w )z;Cluster points with DBSCAN and return one mask per cluster.epsmin_samples)	re   r   fitr   labels_r   r`   r   r   )r   r   r   labelslabels        r4   _cluster_pointsr      sh    
 6{[ 	599+f:MNVVF5;CK5HXEEUWKBJJv'XXXs   
A>!A>c                Z    | j                         }t        d |D              }|d   |d   fS )z,Create an order-independent key for an edge.c              3  N   K   | ]  }t        |j                                 y wrs   r}   r*   rv   vertexs     r4   ry   z_edge_key.<locals>.<genexpr>  s     N6%fmmo6N   #%r   r   )verticesr   )ri   r   endss      r4   rb   rb      s2     }}HNXNND7DGr3   c                T    t        t        d | j                         D                    S )z+Create an order-independent key for a face.c              3  N   K   | ]  }t        |j                                 y wrs   r   r   s     r4   ry   z_face_key.<locals>.<genexpr>	  s     R"6==?3Rr   )r{   r   r   )r)   s    r4   r[   r[     s%     R$--/RR r3   c                    t        | t              r| S t        | dd      }t        |      r |       }t        |t              r|S t	        d|       )zCExtract a Face from a build123d value or raise a descriptive error.r)   NzExpected Face while building )
isinstancer   getattrcallableRuntimeError)rw   contextface_methodr)   s       r4   _as_facer     sR     %%.K}dD!K
6wi@
AAr3   c                    t        ddd      }t        |j                  |             dkD  rt        ddd      }t        | j	                  |            }t        | j	                  |            }||fS )z0Construct an orthonormal basis lying in a plane.      ?        g?)r   absdotrY   cross)r+   helperuvs       r4   _plane_basisr     sd     Cc"F
6::f$S#&FLL()AFLLO$Aa4Kr3   c                b    | D cg c]  }t        ||z
  j                  |            ! c}S c c}w )z%Measure point distances from a plane.)r   r   )r   plane_originplane_normalpoints       r4   _plane_point_distancesr   %  s/    
 HNNeC%**<89NNNs   $,c                    t        |       dk  ryt        | d      D ]5  \  }}}||z
  j                  ||z
        j                  t        kD  s0|||fc S  y)z9Return the first non-collinear triplet of points, if any.   N)re   r   r   lengthEPS)r   point_apoint_bpoint_cs       r4   _pick_non_collinear_tripletr   -  sd    
 6{Q%1&!%< -!'g$$Ww%67>>DGW,,- r3   c                   t        |       |k  rg S t        ||d      j                  t        |             j                  }t        t        |            D cg c]   }|dk7  s	t        j                  ||k(        " c}S c c}w )z1Cluster unit vectors with cosine-distance DBSCAN.cosine)r   r   metricr   )	re   r   r   r   r   r   r`   r   r   )r   r   r   r   r   s        r4   _cluster_unit_vectorsr   :  sm    
 7|k!	3KA	\'"	#	 
 6<CK5HXEEUWKBJJv'XXXs   
A?"A?c                    ||z
  j                   }| |z
  j                   }| |z
  j                   }|| z
  j                  || z
        j                   }|t        k  ry||z  |z  d|z  z  S )z6Return the circumradius of three non-collinear points.Nr   )r   r   r   )r   r   r   side_aside_bside_c
area_twices          r4   _circumradius_from_pointsr   I  sx    
 ''F''F''FG#**7W+<=DDJSVOf$z)9::r3   c                    i }| D ]B  }|j                         D ]-  }|j                         }|j                  t        |      |       / D t	        |j                               S )z7Collect unique face vertices using rounded coordinates.)r   r*   
setdefaultr}   rW   rf   )rQ   r   r)   r   r*   s        r4   _unique_face_verticesr   W  sf     :<H Emmo 	EF]]_F 3F ;VD	EE !""r3   c                t    t        |       }|j                  t        k  rt        d      |j	                         S )z8Return a normalized vector or raise for near-zero input.z!Cannot normalize near-zero vector)r   r   r   
ValueError
normalized)r|   units     r4   rY   rY   b  s2     &>D{{c<==??r3   c                |    t        |       }t        |      t        t        d      fd      }|   dk\  r|S | S )z3Flip a direction to a stable canonical orientation.r   c                     t        |          S rs   r   )r   rf   s    r4   <lambda>z)_canonicalize_direction.<locals>.<lambda>p  s    Cq	N r3   keyr   )rY   r{   maxrd   )	directionr   	max_indexrf   s      @r4   _canonicalize_directionr   k  sB     y!D4[FE!H":;I)$)44u4r3   c           	     8   t        |       dk  rt        d      t        |       }t        j                  | D cg c]  }t        ||z
         c}t              }t        j                  j                  |d      \  }}}t        t        |d          }||fS c c}w )z8Fit a plane to points with singular value decomposition.r   z)Need at least three points to fit a planer   F)full_matricesr   )re   r   r   r   r   r{   r;   linalgsvdr   r   )r   centroidr   rows_u_svhr+   s           r4   _fit_plane_to_pointsr   t  s     6{QDEEF#H::FC5uUX-.C5QDt59JBB$VRV_5FV Ds   Bc                >   | j                          | j                  J |h}|dfg}g }|rr|j                  d      \  }}|j                  |       ||k\  r-| j                  |   D ]2  }	|	|v s|	|vr|j	                  |	       |j                  |	|dz   f       4 |rr|S )z=Collect a local face patch with bounded breadth-first search.r   r   )rl   rU   popappendrc   )

mesh_index
seed_indexallowed_indices	max_depthvisitedfrontierpatchrk   depthneighbor_indexs
             r4   
_bfs_patchr    s     !++777lGQ HE
$LLO
EZ I(>>zJ 	9N(N/,QKK'OO^UQY78		9  Lr3   c                    t        t              }|D ]7  }|t        | j                  |   j                  |         j                  |       9 t        |j                         t        d      S )z4Group face indices by approximately equal face area.Tr   reverse)	r   rW   ru   rQ   arear   r   rf   re   )r   r   
tol_digitsby_areark   s        r4   _group_indices_by_arear
    s`     .9->G% Y
j&&z277DELLZXY'.."T::r3   c                    g }|j                         D ]:  }| j                  j                  t        |            }|*|j	                  |       < t        t        |            S )z/Map a sewn component back to mesh face indices.)rQ   rT   getr[   r   r   r`   )r   	componentindicesr)   rk   s        r4   _indices_from_sewn_componentr    s\     G! '//33IdOD
!NN:&' #g,r3   c           	        t        t              }i }t        | j                        D ]  \  }}|j	                         D ]  }t        |      }||   j                  |       |j                         D cg c]  }|j                          }}t        |d   j                  |d   j                  z   dz  |d   j                  |d   j                  z   dz  |d   j                  |d   j                  z   dz        ||<     t        t        | j                              D ci c]  }|g  }	}|j                         D ]J  \  }}
t        |
      dk7  r|
\  }}||   }|	|   j                  ||f       |	|   j                  ||f       L |	j                         D ci c]  \  }}|t!        |d        c}}S c c}w c c}w c c}}w )z4Build face adjacency keyed by shared edge midpoints.r   r   r   r   c                    | d   S )Nr   r2   items    r4   r   z5_build_face_edge_midpoint_adjacency.<locals>.<lambda>  s
    $q' r3   r   )r   rW   rX   rQ   ra   rb   r   r   r*   r   XYZrd   re   itemsr   )r   edge_to_facesedge_midpointsr(   r)   ri   edge_keyr   r   rj   r9   firstsecondmidpoint	neighborss                  r4   #_build_face_edge_midpoint_adjacencyr    s   
 6A5FM,.N !1!12 	tJJL 	D H(#**516:mmoFFFHF'-!!.#5!!.#5!!.#5(N8$			  %S)9)9%:;6r	6I 6 #0"5"5"7 4,|!$v!(+% 23&  %!234 !* 1E9 	vi%9:: % G6s   1F7
F<Gc                   | j                   | j                  z
  }| j                  | j                  z
  }| j                  | j                   z   dz  }| j                  | j                  z   dz  }t	        | j
                        \  }}t        | j                  || j
                        }t        |t        ||d      z  t        ||      z  d      S )z/Create a planar Face from a fitted plane patch.r   )r:   x_dirz_dirr   zplane primitive)r=   r<   r?   r>   r   r+   r   r:   r   r   r   )r   u_sizev_sizeu_centerv_centeru_vec_v_vecplanes           r4   build_plane_facer*    s     [[5;;&F[[5;;&Fekk)S0Hekk)S0H .ME6U%,,GEHh**Yvv-FF r3   c                V   t        |      }|D cg c]*  }|| j                  z
  j                  | j                        , }}g }|D ]k  }|| j                  z
  }|| j                  |j                  | j                        z  z
  }|j                  t
        k  rQ|j                  |j                         m t        |      }t        |      }	t        |      }
t        | j                  | j                  |z  z   | j                        t        |
|	|z
  t        j                        z  }t        |dd      }t        |      st!        d       |       j#                  t$        j&                        }|rt)        |d   t*              st!        d      | j,                  dkD  r|d   S |d    S c c}w )z7Create a cylindrical Face from a fitted cylinder patch.)r:   r"  alignrQ   Nz*Expected cylinder shape to provide faces()r   z;Expected cylindrical face while building cylinder primitive)r   rC   r   rD   r   r   r   minr   r   r   r   r   NONEr   r   r   	filter_byr   CYLINDERr   r   rF   )r   support_facesr   r   axis_valuesradial_distancesoffsetradialaxis_minaxis_maxrE   cylinder_shapecylinder_facesfiltered_facess                 r4   build_cylinder_facer<    s    %]3HLTBH%""	"''(<(<=K   /%***%..E<P<P1QQQ==C./ ;H;H,-F%"6"6"AA"" 	H,EJJ?@N ^Wd;NN#GHH#%//0A0ABNN1,=t!DI
 	
 !& 1 1A 5>!MN1<M;MM3s   /F&c                    t        |      }t        |D cg c]  }|| j                  z
  j                   c}      }t	        t        t        | j                         t        |      z  d      S c c}w )z3Create a spherical Face from a fitted sphere patch.zsphere primitive)r   r   r*   r   r   r   r{   r   )r   r2  r   r   rE   s        r4   build_sphere_facer>    s]     %]3H(Sfu||3;;STFCu||,-v>@RSS Ts    A-c                    | D cg c]  }t        j                  |      s| }}t        |      dk  rt        d      S |d   }|t        |      dz     }|d   }|t        k  rt        d      S ||z
  |z  S c c}w )z5Measure radius spread relative to the midpoint value.r   infr   r   r   )r   isfinitere   r;   r   )	signaturerw   finitelowmidhighs         r4   _relative_radius_spreadrG    s     "+Abkk%.@eAFA
6{QU|
)C
V!
"C":D
czU|3J# Bs
   A6A6c                   |j                   |   }g }| |   D ]X  \  }}||vr|j                   |   }t        |j                  |j                  |      }	|j                  |	|	n
t	        d             Z t        t        |            S )z2Estimate local radii implied by neighboring faces.r@  )rS   r   r*   r   r;   r{   r   )
edge_adjacencyr   rk   r   sample	estimatesr  edge_midpointneighborrE   s
             r4   _face_radius_signaturerN    s     $$Z0FI)7
)C 	I%0**>:*MMOO

 	6#55<H	I 	"##r3   c                   | j                          | j                  J t        |      }g }|r|j                         }|g}|g}|r_|j                         }| j                  |   D ]:  }||vr|j	                  |       |j                  |       |j                  |       < |r_|j                  t        |             |rt        |t        d      S )z+Split a face set into connected components.Tr  )rl   rU   r`   r   remover   r   re   )	r   r9   	remaining
componentsr   r  r   rk   r  s	            r4   _connected_face_componentsrS  5  s     !++777L!I"$J
]]_
L	<!J","B"B:"N 0!2  0  0/0  	&+,  *#t44r3   c                    t        |       }t               }|D ]3  }t        || ||      }|st        |      |k  s#|j	                  |       5 t        | |      S )z<Return connected components whose signatures look spherical.)r  r`   rN  rG  rc   rS  )r   r   similarity_tolerancerI  sphere_like_indicesrk   rB  s          r4   _sphere_like_face_componentsrW  Q  so     9DN$'E% 
0
*	
	 "9-1EE##J/
0 &j2EFFr3   c                   | j                   |j                  z
  }||j                  |j                  |j                        z  z
  }|j                  t
        k  ryt        |j                  |j                  z
        }dt        |j                         j                  | j                              z
  }t        |dz  |j                  dz        }||kD  s|dkD  ry|t        |t
              z  |dz  z   S )z6Score how well a face sample matches a cylinder patch.Nr   {Gz?{Gz?Q?)r*   rC   rD   r   r   r   r   rE   r   r+   r   )rJ  r   shape_scaler5  r6  radius_errornormal_errorradius_tolerances           r4   _cylinder_face_errorr`  j  s    
 ]]U---Fe**VZZ8L8L-MMMF}}v}}u||34LV..044V]]CDDL;-u||d/BC&&,*=#.44|d7JJJr3   c                   |j                   | j                   z
  }|j                  t        k  ryt        |j                  |j                  z
        }dt        |j                         j                  | j                              z
  }t        |dz  |j                  dz        }||kD  s|dkD  ry|t        |t              z  |dz  z   S )z4Score how well a face sample matches a sphere patch.Nr   rY  rZ  r[  )	r*   r   r   r   rE   r   r   r+   r   )rJ  r   r\  r6  r]  r^  r_  s          r4   _sphere_face_errorrb  {  s    
 \\FMM)F}}v}}u||34LV..044V]]CDDL;-u||d/BC&&,*=#.44|d7JJJr3   c                X   | j                   j                  |j                  j                  |z
  k  xs |j                   j                  | j                  j                  |z
  k  xs | j                   j                  |j                  j                  |z
  k  xs |j                   j                  | j                  j                  |z
  k  xsb | j                   j                  |j                  j                  |z
  k  xs0 |j                   j                  | j                  j                  |z
  k   S )z7Return whether two axis-aligned bounding boxes overlap.)r   r  r.  r  r  )box1box2	tolerances      r4   _bounding_boxes_overlaprg    s     	

TXXZZ)++ 	/88::

Y..	/88::

Y..	/ 88::

Y..	/ 88::

Y..		/
 88::

Y.. r3   c                     y rs   r2   r   r   r   r\  s       r4   grow_curved_patchrj    s     r3   c                     y rs   r2   ri  s       r4   rj  rj    s     r3   c                P   | j                          | j                  J t        |j                        |z  }t	        |      }|r|j                         }| j                  |   D ]i  }||v s||vr| j                  |   }t        |t              rt        |||      nt        |||      }	|	H|j                  |       |j                  |       k |rt        |      D 
cg c]  }
| j                  |
    }}
t        |t              r.g }g }|D ]  }|j                  |j                  z
  }||j                   |j#                  |j                         z  z
  }|j$                  t&        k  r[|j                  |j$                         |j                  dt)        |j+                         j#                  |j,                              z
          t        dt/        |      |j                  |j                   |rt1        |      n|j2                  |j4                  |rt1        |            S |j6                        S g }g }|D ]  }|j                  |j                  z
  }|j$                  t&        k  r0|j                  |j$                         |j                  dt)        |j+                         j#                  |j,                              z
          t9        dt/        |      |j                  |rt1        |      n|j2                  |rt1        |            S |j6                        S c c}
w )z5Grow a curved patch across adjacent compatible faces.r   cylinderr7   r9   rC   rD   rE   rF   r@   spherer7   r9   r*   rE   r@   )rl   rU   r`   r9   rW   r   rS   r   rB   r`  rb  rc   r   r   r*   rC   rD   r   r   r   r   r   r+   	frozensetr   rE   rF   r@   rM   )r   r   r   r\  claimedr   rk   r  rJ  scorer(   claimed_samplesradii	residualsr5  r6  s                   r4   rj  rj    s    !++777%$$%7GG}H
\\^
(>>zJ 	,N(N/,Q,,^<F e]3 %VUK@'{C 
 }KK'OON+	,   DJ'?S%z..u5SOS%'	% 	PF]]U%5%55Fe22VZZ@T@T5UUUF}}#LL'S3v'8'8':'>'>v}}'M#NNO	P "7+'' //*/<&U\\))09\),
 	
 @E~~
 	
 EI! L-==CV]]#s6#4#4#6#:#:6==#IJJKL w'||&+|E",5i( 
 <A>> ; Ts   L#c                H   t        t              }|D ]J  }t        | j                  |   j                        }|t        fd|D                 j                  |       L g }|j                         D ]  }|j                  t        | |              t        |t        d      S )z<Return connected components of approximately coplanar faces.c              3  6   K   | ]  }t        |        y wrs   rt   )rv   rw   normal_digitss     r4   ry   z._plane_like_face_components.<locals>.<genexpr>  s     LEE%7Lrz   Tr  )r   r`   r   rS   r+   r{   rc   rf   extendrS  r   re   )r   r   ry  normal_groupsrk   r+   rR  r9   s     `     r4   _plane_like_face_componentsr|    s     HSSVGWM% 

()@)@)L)S)STeLVLLMQQ	

 #%J%,,. P4ZNOP*#t44r3   c                   | j                         j                  }||z  }	||z  }
||z  }t        j                  |       j	                         }|j                         D cg c]!  }t        |j                               |k\  s |# }}|sg S t        d |D              }|D cg c]  }|j                  ||z  k\  s| }}g }|D ]  }t        |j                               }|j                         }|j                         }g }g }|j                  D ]  }dt        |j                  j!                  |            z
  |kD  r.t        |j                  |z
  j!                  |            }||	kD  r[t#        |j$                  j                         ||
      s|j'                  |j                  |      s|j)                  |j*                         |j)                  |        t        |      dk  r*|j-                  |      }t/        |      }t1        |      \  }}|D cg c]  }||z
  j!                  |       }}|D cg c]  }||z
  j!                  |       }}|j)                  t3        dt5        |      ||t7        |      t        |      t7        |      t        |      t9        |      	              |S c c}w c c}w c c}w c c}w )z0Detect dominant planes from cleaned proxy faces.c              3  4   K   | ]  }|j                     y wrs   )r  )rv   r)   s     r4   ry   z2_detect_planes_from_clean_proxy.<locals>.<genexpr>  s     54995   r   )rf  r   r)  	r7   r9   r:   r+   r<   r=   r>   r?   r@   )bounding_boxdiagonalcopydeepcopycleanrQ   re   ra   r   r  r   rZ   r*   rS   r   r+   r   rg  r)   	is_insider   r(   rn   r   r   r6   rq  r.  r   ) r]   r   normal_toleranceplane_tolerance_factorbbox_tolerance_factorinside_tolerance_factormin_proxy_edgesmin_proxy_area_ratior\  plane_tolerancebbox_toleranceinside_tolerancecleaned_shaper)   proxy_facesmax_areapatches
proxy_faceproxy_normalproxy_center
proxy_bboxmatched_indices	distancesrJ  distancer2  r   r'  v_vecr   u_valuesv_valuess                                    r4   _detect_planes_from_clean_proxyr    s    $$&//K!$::O #88N"%<<MM%(..0M&,,.#djjl2C2VK  	555H$		X@T5T(TK  !#G! *

.z/C/C/EF!((*,,.
	 -- 	'FS**<899<LLFMML8==lKLH/)*((*J ''AQ'R""6<<0X&	' !#"++O<(7#L1uEMN6Vl*//6NNEMN6Vl*//6NN&7##(m(m(m(m%i0
	
=*
V NoB ONs$   !J7J7!J<9J<K<Kc                   t        |      dk  ry| j                  |      }t        |      }t        |      dk  ryt        |      \  }}t	        |      }t        |||      }	||z  }
t        |	      |
kD  ry|D cg c]  }| j                  |    }}|D cg c])  }dt        |j                  j                  |            z
  + }}|rt        |      |kD  ryt        |      \  }}|D cg c]  }||z
  j                  |       }}|D cg c]  }||z
  j                  |       }}t        dt        |      ||t        |      t        |      t        |      t        |      |	rt        |	      	      S d	      S c c}w c c}w c c}w c c}w )z4Fit and validate a plane patch from candidate faces.r   Nr   r   r)  r   r  )re   rn   r   r   r   r   r   rS   r   r+   r   r   r6   rq  r.  r   )r   r9   r\  r  r  r2  r   r:   r+   r  r  r(   samplesrJ  normal_errorsr'  r  r   r  r  s                       r4   _build_plane_patchr  N  s    <1''5M$]3H
8}q)(3NFF$V,F&x@I!$::O
9~';GH%z&&u-HGHHOPfS3v}}00899PMP]+.>>'LE5;CD&%%e,DHD;CD&%%e,DHD|,(m(m(m(m,5i(
 
 <?
 
 IP
 EDs   9E9.E>(F	Fc                    t        t        |       j                  t        |                  }t        t	        t        dt        d|                        S )z1Return the unsigned angle between two directions.r   g      )r   r   r   r;   r	   r.  r   )direction_adirection_br   s      r4   _direction_angle_deltar  z  sF     ,001H1UVC c#s4~./00r3   c                    t        t        t        |      t        |      g            }|| z
  }|||j                  |      z  z
  }|j                  S )z6Measure the perpendicular separation between two axes.)r   r   r   r   )r   r  r   r  average_directiondeltaperpendiculars          r4   _perpendicular_axis_shiftr    s\     0$[13J;3WX	

 gE-		:K0LLLMr3   c                   g }|D ]  }d}|D ]  }	|	d   }
t        |
j                  |j                        |k  s,t        |
j                  |
j                  |j                  |j                        ||z  k  sit	        |
j
                  |j
                  z
        t        |
j
                  |j
                  t              z  |k  s|	j                  |       d} n |r|j                  |g        g }|D ]  }	t        |	      dk(  r|j                  |	d          ' t               j                  d |	D         }t        |      D cg c]  }| j                  |    }}t        t        |	D cg c]  }|j                   c}            }t        |	D cg c]  }|j                   c}      }g }g }|D ]  }|j                   |z
  }|||j#                  |      z  z
  }|j$                  t        k  r=|j                  |j$                         |j                  dt	        |j'                         j#                  |j(                              z
          |sa|j                  t+        d|||t-        |      t/        d |	D              dk\  rdnd	|rt-        |      n"t-        |	D cg c]  }|j0                   c}      
              |S c c}w c c}w c c}w c c}w )z7Merge cylinder patches that describe the same cylinder.Fr   Tr   c              3  4   K   | ]  }|j                     y wrs   r9   rv   r   s     r4   ry   z-merge_equivalent_cylinders.<locals>.<genexpr>  s     *Q%5+=+=*Qr  r   rm  c              3  `   K   | ]&  }|j                   t        |j                        z   ( y wrs   )rF   re   r9   )rv   ps     r4   ry   z-merge_equivalent_cylinders.<locals>.<genexpr>  s"     N11==3q~~+>>Ns   ,.r   rn  )r  rD   r  rC   r   rE   r   r   r   re   rq  unionr   rS   r   r   r*   r   r   r   r+   rB   r   r   r@   )r   r  r\  axis_angle_toleranceaxis_shift_factorradius_ratio_tolerancegroupsr   placedgrouprepresentativemergedr9   r(   r  rD   rC   ru  rv  rJ  r5  r6  r  s                          r4   merge_equivalent_cylindersr    s    )+F # 	E"1XN&"1153G3G (( ."--"11$$((	 !223 --<=n++U\\3?@)* U#)	* MM5'"1#4 #%F '
u:?MM%(#(y{((*Q5*QR?El?STe:**51TT0EB5%..BC
 ""G5#3#3"GH
	 	PF]]Z/Fnvzz./IIIF}}#LL'S3v'8'8':'>'>v}}'M#NNO	P )%-#E* NNNRSS  ! !+%5&Aaqzz&AB	
+'
P MG UB"G6 'Bs   K*KK
Kc	                   t        |      }	t        |	      dk  ryg }
|	D ]k  }|| j                  z
  }|| j                  |j	                  | j                        z  z
  }|j
                  t        k  rQ|
j                  |j
                         m |
syt        ||z  | j                  dz        }t        |
      }t        |
      }t        || j                  z
        |kD  ry|t        |t              z  |kD  ryt        |	      }|	D cg c]   }||z
  j	                  | j                        " }}t        |	|      D cg c]  \  }}|dk  s| }}}t        |	|      D cg c]  \  }}|dk\  s| }}}t        |      dk  st        |      dk  ryt        |      t        |	      z  |k  st        |      t        |	      z  |k  ry||z  }g }g }||fD ]  }t        |      \  }}t!        |||      }t        |      |kD  st        |      |dz  kD  r y|j                  ||f       t#        |      }| yt%        | }|	|t        k  r y|j                  |        t        t        |d   d   j	                  |d   d               dz
        |kD  ryt        |      t        k  ryt        fd	|D              z  |kD  ryt        fd
|
D              z  dkD  ryyc c}w c c}}w c c}}w )z=Validate that support faces form a bounded cylindrical patch.   FrZ  r   r   r   r   r   c              3  :   K   | ]  }t        |z
          y wrs   r   rv   rE   mean_end_radiuss     r4   ry   z,validate_bounded_cylinder.<locals>.<genexpr>7  s     BfC()B   c              3  :   K   | ]  }t        |z
          y wrs   r   r  s     r4   ry   z,validate_bounded_cylinder.<locals>.<genexpr>=  s     IfC()Ir  皙?T)r   re   rC   rD   r   r   r   r   r   rE   r   r   r   r   zipr   r   r   r   ) r   r2  r\  r  radius_tolerance_factormin_bin_fractionmax_radius_std_ratioplane_parallel_toleranceend_radius_ratio_tolerancer   r4  r   r5  r6  r_  radius_mean
radius_stdr*   axial_valuesrw   negative_binpositive_binr  
end_planes	end_radii	point_binr   r   r  triplet
end_radiusr  s                                   @r4   validate_bounded_cylinderr    s/    %]3H
8}q /%***%..E<P<P1QQQ==C./ ;)@@%,,QUBUV/0K-.J
;%&)99CS)),@@(#FNVWFVf_))%*>*>?WLW$',$? 65519L  %(,$? 655A:L  <1L 1A 5LCM),<<|s8},/??!$::O.0JI"L1 %	%9)%D"l*9lLQ		N_,I&3)>><67-i8?.8
s!2$!%& 	C
1a $$Z]1%567#=>
"	# "9-O#B	BB_T
$	%  	I8HII
	
	 s Xs   %K;K#	K#K)-K)c                	   t        | |      }g }t        |d      D ]l  \  }}|j                  j                  |j                        }|j                  dk  r;|j                  |j                  |j                  ft        |      f       n |syt        |D 	
cg c]  \  }	}
|
	 c}
}	dd      }|syt        |d       }t        ||      D cg c]
  \  }}|s	| }}}t        |D 	ch c]  \  }}	|D ]  }|  c}}	}      }t        |      dk  ry| D cg c]  }|j                  |v s| }}t        t        |D 	
cg c]  \  }	}
|
	 c}
}	            }t        |      \  }}t        ||      }g }g }g }|D ]  }|j                  j                  |      |j                  j                  |      f}t!        |d	   dz  |d
   dz  z         }|dk  rY|j                  |j"                  j                  |      |j"                  j                  |      f       |j                  |d	   |z  |d
   |z  f       |j                  |j"                  j                  |              t        |      dk  ryt        t        ||      d      D  !cg c]  \  \  }}\  } }!t%        ||| |!      x}"	 |"  }#} }}}!t        |#      dk  ryt'        |#|dz  d      }$|$syt        |$d       }%t        |#|%      D &cg c]
  \  }&}|s	|& }'}&}t)        |'D &cg c]  }&|&d	   	 c}&      t)        |'D &cg c]  }&|&d
   	 c}&      f}(||(d	   z  ||(d
   z  z   |t)        |      z  z   })g }*g }+g },|D ]  }|j"                  |)z
  }-|-||-j                  |      z  z
  }.|.j                  t*        k  r=|.j-                         }/|*j                  |.j                         |/j                  |j                        }0|,j                  |0       |+j                  dt/        |0      z
          |*syt)        |*      }1|1t*        k  s|1|kD  ryt1        |*      }2|+rt)        |+      nd}3|2t        |1t*              z  dkD  s|3dkD  ryt3        dt5        |      |)||1t)        |,      d	k\  rd
|3      S d|3      S c c}
}	w c c}}w c c}}	}w c c}w c c}
}	w c c}!} }}w c c}}&w c c}&w c c}&w )z+Fit a cylinder patch to local face samples.r   MbP?Nr[     r   c                >    t        t        j                  |             S rs   r'   r   count_nonzeromasks    r4   r   z$fit_local_cylinder.<locals>.<lambda>_  s    C0@0@0F,G r3   r   r   r   c                >    t        t        j                  |             S rs   r  r  s    r4   r   z$fit_local_cylinder.<locals>.<lambda>  s    B<L<LT<R8S r3   r   r   g333333?皙?rm  r   rn  )r   r   r+   r   r   r   r(   r   r   r   r  r   re   r   r   r   r   r*   _intersect_2d_linesr   r   r   r   r   r   rB   rq  )4r  r\  max_pair_face_countmax_intersection_face_countaxis_samplesrecordssample_asample_br   _r   masks	best_maskrecordkeepcluster_recordsr  r(   r9   rJ  
face_grouprD   r'  r  center_samples	points_2d
normals_2daxis_coordsprojected_normalprojected_lengthr   r  r   r  intersectionintersections_2dpoint_masksbest_point_maskr   best_points	center_2drC   ru  rv  signed_alignmentsr5  r6  radial_directionsigned_alignmentrE   r  r@   s4                                                       r4   fit_local_cylinderr  E  s6    )2EFL46G*<; 
(%%hoo6<<4nnhnn-/Fu/MN	
	
 !'./|q)/TqE EGHI25gy2IR,&$TvROR!0FF:7AgFUFFL <1'.OV&,,,2N&OJO,ODLAyiDEN  /LE5*:7RSNIJK  >"MM--e4fmm6G6G6NO 0 3q 8;KA;NRS;S STt#&--++E2FMM4E4Ee4LMN #&66 #&66	
 	6==,,^<=> 9~ ?K	:&?
 :"Wk$:Wk 0g{ L 	 	  q !kD0aK ++STO!$%5!G%4K  	K85eAh89K85eAh89I
 		!
)A,
	
<4
4	5  EI 	6+.6::n+EEE==C!,,.V]]#+//>  !12s#3445	6 % F},U#J*3|I&HC$$t+x$|,%%&78A=A  DF  	0 SF
 PD0* 98sH   R!

R'R'+R-R4.R4R9#R?
8
SSS.S
c                    |d   |d   z  |d   |d   z  z
  }t        |      t        k  ry|d   | d   z
  }|d   | d   z
  }|d   |z  |d   |z  z
  |z  }| d   ||d   z  z   | d   ||d   z  z   fS )z:Intersect two 2D lines expressed as point-direction pairs.r   r   N)r   r   )r   r  r   r  determinantdelta_xdelta_yscale_as           r4   r  r    s     a.;q>1KN[QR^4SSK
;3aj71:%Gaj71:%G1~'+a.7*BBkQG
W{1~--
W{1~-- r3   c                    t        | |      S )z?Detect high-confidence planar regions from cleaned proxy faces.)r  )meshr   s     r4   detect_planes_from_clean_proxyr    s     +4<<r3   c                   | j                         j                  }g }t        t        t	        j
                                    |xs
 t               z
  }	t        |	|      D ]  }
|
D cg c]	  }||	v s| }
}t	        |
      |k  r&t	        |
      dk(  rt        fd|
D              |dz  |z  k  rRt        |
|      }|b|j                  |       |	j                  |j                          |S c c}w )zHDetect planar regions by grouping connected faces with matching normals.)ry  r   c              3  P   K   | ]  }j                   |   j                    y wrs   )rQ   r  )rv   rk   r   s     r4   ry   z-detect_planes_from_normals.<locals>.<genexpr>  s"     Vj
  ,11Vs   #&)r  r  r`   rd   re   rQ   r|  r   r  r   difference_updater9   )r  r   blocked_indicesry  r  min_component_sizemin_two_face_area_factorr\  plane_patchesrQ  component_indicesrk   r   s    `           r4   detect_planes_from_normalsr    s    ##%..K&(ME#j../01_5MNI8# 8 *;
%jI>UJ
 
  !$66 !Q&VDUVVA~!99: ":/@+N=U###E$6$67'8* !
s   )	C43C4c           	         t        | |      }|r t               j                  d |D         n	t               }t        | ||||||      }g ||S )z Detect planar regions in a mesh.c              3  4   K   | ]  }|j                     y wrs   r  r  s     r4   ry   z detect_planes.<locals>.<genexpr>       JUe((Jr  )r  ry  r  r	  r
  )r  r`   r  r  )	r  r   ry  r  r	  r
  clean_plane_patchesclean_plane_indicesnormal_plane_patchess	            r4   detect_planesr    so     9zJ  	J6IJKU 
 6+#5-!9 9 8#788r3   c                   t        |       }t               }|D ]  }t        || ||      }|D cg c]  }t        j                  |      s| }	}t        |	      dk  rEt        |	      dk(  r;|	d   |	d   }}
|
t        k  rg|t        kD  r||
z  nd}||k\  r|j                  |       |	d   |	d   |	d   }}}
|
t        k  s	|t        k  rt        ||
z
        |z  }|t        kD  r||z  nd}||k  s||k\  s|j                  |        |S c c}w )z?Broadly classify which faces are plausible cylinder candidates.r   r   r   r   )	r  r`   rN  r   rA  re   r   rc   r   )r   r   pair_similarity_toleranceanisotropy_ratio_thresholdrI  cylinder_like_indicesrk   rB  rw   rC  rD  rF  
anisotropyrE  pair_similaritys                  r4   _cylinder_like_face_indicesr  &  s5    9DN&)e% 2
*	
	 &/EE"++e2D%EEv;?v;!q	6!9Ccz'+czsJ77%))*5F1Ivay$S#:cCi.3.#'#:TCZ3
8888!%%j1728 ! + Fs   D
D
c           	        t        |       dk  ryg }g }| D ]Q  }t        |j                        \  }}}	|j                  |||	dg       |j                  ||z  ||z  z   |	|	z  z           S t        j
                  j                  t	        j                  |t              t	        j                  |t              d      \  }
}}}|dk  ry|
\  }}}}t        | dz  | dz  | dz        }|j                  |      |z
  }|t        k  ryg }g }| D ]|  }||j                  z
  }|j                  }|t        k  r(|j                  |       |j                  dt        |j                         j                  |j                              z
         ~ t        |      dk  ryt!        |      t        |      z  t        k  s|kD  ryt        t	        j"                  |            }|rt!        |      t        |      z  nd}t!        fd|D              t        |      z  }|t%        t              z  |kD  ry||kD  ryt'        d	t)        d
 | D              ||      S )z)Fit a sphere patch to local face samples.r  Nr   r   )rcondr   r   c              3  :   K   | ]  }t        |z
          y wrs   r   )rv   rw   rE   s     r4   ry   z#fit_local_sphere.<locals>.<genexpr>  s     :53uv~&:r  ro  c              3  4   K   | ]  }|j                     y wrs   )r(   )rv   rJ  s     r4   ry   z#fit_local_sphere.<locals>.<genexpr>  s     Bv||Br  rp  )re   r{   r*   r   r   r   lstsqr   r;   r   r   r   r   r   r   r+   r   stdr   rM   rq  )r  r\  radius_std_ratio_limitnormal_error_limitr   rhsrJ  xyzcoeffs
_residualsrank	_singularabcdr*   	radius_sqru  r  r6  radial_lengthr  r^  r@   rE   s                              @r4   fit_local_spherer2  P  sL    7|aD
C -&1aQ1cN#

QUQU]QU*+,-
 +-))//


4u%


3e$ +: +'FJi
 axJAq!QQBHqb3hS1F

6"Q&ICEM P&--'C]#S3v'8'8':'>'>v}}'M#NNOP 5zA~Z#e*$F},rvve}%J>K3}%M(::QTL:E::SZGHC$$'==((B'BB r3   c                X    t        | |      }|y|j                  |j                  |z  k  S )z;Reject cylinders that are better explained by a sphere fit.F)r2  r@   )r  cylinder_patchr\  residual_factorsphere_patchs        r4   _cylinder_patch_looks_sphericalr7    s5     $G[9L  N$;$;o$MMMr3   c           	        t        | |||      }t        |j                        |k  ryt        |j                        D cg c]  }| j                  |    }}t        ||      }	|	Vt        | |	||      }t        |j                        |k  ryt        |j                        D cg c]  }| j                  |    }}|r2| j                  t        |j                              }
t        ||
|      syt        |||      ryt        dt        |j                        |j                  |j                  |j                  |j                  |j                        S c c}w c c}w )z5Grow, refit, and validate a cylinder patch candidate.Nrm  rn  )rj  re   r9   r   rS   r  rn   r  r7  rB   rq  rC   rD   rE   rF   r@   )r   r   rQ  r\  r	  require_bounded_validationgrown_patchr(   grown_samplesrefit_patchr2  s              r4   _finalize_cylinder_patchr=    sk    $	K ;##$'99 5;;;S;S4T+0
&M  %]K@K'	
 {''(+==8>{?W?W8X
/4J##E*
 
 ""++F;3K3K,LM(m[Q	([	Q{778))"11!!++%% 1
s   E#Ec           
        t        t        t        |j                                    |z
  }| j	                         j
                  }	g }
t        |||      D ]  }|j                  |      }t        j                  |      D ]  }t        ||      D cg c]  }||v r|
 }}t        |      |k  r/|D cg c]  }|j                  |    }}t        ||	      }|Zt        ||||	|d      }|n|
j                  |       |j                  |j                            t               }t#        t%        ||||            }|D ]  }||vs||v r|j'                  |       t)        ||||      }t        |      |k  r:|D cg c]  }|j                  |    }}t        ||	      }|et        ||||	|d      }|y|
j                  |       |j                  |j                           t+        ||
|	      S c c}w c c}w c c}w )z%Detect cylindrical regions in a mesh.)r  T)r9  )r  r  F)r`   rd   re   rQ   r  r  r
  rn   r   	sew_facesr  rS   r  r=  r   r  r9   r   r  rc   r  r  )r  r   r  area_tol_digitsr  r  local_seed_depthr	  rQ  r\  r  
area_groupgroup_facesr  rk   r  r(   component_samplesr   finalized_patchtried_seed_indicesclfir   local_indiceslocal_sampless                            r4   detect_cylindersrJ    sm    E#j../01OCI##%..K#%G,I/ F
 !))*54 	FI #?z9"U!* ! !
 $%(::<M!38
''.! ! ''8+FE}6"+/O &NN?+''(D(DE3	F	F> $'5#&?'A		
D  B
Y&*8J*Jz*"	
 } 22ERSE007SS"=+>=2',
 "'##O$@$@A7B: &j';GGA!!P Ts   G41G9>G>c                *   g }t        | d       D ]  }d}|D ]  }|j                  |j                  z
  j                  }t        |j                  |j                  z
        t        |j                  |j                  t              z  }	t        |j                  |j                  z        t        t        t        |j                        t        |j                              d      z  }
||k  s|	|k  s|
|k\  sd} n |r|j                  |        |S )zDRemove overlapping sphere detections that describe the same surface.c                F    | j                   t        | j                         fS rs   )r@   re   r9   )r  s    r4   r   z,suppress_duplicate_spheres.<locals>.<lambda>9  s    

S=P<P/Q r3   r   Fr   T)r   r*   r   r   rE   r   r   re   r9   r.  r   )r  center_tolerancer  overlap_ratio_tolerancekeptr   	duplicate
kept_patchcenter_shiftradius_deltaoverlaps              r4   suppress_duplicate_spheresrU  0  s    !D%QR 	 	J!LL:+<+<<DDLu||j.?.??@3j//D L %,,z/F/FFG#C**+S1H1H-IJK G
  00 $::66 		  KK'( Kr3   c                   t        t        t        |j                                    |z
  }| j	                         j
                  }g }t        |||      D ]  }|j                  |      }	t        j                  |	      D ]  }
t        ||
      D cg c]  }||v r|
 }}t        |      |k  r/|D cg c]  }|j                  |    }}t        ||      }|Zt        ||||      }t        |j                        |k  r|j                  t!        dt#        |j                        |j$                  |j&                  |j(                               |j+                  |j                           t-        ||dz        S c c}w c c}w )z#Detect spherical regions in a mesh.)rU  ro  rp  r[  )rM  )r`   rd   re   rQ   r  r  rW  rn   r   r?  r  rS   r2  rj  r9   r   rM   rq  r*   rE   r@   r  rU  )r  r   r  rU  r	  rQ  r\  r  radius_grouprC  r  rk   r  r(   rD  r   r:  s                    r4   detect_spheresrX  P  s    E#j../01OCI##%..K!#G41 %B
 !)),74 	BI #?z9"U!* ! !
 $%(::<M!38
''.! ! %%6DE}+	K ;++,/AANN!!*;+C+C!D&--&--(11 ''(@(@A?	B%BN &$t+ A!!s   F 2Fg-C6?c                B    t        |      t        k  r|  dS |  d|ddS )z4Format a plane offset expression for generated code. * z.offset(0.6g) * r   	TOLERANCE)basevals     r4   _offsetra    s/     s8i/dV3<TvXc$Zt5TTr3   c                d    t        |       t        k  rt        |      t        k  rdS d| dd|ddS )z=Format an in-plane translation expression for generated code. Pos(r[  , r\  r]  )r,  r-  s     r4   _posrf    sB    
 FY3q6I#5 	 Ad82aXT*r3   r   zPlane.XYc                    |S rs   r2   r%  r&  r'  s      r4   r   r          r3   c                
    | |fS rs   r2   rh  s      r4   r   r     
    A r3   r   zPlane.YXc                    |S rs   r2   rh  s      r4   r   r     ri  r3   c                
    || fS rs   r2   rh  s      r4   r   r     rk  r3   zPlane.ZXc                    |S rs   r2   rh  s      r4   r   r     ri  r3   c                
    || fS rs   r2   rh  s      r4   r   r     rk  r3   zPlane.XZc                    |S rs   r2   rh  s      r4   r   r     ri  r3   c                
    | |fS rs   r2   rh  s      r4   r   r     rk  r3   zPlane.YZc                    | S rs   r2   rh  s      r4   r   r     ri  r3   c                
    ||fS rs   r2   rh  s      r4   r   r     rk  r3   zPlane.ZYc                    | S rs   r2   rh  s      r4   r   r     ri  r3   c                
    ||fS rs   r2   rh  s      r4   r   r     rk  r3   c                    t        j                  d|       }|r|j                  d      nd}t        j                  d|       }|rt	        |j                  d            nd}||fS )z:Extract a stable sort key from generated plane-based code.z(Plane\.\w+|Location)r   ZZZz\.offset\(([^)]+)\)r   )rematchr  searchr;   )smnameoffset_mr5  s        r4   plane_sort_keyr    sZ     	)1-A1771:D yy/3H)1U8>>!$%sF&>r3   c           
     
   g }| D ]  }|j                   xt        j                  k(  rZ t        |      }|j                  t        fdt        D        d      }|!|\  }}}}}	}
t        |j                  ||      }|j                  |      }|j                         j                  t        j                        d   j                  t        j                        d   }t        |j                  |j                  d      }|j!                  |      }|j#                  |      }t%        |t              st'        d      |}|j)                         }|j*                  j                  |j*                  j                  }}t-        |t/        ||t0        j2                        z  d      }|j5                  |      }|rt%        |d   t6              s|j9                  d       t;        |d   j<                  |j<                  z
        t>        kD  r||}}d	 |j                  D        \  }}}|(tA         	|||      z        }tC         
|||       }nd
}dtE        |      dd}||z   d|dd|ddz   }|j9                  |       }xt        jF                  k(  r% t        |jH                        }|j                  t        fdt        D        d      }|!|\  }}}}}	}
t        |j                  ||      }|j)                         }|j                  |      }|j)                         j*                  jJ                  }d |j                  D        \  }}}|(tA         	|||      z        }tC         
|||       }nd|jL                  dd}d
}||z   d|jN                  dd|ddz   }|j9                  |       t        jP                  k(  sd|jR                  dd|jN                  dd}|j9                  |        |S )z=Convert detected primitive faces into build123d code strings.c              3  `   K   | ]%  }j                  |d          dt        z
  kD  s"| ' ywr   r   Nr   r^  rv   r.  r"  s     r4   ry   z!shapes_to_code.<locals>.<genexpr>  (     R1%))AaD/A	M2QQR   #..N)r!  r"  r   z-Expected Plane.shift_origin() to return Planer,  zplanar rectanglez$Error in generating planar rectanglec              3  <   K   | ]  }t        |t                y wrs   ru   r   rv   r/  s     r4   ry   z!shapes_to_code.<locals>.<genexpr>       DA5J/D   rc  r   r[  rZ  z
Rectangle(re  z, align=Align.MIN)c              3  `   K   | ]%  }j                  |d          dt        z
  kD  s"| ' ywr  r  r  s     r4   ry   z!shapes_to_code.<locals>.<genexpr>&  r  r  c              3  <   K   | ]  }t        |t                y wrs   r  r  s     r4   ry   z!shapes_to_code.<locals>.<genexpr>/  r  r  zFace.extrude(Circle(z).edge(), (0, 0, z))rd  z) * Sphere(z').faces().filter_by(GeomType.SPHERE)[0])*	geom_typer   PLANEr   r"  next_PLANE_CONFIGSr:   to_local_coordsr   group_byr   r  sort_byr  r!   from_local_coordsshift_originr   r   r  sizer   r   r   MIN	intersectr   r   r   r  r^  ra  rf  r   r1  axis_of_rotationr  locationrE   SPHEREposition)
primitives
code_lines	primitiveplncfgz_vecx_vecpln_namesign	offset_fnpos_fncenter_oriented_rectlocal_originglobal_originshifted_planebboxwheightrectcommonr%  r&  r'  pln_strpos_strrect_str	local_cylcylinder_str
sphere_strr"  s                                @r4   shapes_to_coder    s    J [.	!!I&		 RRTX ?FICE5(D)V

%uEC'*':':9'E$(113Xdff%a)WTVV_Q( 
  &lnnlnnaH # 5 5l C # 0 0 ?!-7&'VWW#+88: IIKK6)AvUYY??AS 	2Zq	4%@%%&LMvay~~	67)C !6AFDD1a?%hyAq7I0IJG"F1aO4G G (#t(<C@G "1T("VDM9KLM 
 !!(+"""I667		RRTX ?FICE5(D)V

%uEC --///	:	"//16688DD1a?%hyAq7I0IJG"F1aO4G (););D(AEG G,Y-=-=d,CCTU[\`Taacde 
 !!,/9--d3 4'..t44[]  !!*-w[.z r3   c                   t         j                  |       }t        | |      }|r t               j                  d |D         n	t               }t        | ||      }|r t               j                  d |D         n	t               }t        | |||z        }|r t               j                  d |D         n	t               }t        | |||z  |z        }g ||||}	g }
t               }|	D ]  }|j                  t        |j                              }|j                  |j                         |j                  dk(  rt        |      }n(|j                  dk(  rt        ||      }nt        ||      }|
j!                  |        |j                  t        t        t#        t%        |j&                                    |z
              }t)        |
      t        t+        |
      d       }|rt-        t.        t+        |       \  }
ng g c}
r]t%              }t1        t3        |            }g d	}t#        |      D ],  t5        fd
|D        d      }| d| dd    <   . t7        |
      t7        |      fS )a  Detect analytic primitives in a mesh and return faces, leftovers, and code.

    This is the user-facing entry point for STL-to-BREP reconstruction. The
    mesh is indexed first so face geometry and adjacency can be reused
    throughout the pipeline.

    Detection proceeds in stages:

    1. High-confidence planes are found first from cleaned proxy faces.
    2. Spheres are found next from broad radius-signature classification,
       connected or sewn regions, local sphere fitting, and region growth.
    3. Cylinders are detected from area-grouped sewn regions and local
       cylinder seeds, then grown, refit, and validated.
    4. Remaining coplanar connected components are detected as fallback
       planes.

    Each accepted patch is converted into a build123d Face, unmatched mesh
    faces are returned as leftovers, and the generated code strings are sorted
    in the same order as the returned primitives.
    c              3  4   K   | ]  }|j                     y wrs   r  r  s     r4   ry   z$detect_primitives.<locals>.<genexpr>e  r  r  c              3  4   K   | ]  }|j                     y wrs   r  r  s     r4   ry   z$detect_primitives.<locals>.<genexpr>l  s     EUe((Er  c              3  4   K   | ]  }|j                     y wrs   r  r  s     r4   ry   z$detect_primitives.<locals>.<genexpr>w  s     GUe((Gr  )r  r)  rm  c                    t        | d         S )Nr   )r  r  s    r4   r   z#detect_primitives.<locals>.<lambda>  s    Q0 r3   r   ))r   r)Circler.  )r   r{  c              3  8   K   | ]  \  }}|   v s|  y wrs   r2   )rv   r   r   r  r   s      r4   ry   z$detect_primitives.<locals>.<genexpr>  s      LUjm5KLs   N0r/  z = )rO   r^   r  r`   r  rX  rJ  r  rn   r   r9   rg   r7   r*  r<  r>  r   rd   re   rQ   r  r  maprW   r
   r   r  r    )r  r   r  r  sphere_patchessphere_indicescylinder_patchescylinder_indicesr  r  r  rr  r   r2  primitive_face	leftoversprimitive_code_pairs	num_lines
num_digits
SHAPE_KEYS	code_typer  r   s                        @@r4   detect_primitivesr  H  s   0 %%d+J 9zJ  	J6IJKU  $D*6IJN  	EnEFU  (n,  	G6FGHU  6+n<?OO$	$	$ 
$ 
	$G  JG *"++F53E3E,FGu))*
 :: -e4NZZ:%0FN.umDN.)* ##F3uS9I9I5J/K+Lw+V$WXI
+J!J
#0 !$T30D+E!F
J!#R
J 
O	%	*+
K
y! 	OALzLdI  )k!Aj\?);3z!}oNJqM		O Z )I"6
BBr3   )	   )r|   r   rx   r'   rJ   ztuple[float, float, float])r   Sequence[Vector]rJ   
np.ndarray)rf   zSequence[float]rJ   r;   )r   r  rJ   r   )r   Sequence[Sequence[float]]rJ   r  )rf   zSequence[T]r   r'   rJ   zlist[T])r   r  r   r;   r   r'   rJ   list[np.ndarray])rJ   r#   )r)   r   rJ   z&tuple[tuple[float, float, float], ...])rw   r   r   strrJ   r   )r+   r   rJ   tuple[Vector, Vector])r   r  r   r   r   r   rJ   zlist[float])r   r  rJ   z$tuple[Vector, Vector, Vector] | None)r   r  r   r;   r   r'   rJ   r  )r   r   r   r   r   r   rJ   float | None)rQ   Sequence[Face]rJ   zlist[Vector])r|   Vector | Sequence[float]rJ   r   )r   r  rJ   r   )r   r  rJ   r  )
r   rO   r   r'   r   set[int]r   r'   rJ   	list[int])   )r   rO   r   r  r  r'   rJ   list[list[int]])r   rO   rJ   r  )r   rO   rJ   #dict[int, list[tuple[int, Vector]]])r   r6   rJ   r   )r   rB   r2  r  rJ   r   )r   rM   r2  r  rJ   r   )rB  tuple[float, ...]rJ   r;   )
rI  r  r   rO   rk   r'   r   r  rJ   r  )r   rO   r9   r  rJ   r  )r   )r   rO   r   r  rU  r;   rJ   r  )rJ  r&   r   rB   r\  r;   rJ   r  )rJ  r&   r   rM   r\  r;   rJ   r  )r   )rf  r;   rJ   bool)
r   rO   r   rB   r   r  r\  r;   rJ   rB   )
r   rO   r   rM   r   r  r\  r;   rJ   rM   )
r   rO   r   CylinderPatch | SpherePatchr   r  r\  r;   rJ   r  )r   )r   rO   r   r  ry  r'   rJ   r  )r  Mb`?r  r  r        ?)r   rO   r  r;   r  r;   r  r;   r  r;   r  r'   r  r;   rJ   list[PlanePatch])~jth?rY  )r   rO   r9   r  r\  r;   r  r;   r  r;   rJ   zPlanePatch | None)r  r   r  r   rJ   r;   )
r   r   r  r   r   r   r  r   rJ   r;   )g{Gz?gQ?Q?)r   rO   r  zSequence[CylinderPatch]r\  r;   r  r;   r  r;   r  r;   rJ   list[CylinderPatch])gMbp?rY  ffffff?{Gz?rZ  r  )r   rB   r2  r  r\  r;   r  r;   r  r;   r  r;   r  r;   r  r;   r  r;   rJ   r  )@   r  )
r  zSequence[FaceSample]r\  r;   r  r'   r  r'   rJ   CylinderPatch | None)
r   tuple[float, float]r  r  r   r  r  r  rJ   ztuple[float, float] | None)r   rO   rJ   r  )Nr   r  r   r  )r   rO   r  zset[int] | Nonery  r'   r  r;   r	  r'   r
  r;   rJ   r  )r   r  r   r  )r   rO   ry  r'   r  r;   r	  r'   r
  r;   rJ   r  )r        ?)
r   rO   r   r  r  r;   r  r;   rJ   r  )r  r  )
r  rR   r\  r;   r"  r;   r#  r;   rJ   zSpherePatch | None)r  )
r  rR   r4  rB   r\  r;   r5  r;   rJ   r  )r   rO   r   rB   rQ  r  r\  r;   r	  r'   r9  r  rJ   r  )r  r  r  r   r  )r   rO   r  r  r@  r'   r  r;   r  r;   rA  r'   r	  r'   rJ   r  )g?r  )
r  zSequence[SpherePatch]rM  r;   r  r;   rN  r;   rJ   list[SpherePatch])r  r  )
r   rO   r  r  rU  r;   r	  r'   rJ   r  )r_  r  r`  r;   rJ   r  )r,  r;   r-  r;   rJ   r  )r{  r  rJ   r{   )r  zIterable[Shape]rJ   z	list[str])r  r   rJ   z2tuple[ShapeList[Face], ShapeList[Face], list[str]]){r0   
__future__r   r  rx  collectionsr   collections.abcr   r   dataclassesr   	itertoolsr   mathr	   r
   r   r   typingr   r   r   r   r   numpyr   sklearn.clusterr   build123d.build_enumsr   r   build123d.geometryr   r   r   r   r   r   build123d.objects_partr   r   build123d.objects_sketchr   build123d.topologyr   r   r    r!   r   r"   r{   r;   r#   r1   r&   r6   rB   rM   DetectedPatchrO   r}   r   r   r   r   r   r   r   r   rb   r[   r   r   r   r   r   r   r   rY   r   r   r  r
  r  r  r*  r<  r>  rG  rN  rS  rW  r`  rb  rg  rj  r|  r  r  r  r  r  r  r  r  r  r  r  r  r2  r7  r=  rJ  rU  rX  r^  ra  rf  r  r  r  r  r2   r3   r4   <module>r     s
  'R #  	 # . ! " ( ( = =  " 1 M M 3 . = =
CL5u!45uUE5=P7QQR R $   $   $: : :" $   ]*[8 0= 0= 0=h;J%9L +
WY%Y,1Y@CYY
BOO,2OBHOO

)
YY$)Y8;YY;;$;/5;;#5	  	
 : 
;
;
; 
; 	
; !!(!JN@T$7$$ $ 	$
 $.555 5> #&GGG  G 	G2KK,K;@KK"KK*K9>KK 
 
  	
  
 
  	
  
@@&@ @ 	@
 !@N 555 5 	5. #$)#(%*"%II I "	I
 !I #I I  I I` %*"))) ) "	)
 ) )X1      	 
  , #'$$(NN$N N  	N
 N "N Nj %*%)""&&*(,\\!\ \ "	\
 #\ \  \ $\ !&\ 
\D  "')	|!|| | "%	|
 |~ $ ! %	
  (== = (,$)&*$$ %$ 	$
 "$ $ $$ $T $)&*99 9 "	9
 9 $9 9B (,(+	'!'!'!  %'! !&	'!
 '!Z %( $	@@@ "@ 	@
 @N "	NN!N N 	N
 
N333 3 	3
 3 !%3 3t '+(+UHUH UH 	UH
  %UH !&UH UH UH UHx %(%(	" " #	
 H #&77 7  	7
 7 7v 	U 	q!Qq!Q	 	q!Rq!Q
 	q!Qq!Q	 	q"aq!Q
 	q!Qq!Q	 	r1aq!Q
S1haJjC
jC7jCr3   