Skip to content

Commit 82028fb

Browse files
committed
Initial tests of 'bounded boxes' methods
1 parent 8ea89c7 commit 82028fb

File tree

2 files changed

+342
-8
lines changed

2 files changed

+342
-8
lines changed

src/osut/osut.py

Lines changed: 336 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

44924825
def facets(spaces=[], boundary="all", type="all", sides=[]) -> list:
44934826
"""Returns an array of OpenStudio space surfaces or subsurfaces that match

tests/test_osut.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2621,11 +2621,12 @@ def test23_fits_overlaps(self):
26212621
south = south.get()
26222622

26232623
# Side test: triad, medial and bounded boxes.
2624-
# pts = mod1.getNonCollinears(ceiling.vertices, 3)
2625-
# box01 = mod1.triadBox(pts)
2626-
# box11 = mod1.boundedBox(ceiling)
2627-
# self.asserTrue(mod1.areSame(box01, box11)
2628-
# self.asserTrue(mod1.fits(box01, ceiling)
2624+
pts = osut.nonCollinears(ceiling.vertices(), 3)
2625+
box01 = osut.triadBox(pts)
2626+
box11 = osut.boundedBox(ceiling)
2627+
print(o.logs())
2628+
self.assertTrue(osut.areSame(box01, box11))
2629+
self.assertTrue(osut.fits(box01, ceiling))
26292630

26302631
del(model)
26312632
self.assertEqual(o.clean(), DBG)

0 commit comments

Comments
 (0)