@@ -4183,7 +4183,7 @@ def offset(p1=None, w=0, v=0) -> openstudio.Point3dVector:
41834183
41844184 offset = offset .get ()
41854185 offset .reverse ()
4186- return to_p3Dv (list (t * offset ))
4186+ return p3Dv (list (t * offset ))
41874187 else : # brute force approach
41884188 pz = {}
41894189 pz ["A" ] = {}
@@ -4407,6 +4407,7 @@ def outline(a=[], bfr=0, flat=True) -> openstudio.Point3dVector:
44074407
44084408 try :
44094409 bfr = float (bfr )
4410+ if bfr < 0.0254 : bfr = 0
44104411 except :
44114412 oslg .mismatch ("buffer" , bfr , float , mth )
44124413 bfr = 0
@@ -4425,7 +4426,6 @@ def outline(a=[], bfr=0, flat=True) -> openstudio.Point3dVector:
44254426
44264427 vtx = poly (a [0 ])
44274428 if not vtx : return out
4428- if bfr < 0.0254 : bfr = 0
44294429
44304430 t = openstudio .Transformation .alignFace (vtx )
44314431
@@ -4485,9 +4485,342 @@ def outline(a=[], bfr=0, flat=True) -> openstudio.Point3dVector:
44854485 # Apply buffer, apply ULC (options).
44864486 if bfr > 0.0254 : out = offset (out , bfr , 300 )
44874487
4488- return to_p3Dv (t * out )
4488+ return p3Dv (t * out )
44894489
44904490
4491+ def triadBox (pts = None ) -> openstudio .Point3dVector :
4492+ """Generates a BLC box from a triad (3D points). Points must be unique and
4493+ non-collinear.
4494+
4495+ Args:
4496+ pts (openstudio.Point3dVector):
4497+ A 'triad' - an OpenStudio vector of 3x 3D points.
4498+
4499+ Returns:
4500+ openstudio.Point3dVector:
4501+ A rectangular BLC box (see logs if empty).
4502+
4503+ """
4504+ mth = "osut.triadBox"
4505+ t = None
4506+ bkp = openstudio .Point3dVector ()
4507+ box = []
4508+ pts = nonCollinears (pts )
4509+ if not pts : return bkp
4510+
4511+ if not shareXYZ (pts , "z" ):
4512+ t = openstudio .Transformation .alignFace (pts )
4513+ pts = poly (pts , False , True , True , t )
4514+ if not pts : return bkp
4515+
4516+ if len (pts ) != 3 : return oslg .invalid ("triad" , mth , 1 , CN .ERR , bkp )
4517+
4518+ if isClockwise (pts ):
4519+ pts = list (pts )
4520+ pts .reverse ()
4521+ pts = p3Dv (pts )
4522+
4523+ p0 = pts [0 ]
4524+ p1 = pts [1 ]
4525+ p2 = pts [2 ]
4526+
4527+ # Cast p0 unto vertical plane defined by p1/p2.
4528+ pp0 = verticalPlane (p1 , p2 ).project (p0 )
4529+ v00 = p0 - pp0
4530+ v11 = pp0 - p1
4531+ v10 = p0 - p1
4532+ v12 = p2 - p1
4533+
4534+ # Reset p0 and/or p1 if obtuse or acute.
4535+ if v12 .dot (v10 ) < 0 :
4536+ p0 = p1 + v00
4537+ elif v12 .dot (v10 ) > 0 :
4538+ if v11 .length () < v12 .length ():
4539+ p1 = pp0
4540+ else :
4541+ p0 = p1 + v00
4542+
4543+ p3 = p2 + v00
4544+
4545+ box .append (openstudio .Point3d (p0 .x (), p0 .y (), p0 .z ()))
4546+ box .append (openstudio .Point3d (p1 .x (), p1 .y (), p1 .z ()))
4547+ box .append (openstudio .Point3d (p2 .x (), p2 .y (), p2 .z ()))
4548+ box .append (openstudio .Point3d (p3 .x (), p3 .y (), p3 .z ()))
4549+
4550+ box = nonCollinears (box , 4 )
4551+ if len (box ) != 4 : return bkp
4552+
4553+ box = blc (box )
4554+ if not isRectangular (box ): return bkp
4555+
4556+ if t : box = p3Dv (t * box )
4557+
4558+ return box
4559+
4560+
4561+ def medialBox (pts = None ) -> openstudio .Point3dVector :
4562+ """Generates a BLC box bounded within a triangle (midpoint theorem).
4563+
4564+ Args:
4565+ pts (openstudio.Point3dVector):
4566+ A triangular polygon.
4567+
4568+ Returns:
4569+ openstudio.Point3dVector: A medial bounded box (see logs if empty).
4570+ """
4571+ mth = "osut.medialBox"
4572+ t = None
4573+ bkp = openstudio .Point3dVector ()
4574+ box = []
4575+ pts = poly (pts , True , True , True )
4576+ if not pts : return bkp
4577+ if len (pts ) != 3 : return oslg .invalid ("triangle" , mth , 1 , CN .ERR , bkp )
4578+
4579+ if not shareXYZ (pts , "z" ):
4580+ t = openstudio .Transformation .alignFace (pts )
4581+ pts = poly (pts , False , False , False , t )
4582+ if not pts : return bkp
4583+
4584+ if isClockwise (pts ):
4585+ pts .reverse ()
4586+ pts = p3Dv (pts )
4587+
4588+ # Generate vertical plane along longest segment.
4589+ sgs = segments (pts )
4590+
4591+ mpoints = []
4592+ longest = sgs [0 ]
4593+ distance = openstudio .getDistanceSquared (longest [0 ], longest [1 ])
4594+
4595+ for sg in sgs :
4596+ if sg == longest : continue
4597+
4598+ d0 = openstudio .getDistanceSquared (sg [0 ], sg [1 ])
4599+
4600+ if distance < d0 :
4601+ distance = d0
4602+ longest = sg
4603+
4604+ plane = verticalPlane (longest [0 ], longest [1 ])
4605+
4606+ # Fetch midpoints of other 2 segments.
4607+ for sg in sgs :
4608+ if sg != longest : mpoints .append (midpoint (sg [0 ], sg [1 ]))
4609+
4610+ if len (mpoints ) != 2 : return bkp
4611+
4612+ # Generate medial bounded box.
4613+ box .append (plane .project (mpoints [0 ]))
4614+ box .append (mpoints [0 ])
4615+ box .append (mpoints [1 ])
4616+ box .append (plane .project (mpoints [1 ]))
4617+ box = list (nonCollinears (box ))
4618+ if box .size != 4 : return bkp
4619+
4620+ if isClockwise (box ): box .reverse ()
4621+
4622+ box = blc (box )
4623+ if not isRectangular (box ): return bkp
4624+ if not fits (box , pts ): return bkp
4625+
4626+ if t : box = p3Dv (t * box )
4627+
4628+ return box
4629+
4630+
4631+ def boundedBox (pts = None ) -> openstudio .Point3dVector :
4632+ """Generates a BLC bounded box within a polygon.
4633+
4634+ Args:
4635+ pts (openstudio.Point3dVector):
4636+ A set of OpenStudio 3D points.
4637+
4638+ Returns:
4639+ openstudio.Point3dVector: A bounded box (see logs if empty).
4640+ """
4641+ # str = ".*(?<!utilities.geometry.join)$"
4642+ # OpenStudio::Logger.instance.standardOutLogger.setChannelRegex(str)
4643+
4644+ mth = "osut.boundedBox"
4645+ t = None
4646+ bkp = openstudio .Point3dVector ()
4647+ box = []
4648+ pts = poly (pts , False , True , True )
4649+ if not pts : return bkp
4650+
4651+ if not shareXYZ (pts , "Z" ):
4652+ t = openstudio .Transformation .alignFace (pts )
4653+ pts = p3Dv (t .inverse () * pts )
4654+ if not pts : return bkp
4655+
4656+ if isClockwise (pts ):
4657+ pts = list (pts )
4658+ pts .reverse ()
4659+ pts = p3Dv (pts )
4660+
4661+ # PATH A : Return medial bounded box if polygon is a triangle.
4662+ if len (pts ) == 3 :
4663+ box = medialBox (pts )
4664+
4665+ if box :
4666+ if t : box = p3Dv (t * box )
4667+ return box
4668+
4669+ # PATH B : Return polygon itself if already rectangular.
4670+ if isRectangular (pts ):
4671+ box = p3Dv (t * pts ) if t else pts
4672+ return box
4673+
4674+ aire = 0
4675+
4676+ # PATH C : Right-angle, midpoint triad approach.
4677+ for sg in segments (pts ):
4678+ m0 = midpoint (sg [0 ], sg [1 ])
4679+
4680+ for seg in getSegments (pts ):
4681+ p1 = seg [0 ]
4682+ p2 = seg [1 ]
4683+ if areSame (p1 , sg [0 ]): continue
4684+ if areSame (p1 , sg [1 ]): continue
4685+ if areSame (p2 , sg [0 ]): continue
4686+ if areSame (p2 , sg [1 ]): continue
4687+
4688+ out = triadBox (openstudio .Point3dVector ([m0 , p1 , p2 ]))
4689+ if not out : continue
4690+ if not fits (out , pts ): continue
4691+ if fits (pts , out ): continue
4692+
4693+ area = openstudio .getArea (out )
4694+ if not area : continue
4695+
4696+ area = area .get ()
4697+ if area < CN .TOL : continue
4698+ if area < aire : continue
4699+
4700+ aire = area
4701+ box = out
4702+
4703+ # PATH D : Right-angle triad approach, may override PATH C boxes.
4704+ for sg in segments (pts ):
4705+ p0 = sg [0 ]
4706+ p1 = sg [1 ]
4707+
4708+ for p2 in pts :
4709+ if areSame (p2 , p0 ): continue
4710+ if areSame (p2 , p1 ): continue
4711+
4712+ out = triadBox (openstudio .Point3dVector ([p0 , p1 , p2 ]))
4713+ if not out : continue
4714+ if not fits (out , pts ): continue
4715+ if fits (pts , out ): continue
4716+
4717+ area = openstudio .getArea (out )
4718+ if not area : continue
4719+
4720+ area = area .get ()
4721+ if area < CN .TOL : continue
4722+ if area < aire : continue
4723+
4724+ aire = area
4725+ box = out
4726+
4727+ if aire > CN .TOL :
4728+ if t : box = p3Dv (t * box )
4729+ return box
4730+
4731+ # PATH E : Medial box, segment approach.
4732+ aire = 0
4733+
4734+ for sg in segments (pts ):
4735+ p0 = sg [0 ]
4736+ p1 = sg [1 ]
4737+
4738+ for p2 in pts :
4739+ if areSame (p2 , p0 ): continue
4740+ if areSame (p2 , p1 ): continue
4741+
4742+ out = medialBox (openstudioPoint3dVector ([p0 , p1 , p2 ]))
4743+ if not out : continue
4744+ if not fits (out , pts ): continue
4745+ if fits (pts , out ): continue
4746+
4747+ area = openstudio .getArea (box )
4748+ if not area : continue
4749+
4750+ area = area .get ()
4751+ if area < CN .TOL : continue
4752+ if area < aire : continue
4753+
4754+ aire = area
4755+ box = out
4756+
4757+ if aire > CN .TOL :
4758+ if t : box = p3Dv (t * box )
4759+ return box
4760+
4761+ # PATH F : Medial box, triad approach.
4762+ aire = 0
4763+
4764+ for sg in triads (pts ):
4765+ p0 = sg [0 ]
4766+ p1 = sg [1 ]
4767+ p2 = sg [2 ]
4768+
4769+ out = medialBox (openstudio .Point3dVector ([p0 , p1 , p2 ]))
4770+ if not out : continue
4771+ if not fits (out , pts ): continue
4772+ if fits (pts , out ): continue
4773+
4774+ area = openstudio .getArea (box )
4775+ if not area : continue
4776+
4777+ area = area .get ()
4778+ if area < CN .TOL : continue
4779+ if area < aire : continue
4780+
4781+ aire = area
4782+ box = out
4783+
4784+ if aire > CN .TOL :
4785+ if t : box = p3Dv (t * box )
4786+ return box
4787+
4788+ # PATH G : Medial box, triangulated approach.
4789+ aire = 0
4790+ outer = list (pts )
4791+ outer .reverse ()
4792+ outer = p3Dv (outer )
4793+ holes = openstudio .Point3dVectorVector ()
4794+
4795+ for triangle in openstudio .computeTriangulation (outer , holes ):
4796+ for sg in segments (triangle ):
4797+ p0 = sg [0 ]
4798+ p1 = sg [1 ]
4799+
4800+ for p2 in pts :
4801+ if areSame (p2 , p0 ): continue
4802+ if areSame (p2 , p1 ): continue
4803+
4804+ out = medialBox (openstudio .Point3dVector ([p0 , p1 , p2 ]))
4805+ if not out : continue
4806+ if not fits (out , pts ): continue
4807+ if fits (pts , out ): continue
4808+
4809+ area = openstudio .getArea (out )
4810+ if not area : continue
4811+
4812+ area = area .get ()
4813+ if area < CN .TOL : continue
4814+ if area < aire : continue
4815+
4816+ aire = area
4817+ box = out
4818+
4819+ if aire < CN .TOL : return bkp
4820+ if t : box = p3Dv (t * box )
4821+
4822+ return box
4823+
44914824
44924825def facets (spaces = [], boundary = "all" , type = "all" , sides = []) -> list :
44934826 """Returns an array of OpenStudio space surfaces or subsurfaces that match
0 commit comments