@@ -4030,7 +4030,6 @@ def overlap(p1=None, p2=None, flat=False) -> bool:
40304030
40314031 area1 = openstudio .getArea (a1 )
40324032 area2 = openstudio .getArea (a2 )
4033-
40344033 if not area1 : return oslg .empty ("points 1 area" , mth , CN .ERR , face )
40354034 if not area2 : return oslg .empty ("points 2 area" , mth , CN .ERR , face )
40364035
@@ -4066,7 +4065,7 @@ def overlap(p1=None, p2=None, flat=False) -> bool:
40664065 return p3Dv (res1 )
40674066
40684067
4069- def overlapping (p1 = None , p2 = None , flat = False ):
4068+ def overlapping (p1 = None , p2 = None , flat = False ) -> bool :
40704069 """Determines whether OpenStudio polygons overlap.
40714070
40724071 Args:
@@ -4136,6 +4135,360 @@ def cast(p1=None, p2=None, ray=None) -> openstudio.Point3dVector:
41364135 return face
41374136
41384137
4138+ def offset (p1 = None , w = 0 , v = 0 ) -> openstudio .Point3dVector :
4139+ """Generates offset vertices (by width) for a 3- or 4-sided, convex polygon.
4140+ If width is negative, the vertices are contracted inwards.
4141+
4142+ Args:
4143+ p1 (openstudio.Point3dVector):
4144+ OpenStudio vector of 3D points.
4145+ w (float):
4146+ Offset width (absolute min: 0.0254m).
4147+ v (int):
4148+ OpenStudio SDK version, eg '321' for "v3.2.1" (optional).
4149+
4150+ Returns:
4151+ openstudio.Point3dVector: Offset points (see logs if unaltered).
4152+
4153+ """
4154+ mth = "osut.offset"
4155+ vs = int ("" .join (openstudio .openStudioVersion ().split ("." )))
4156+ pts = poly (p1 , True , True , False , True , "cw" )
4157+
4158+ if len (pts ) < 3 or len (pts ) > 4 :
4159+ return oslg .invalid ("points" , mth , 1 , CN .DBG , p1 )
4160+ elif len (pts ) == 4 :
4161+ iv = True
4162+ else :
4163+ iv = False
4164+
4165+ try :
4166+ w = float (w )
4167+ except :
4168+ oslg .mismatch ("width" , w , float , mth )
4169+ w = 0
4170+
4171+ try :
4172+ v = int (v )
4173+ except :
4174+ oslg .mismatch ("version" , v , int , mth )
4175+ v = vs
4176+
4177+ if abs (w ) < 0.0254 : return p1
4178+
4179+ if v >= 340 :
4180+ t = openstudio .Transformation .alignFace (p1 )
4181+ offset = openstudio .buffer (pts , w , CN .TOL )
4182+ if not offset : return p1
4183+
4184+ offset = offset .get ()
4185+ offset .reverse ()
4186+ return to_p3Dv (list (t * offset ))
4187+ else : # brute force approach
4188+ pz = {}
4189+ pz ["A" ] = {}
4190+ pz ["B" ] = {}
4191+ pz ["C" ] = {}
4192+ if iv :
4193+ pz ["D" ] = {}
4194+
4195+ pz ["A" ]["p" ] = openstudio .Point3d (p1 [0 ].x (), p1 [0 ].y (), p1 [0 ].z ())
4196+ pz ["B" ]["p" ] = openstudio .Point3d (p1 [1 ].x (), p1 [1 ].y (), p1 [1 ].z ())
4197+ pz ["C" ]["p" ] = openstudio .Point3d (p1 [2 ].x (), p1 [2 ].y (), p1 [2 ].z ())
4198+ if iv :
4199+ pz ["D" ]["p" ] = openstudio .Point3d (p1 [3 ].x (), p1 [3 ].y (), p1 [3 ].z ())
4200+
4201+ pzAp = pz ["A" ]["p" ]
4202+ pzBp = pz ["B" ]["p" ]
4203+ pzCp = pz ["C" ]["p" ]
4204+ if iv :
4205+ pzDp = pz ["D" ]["p" ]
4206+
4207+ # Generate vector pairs, from next point & from previous point.
4208+ # :f_n : "from next"
4209+ # :f_p : "from previous"
4210+ #
4211+ #
4212+ #
4213+ #
4214+ #
4215+ #
4216+ # A <---------- B
4217+ # ^
4218+ # \
4219+ # \
4220+ # C (or D)
4221+ #
4222+ pz ["A" ]["f_n" ] = pzAp - pzBp
4223+ if iv :
4224+ pz ["A" ]["f_p" ] = pzAp - pzDp
4225+ else :
4226+ pz ["A" ]["f_p" ] = pzAp - pzCp
4227+
4228+ pz ["B" ]["f_n" ] = pzBp - pzCp
4229+ pz ["B" ]["f_p" ] = pzBp - pzAp
4230+
4231+ pz ["C" ]["f_p" ] = pzCp - pzBp
4232+ if iv :
4233+ pz ["C" ]["f_n" ] = pzCp - pzDp
4234+ else :
4235+ pz ["C" ]["f_n" ] = pzCp - pzAp
4236+
4237+ if iv :
4238+ pz ["D" ]["f_n" ] = pzDp - pzAp
4239+ pz ["D" ]["f_p" ] = pzDp - pzCp
4240+
4241+ # Generate 3D plane from vectors.
4242+ #
4243+ #
4244+ # | <<< 3D plane ... from point A, with normal B>A
4245+ # |
4246+ # |
4247+ # |
4248+ # <---------- A <---------- B
4249+ # |\
4250+ # | \
4251+ # | \
4252+ # | C (or D)
4253+ #
4254+ pz ["A" ]["pl_f_n" ] = openstudio .Plane (pzAp , pz ["A" ]["f_n" ])
4255+ pz ["A" ]["pl_f_p" ] = openstudio .Plane (pzAp , pz ["A" ]["f_p" ])
4256+
4257+ pz ["B" ]["pl_f_n" ] = openstudio .Plane (pzBp , pz ["B" ]["f_n" ])
4258+ pz ["B" ]["pl_f_p" ] = openstudio .Plane (pzBp , pz ["B" ]["f_p" ])
4259+
4260+ pz ["C" ]["pl_f_n" ] = openstudio .Plane (pzCp , pz ["C" ]["f_n" ])
4261+ pz ["C" ]["pl_f_p" ] = openstudio .Plane (pzCp , pz ["C" ]["f_p" ])
4262+
4263+ if iv :
4264+ pz ["D" ]["pl_f_n" ] = openstudio .Plane (pzDp , pz ["D" ]["f_n" ])
4265+ pz ["D" ]["pl_f_p" ] = openstudio .Plane (pzDp , pz ["D" ]["f_p" ])
4266+
4267+ # Project an extended point (pC) unto 3D plane.
4268+ #
4269+ # pC <<< projected unto extended B>A 3D plane
4270+ # eC |
4271+ # \ |
4272+ # \ |
4273+ # \|
4274+ # <---------- A <---------- B
4275+ # |\
4276+ # | \
4277+ # | \
4278+ # | C (or D)
4279+ #
4280+ pz ["A" ]["p_n_pl" ] = pz ["A" ]["pl_f_n" ].project (pz ["A" ]["p" ] + pz ["A" ]["f_p" ])
4281+ pz ["A" ]["n_p_pl" ] = pz ["A" ]["pl_f_p" ].project (pz ["A" ]["p" ] + pz ["A" ]["f_n" ])
4282+
4283+ pz ["B" ]["p_n_pl" ] = pz ["B" ]["pl_f_n" ].project (pz ["B" ]["p" ] + pz ["B" ]["f_p" ])
4284+ pz ["B" ]["n_p_pl" ] = pz ["B" ]["pl_f_p" ].project (pz ["B" ]["p" ] + pz ["B" ]["f_n" ])
4285+
4286+ pz ["C" ]["p_n_pl" ] = pz ["C" ]["pl_f_n" ].project (pz ["C" ]["p" ] + pz ["C" ]["f_p" ])
4287+ pz ["C" ]["n_p_pl" ] = pz ["C" ]["pl_f_p" ].project (pz ["C" ]["p" ] + pz ["C" ]["f_n" ])
4288+
4289+ if iv :
4290+ pz ["D" ]["p_n_pl" ] = pz ["D" ]["pl_f_n" ].project (pz ["D" ]["p" ] + pz ["D" ]["f_p" ])
4291+ pz ["D" ]["n_p_pl" ] = pz ["D" ]["pl_f_p" ].project (pz ["D" ]["p" ] + pz ["D" ]["f_n" ])
4292+
4293+ # Generate vector from point (e.g. A) to projected extended point (pC).
4294+ #
4295+ # pC
4296+ # eC ^
4297+ # \ |
4298+ # \ |
4299+ # \|
4300+ # <---------- A <---------- B
4301+ # |\
4302+ # | \
4303+ # | \
4304+ # | C (or D)
4305+ #
4306+ pz ["A" ]["n_p_n_pl" ] = pz ["A" ]["p_n_pl" ] - pzAp
4307+ pz ["A" ]["n_n_p_pl" ] = pz ["A" ]["n_p_pl" ] - pzAp
4308+
4309+ pz ["B" ]["n_p_n_pl" ] = pz ["B" ]["p_n_pl" ] - pzBp
4310+ pz ["B" ]["n_n_p_pl" ] = pz ["B" ]["n_p_pl" ] - pzBp
4311+
4312+ pz ["C" ]["n_p_n_pl" ] = pz ["C" ]["p_n_pl" ] - pzCp
4313+ pz ["C" ]["n_n_p_pl" ] = pz ["C" ]["n_p_pl" ] - pzCp
4314+
4315+ if iv :
4316+ pz ["D" ]["n_p_n_pl" ] = pz ["D" ]["p_n_pl" ] - pzDp
4317+ pz ["D" ]["n_n_p_pl" ] = pz ["D" ]["n_p_pl" ] - pzDp
4318+
4319+ # Fetch angle between both extended vectors (A>pC & A>pB),
4320+ # ... then normalize (Cn).
4321+ #
4322+ # pC
4323+ # eC ^
4324+ # \ |
4325+ # \ Cn
4326+ # \|
4327+ # <---------- A <---------- B
4328+ # |\
4329+ # | \
4330+ # | \
4331+ # | C (or D)
4332+ #
4333+ a1 = openstudio .getAngle (pz ["A" ]["n_p_n_pl" ], pz ["A" ]["n_n_p_pl" ])
4334+ a2 = openstudio .getAngle (pz ["B" ]["n_p_n_pl" ], pz ["B" ]["n_n_p_pl" ])
4335+ a3 = openstudio .getAngle (pz ["C" ]["n_p_n_pl" ], pz ["C" ]["n_n_p_pl" ])
4336+ if iv :
4337+ a4 = openstudio .getAngle (pz ["D" ]["n_p_n_pl" ], pz ["D" ]["n_n_p_pl" ])
4338+
4339+ # Generate new 3D points A', B', C' (and D') ... zigzag.
4340+ #
4341+ #
4342+ #
4343+ #
4344+ # A' ---------------------- B'
4345+ # \
4346+ # \ A <---------- B
4347+ # \ \
4348+ # \ \
4349+ # \ \
4350+ # C' C
4351+ pz ["A" ]["f_n" ].normalize ()
4352+ pz ["A" ]["n_p_n_pl" ].normalize ()
4353+ pzAp = pzAp + scalar (pz ["A" ]["n_p_n_pl" ], w )
4354+ pzAp = pzAp + scalar (pz ["A" ]["f_n" ], w * math .tan (a1 / 2 ))
4355+
4356+ pz ["B" ]["f_n" ].normalize ()
4357+ pz ["B" ]["n_p_n_pl" ].normalize ()
4358+ pzBp = pzBp + scalar (pz ["B" ]["n_p_n_pl" ], w )
4359+ pzBp = pzBp + scalar (pz ["B" ]["f_n" ], w * math .tan (a2 / 2 ))
4360+
4361+ pz ["C" ]["f_n" ].normalize ()
4362+ pz ["C" ]["n_p_n_pl" ].normalize ()
4363+ pzCp = pzCp + scalar (pz ["C" ]["n_p_n_pl" ], w )
4364+ pzCp = pzCp + scalar (pz ["C" ]["f_n" ], w * math .tan (a3 / 2 ))
4365+
4366+ if iv :
4367+ pz ["D" ]["f_n" ].normalize ()
4368+ pz ["D" ]["n_p_n_pl" ].normalize ()
4369+ pzDp = pzDp + scalar (pz ["D" ]["n_p_n_pl" ], w )
4370+ pzDp = pzDp + scalar (pz ["D" ]["f_n" ], w * math .tan (a4 / 2 ))
4371+
4372+ # Re-convert to OpenStudio 3D points.
4373+ vec = openstudio .Point3dVector ()
4374+ vec .append (openstudio .Point3d (pzAp .x (), pzAp .y (), pzAp .z ()))
4375+ vec .append (openstudio .Point3d (pzBp .x (), pzBp .y (), pzBp .z ()))
4376+ vec .append (openstudio .Point3d (pzCp .x (), pzCp .y (), pzCp .z ()))
4377+ if iv :
4378+ vec .append (openstudio .Point3d (pzDp .x (), pzDp .y (), pzDp .z ()))
4379+
4380+ return vec
4381+
4382+
4383+ def outline (a = [], bfr = 0 , flat = True ) -> openstudio .Point3dVector :
4384+ """Generates a ULC OpenStudio 3D point vector (a bounding box) that
4385+ surrounds multiple (smaller) OpenStudio 3D point vectors. The generated,
4386+ 4-point outline is optionally buffered (or offset). Frame and Divider frame
4387+ widths are taken into account.
4388+
4389+ Args:
4390+ a (list):
4391+ One or more sets of OpenStudio 3D points.
4392+ bfr (float):
4393+ An optional buffer size (min: 0.0254m).
4394+ flat (bool):
4395+ Whether points are to be pre-flattened (Z=0).
4396+ Returns:
4397+ openstudio.Point3dVector: ULC outline (see logs if empty).
4398+
4399+ """
4400+ mth = "osut.outline"
4401+ out = openstudio .Point3dVector ()
4402+ xMIN = None
4403+ xMAX = None
4404+ yMIN = None
4405+ yMAX = None
4406+ a2 = []
4407+
4408+ try :
4409+ bfr = float (bfr )
4410+ except :
4411+ oslg .mismatch ("buffer" , bfr , float , mth )
4412+ bfr = 0
4413+
4414+ try :
4415+ flat = bool (flat )
4416+ except :
4417+ flat = True
4418+
4419+ try :
4420+ a = list (a )
4421+ except :
4422+ return oslg .mismatch ("array" , a , list , mth , CN .DBG , out )
4423+
4424+ if not a : return oslg .empty ("array" , mth , CN .DBG , out )
4425+
4426+ vtx = poly (a [0 ])
4427+ if not vtx : return out
4428+ if bfr < 0.0254 : bfr = 0
4429+
4430+ t = openstudio .Transformation .alignFace (vtx )
4431+
4432+ for pts in a :
4433+ points = poly (pts , False , True , False , t )
4434+ if flat : points = flatten (points )
4435+ if not points : continue
4436+
4437+ a2 .append (points )
4438+
4439+ for pts in a2 :
4440+ xs = [pt .x () for pt in pts ]
4441+ ys = [pt .y () for pt in pts ]
4442+
4443+ minX = min (xs )
4444+ maxX = max (xs )
4445+ minY = min (ys )
4446+ maxY = max (ys )
4447+
4448+ # Consider frame width, if frame-and-divider-enabled sub surface.
4449+ if hasattr (pts , "allowWindowPropertyFrameAndDivider" ):
4450+ w = 0
4451+ fd = pts .windowPropertyFrameAndDivider ()
4452+ if fd : w = fd .get ().frameWidth ()
4453+
4454+ if w > CN .TOL :
4455+ minX -= w
4456+ maxX += w
4457+ minY -= w
4458+ maxY += w
4459+
4460+ if not xMIN : xMIN = minX
4461+ if not xMAX : xMAX = maxX
4462+ if not yMIN : yMIN = minY
4463+ if not yMAX : yMAX = maxY
4464+
4465+ xMIN = min (xMIN , minX )
4466+ xMAX = max (xMAX , maxX )
4467+ yMIN = min (yMIN , minY )
4468+ yMAX = max (yMAX , maxY )
4469+
4470+ if xMAX < xMIN :
4471+ return oslg .negative ("outline width" , mth , CN .DBG , out )
4472+ if yMAX < yMIN :
4473+ return oslg .negative ("outline height" , mth , Cn .DBG , out )
4474+ if abs (xMIN - xMAX ) < TOL :
4475+ return oslg .zero ("outline width" , mth , CN .DBG , out )
4476+ if abs (yMIN - yMAX ) < TOL :
4477+ return oslg .zero ("outline height" , mth , CN .DBG , out )
4478+
4479+ # Generate ULC point 3D vector.
4480+ out .append (openstudio .Point3d (xMIN , yMAX , 0 ))
4481+ out .append (openstudio .Point3d (xMIN , yMIN , 0 ))
4482+ out .append (openstudio .Point3d (xMAX , yMIN , 0 ))
4483+ out .append (openstudio .Point3d (xMAX , yMAX , 0 ))
4484+
4485+ # Apply buffer, apply ULC (options).
4486+ if bfr > 0.0254 : out = offset (out , bfr , 300 )
4487+
4488+ return to_p3Dv (t * out )
4489+
4490+
4491+
41394492def facets (spaces = [], boundary = "all" , type = "all" , sides = []) -> list :
41404493 """Returns an array of OpenStudio space surfaces or subsurfaces that match
41414494 criteria, e.g. exterior, north-east facing walls in hotel "lobby". Note
0 commit comments