@@ -3610,11 +3610,11 @@ def poly(pts=None, vx=False, uq=False, co=False, tt=False, sq="no") -> openstudi
36103610 if not pln .pointOnPlane (pt ): return oslg .empty ("plane" , mth , CN .ERR , v )
36113611
36123612 t = openstudio .Transformation .alignFace (pts )
3613- at = t .inverse () * pts
3613+ at = list ( t .inverse () * pts )
36143614 at .reverse ()
36153615
36163616 if isinstance (tt , cl ):
3617- att = tt .inverse () * pts
3617+ att = list ( tt .inverse () * pts )
36183618 att .reverse ()
36193619
36203620 if areSame (at , att ):
@@ -3628,7 +3628,7 @@ def poly(pts=None, vx=False, uq=False, co=False, tt=False, sq="no") -> openstudi
36283628 t = openstudio .Transformation .alignFace (att )
36293629
36303630 if t :
3631- a = t .inverse () * att
3631+ a = list ( t .inverse () * att )
36323632 a .reverse ()
36333633 else :
36343634 a = att
@@ -3717,13 +3717,13 @@ def isPointWithinPolygon(p0=None, s=[], entirely=False) -> bool:
37173717 s = poly (s , False , True , True )
37183718 if not s : return oslg .empty ("polygon" , mth , CN .DBG , False )
37193719
3720- n = OpenStudio .getOutwardNormal (s )
3720+ n = openstudio .getOutwardNormal (s )
37213721 if not n : return oslg .invalid ("plane/normal" , mth , 2 , CN .DBG , False )
37223722
37233723 n = n .get ()
37243724 pl = openstudio .Plane (s [0 ], n )
37253725 if not pl .pointOnPlane (p0 ): return False
3726- if not isinstance (entireley , bool ): entirely = False
3726+ if not isinstance (entirely , bool ): entirely = False
37273727
37283728 segs = segments (s )
37293729
@@ -3931,6 +3931,166 @@ def isSquare(pts=None) -> bool:
39313931 return True
39323932
39333933
3934+ def fits (p1 = None , p2 = None , entirely = False ) -> bool :
3935+ """Determines whether a 1st OpenStudio polygon (p1) fits within a 2nd
3936+ polygon (p2). Vertex sequencing of both polygons must be counterclockwise.
3937+ If option 'entirely' is True, then the method returns False if a 'p1' point
3938+ lies along any of the 'p2' polygon edges, or is very near any of its
3939+ vertices.
3940+
3941+ Args:
3942+ p1 (openstudio.Point3d):
3943+ 1st OpenStudio vector of 3D points.
3944+ p2 (openstudio.Point3d):
3945+ 2nd OpenStudio vector of 3D points.
3946+ entirely (bool):
3947+ Whether point should be neatly within polygon limits.
3948+
3949+ Returns:
3950+ bool: Whether 1st polygon fits within the 2nd polygon.
3951+ False: If invalid input (see logs).
3952+
3953+ """
3954+ pts = []
3955+ p1 = poly (p1 )
3956+ p2 = poly (p2 )
3957+ if not p1 : return False
3958+ if not p2 : return False
3959+
3960+ for p0 in p1 :
3961+ if not isPointWithinPolygon (p0 , p2 ): return False
3962+
3963+ # Although p2 points may lie ALONG p1, none may lie entirely WITHIN p1.
3964+ for p0 in p2 :
3965+ if isPointWithinPolygon (p0 , p1 ): return False
3966+
3967+ # p1 segment mid-points must not lie OUTSIDE of p2.
3968+ for sg in segments (p1 ):
3969+ mp = midpoint (sg [0 ], sg [1 ])
3970+ if not isPointWithinPolygon (mp , p2 ): return False
3971+
3972+ if not isinstance (entirely , bool ): entirely = False
3973+ if not entirely : return True
3974+
3975+ for p0 in p1 :
3976+ if not isPointWithinPolygon (p0 , p2 , entirely ): return False
3977+
3978+ return True
3979+
3980+
3981+ def overlaps (p1 = None , p2 = None , flat = False ) -> bool :
3982+ """Returns intersection of overlapping polygons, empty if non intersecting.
3983+ If the optional 3rd argument is left as False, the 2nd polygon may only
3984+ overlap if it shares the 3D plane equation of the 1st one. If the 3rd
3985+ argument is instead set to True, then the 2nd polygon is first 'cast' onto
3986+ the 3D plane of the 1st one; the method therefore returns (as overlap) the
3987+ intersection of a 'projection' of the 2nd polygon onto the 1st one. The
3988+ method returns the smallest of the 2 polygons if either fits within the
3989+ larger one.
3990+
3991+ Args:
3992+ p1 (openstudio.Point3d):
3993+ 1st OpenStudio vector of 3D points.
3994+ p2 (openstudio.Point3d):
3995+ 2nd OpenStudio vector of 3D points.
3996+ flat (bool):
3997+ Whether to first project the 2nd set onto the 1st set plane.
3998+
3999+ Returns:
4000+ openstudio.Point3dVector: Largest intersection (see logs if empty).
4001+
4002+ """
4003+ mth = "osut.overlap"
4004+ face = openstudio .Point3dVector ()
4005+ p01 = poly (p1 )
4006+ p02 = poly (p2 )
4007+ if not p01 : return oslg .empty ("points 1" , mth , CN .DBG , face )
4008+ if not p02 : return oslg .empty ("points 2" , mth , CN .DBG , face )
4009+ if fits (p01 , p02 ): return p01
4010+ if fits (p02 , p01 ): return p02
4011+
4012+ if not isinstance (flat , bool ): flat = False
4013+
4014+ if shareXYZ (p01 , "z" ):
4015+ t = None
4016+ a1 = list (p01 )
4017+ a2 = list (p02 )
4018+ cw1 = isClockwise (p01 )
4019+ if cw1 : a1 .reverse ()
4020+ if flat : a2 = flatten (a2 )
4021+
4022+ if not shareXYZ (a2 , "z" ):
4023+ return invalid ("points 2" , mth , 2 , CN .DBG , face )
4024+
4025+ cw2 = isClockwise (a2 )
4026+ if cw2 : a2 .reverse ()
4027+ else :
4028+ t = openstudio .Transformation .alignFace (p01 )
4029+ a1 = t .inverse () * p01
4030+ a2 = t .inverse () * p02
4031+ if flat : a2 = list (flatten (a2 ))
4032+
4033+ if not shareXYZ (a2 , "z" ):
4034+ return invalid ("points 2" , mth , 2 , CN .DBG , face )
4035+
4036+ cw2 = isClockwise (a2 )
4037+ if cw2 : a2 .reverse ()
4038+
4039+ # Return either (transformed) polygon if one fits into the other.
4040+ p1t = p01
4041+
4042+ if t :
4043+ if not cw2 : a2 .reverse ()
4044+ p2t = to_p3Dv (t * a2 )
4045+ else :
4046+ if cw1 :
4047+ if cw2 : a2 .reverse ()
4048+ p2t = to_p3Dv (a2 )
4049+ else :
4050+ if not cw2 : a2 .reverse ()
4051+ p2t = to_p3Dv (a2 )
4052+
4053+ if fits (a1 , a2 ): return p1t
4054+ if fits (a2 , a1 ): return p2t
4055+
4056+ area1 = openstudio .getArea (a1 )
4057+ area2 = openstudio .getArea (a2 )
4058+
4059+ if not area1 : return oslg .empty ("points 1 area" , mth , CN .ERR , face )
4060+ if not area2 : return oslg .empty ("points 2 area" , mth , CN .ERR , face )
4061+
4062+ area1 = area1 .get ()
4063+ area2 = area2 .get ()
4064+ a1 .reverse ()
4065+ a2 .reverse ()
4066+ union = openstudio .join (a1 , a2 , CN .TOL2 )
4067+ if not union : return face
4068+
4069+ union = union .get ()
4070+ area = OpenStudio .getArea (union )
4071+ if not area : return face
4072+
4073+ area = area .get ()
4074+ delta = area1 + area2 - area
4075+
4076+ if area > CN .TOL :
4077+ if round (area , 2 ) == round (area1 , 2 ): return face
4078+ if round (area , 2 ) == round (area2 , 2 ): return face
4079+ if round (delta , 2 ) == 0 : return face
4080+
4081+ res = openstudio .intersect (a1 , a2 , CN .TOL )
4082+ if not res : return face
4083+
4084+ res = res .get ()
4085+ res1 = res .polygon1 ()
4086+ if not res1 : return face
4087+
4088+ res1 .reverse ()
4089+ if t : res1 = t * res1
4090+
4091+ return to_p3Dv (res1 )
4092+
4093+
39344094def facets (spaces = [], boundary = "all" , type = "all" , sides = []) -> list :
39354095 """Returns an array of OpenStudio space surfaces or subsurfaces that match
39364096 criteria, e.g. exterior, north-east facing walls in hotel "lobby". Note
0 commit comments