osut 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osut
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: OpenStudio SDK utilities for Python
5
5
  Author-email: Denis Bourgeois <denis@rd2.ca>
6
6
  Maintainer-email: Denis Bourgeois <denis@rd2.ca>
@@ -16,7 +16,6 @@ Requires-Python: >=3.2
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: oslg
19
- Requires-Dist: openstudio>=3.6.1
20
19
  Dynamic: license-file
21
20
 
22
21
  # pyOSut
@@ -25,3 +24,27 @@ Python implementation of the _OSut_ Ruby gem for the OpenStudio SDK.
25
24
  - PyPi [package](https://pypi.org/project/osut/)
26
25
  - Ruby [gem](https://rubygems.org/gems/osut)
27
26
  - Ruby GitHub [repository](https://github.com/rd2/osut)
27
+
28
+ ----
29
+
30
+ _OSut_ interacts with _OpenStudio_:
31
+
32
+ `pip install openstudio`
33
+
34
+ ----
35
+
36
+ To download the _OSut_ Python package:
37
+
38
+ `pip install --upgrade osut`
39
+
40
+ ----
41
+
42
+ To import the _OSut_ module in a Python project:
43
+
44
+ `from osut import osut`
45
+
46
+ ____
47
+
48
+ To run the _OSut_ unit tests on a `git clone` of the repo:
49
+
50
+ `python -m unittest`
osut-0.7.0/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # pyOSut
2
+ Python implementation of the _OSut_ Ruby gem for the OpenStudio SDK.
3
+
4
+ - PyPi [package](https://pypi.org/project/osut/)
5
+ - Ruby [gem](https://rubygems.org/gems/osut)
6
+ - Ruby GitHub [repository](https://github.com/rd2/osut)
7
+
8
+ ----
9
+
10
+ _OSut_ interacts with _OpenStudio_:
11
+
12
+ `pip install openstudio`
13
+
14
+ ----
15
+
16
+ To download the _OSut_ Python package:
17
+
18
+ `pip install --upgrade osut`
19
+
20
+ ----
21
+
22
+ To import the _OSut_ module in a Python project:
23
+
24
+ `from osut import osut`
25
+
26
+ ____
27
+
28
+ To run the _OSut_ unit tests on a `git clone` of the repo:
29
+
30
+ `python -m unittest`
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "osut"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "OpenStudio SDK utilities for Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.2"
7
7
  authors = [ {name = "Denis Bourgeois", email = "denis@rd2.ca"} ]
8
8
  maintainers = [ {name = "Denis Bourgeois", email = "denis@rd2.ca"} ]
9
- dependencies = ["oslg","openstudio>=3.6.1"]
9
+ dependencies = ["oslg"]
10
10
  license = "BSD-3-Clause"
11
11
  license-files = ["LICENSE"]
12
12
  classifiers = [
@@ -107,8 +107,8 @@ _film = dict(
107
107
 
108
108
  # Default (~1980s) envelope Uo (W/m2•K), based on surface type.
109
109
  _uo = dict(
110
- shading = None, # N/A
111
- partition = None, # N/A
110
+ shading = None, # N/A
111
+ partition = None, # N/A
112
112
  wall = 0.384, # rated R14.8 hr•ft2F/Btu
113
113
  roof = 0.327, # rated R17.6 hr•ft2F/Btu
114
114
  floor = 0.317, # rated R17.9 hr•ft2F/Btu (exposed floor)
@@ -335,9 +335,8 @@ def genConstruction(model=None, specs=dict()):
335
335
  ide = "OSut.CON." + specs["type"]
336
336
  if specs["type"] not in uo():
337
337
  return oslg.invalid("surface type", mth, 2, CN.ERR)
338
- if "uo" not in specs:
339
- specs["uo"] = uo()[ specs["type"] ]
340
338
 
339
+ if "uo" not in specs: specs["uo"] = uo()[ specs["type"] ] # can be None
341
340
  u = specs["uo"]
342
341
 
343
342
  if u:
@@ -348,6 +347,8 @@ def genConstruction(model=None, specs=dict()):
348
347
 
349
348
  if u < 0:
350
349
  return oslg.negative(id + " Uo", mth, CN.ERR)
350
+ if round(u, 2) == 0:
351
+ return oslg.zero(id + " Uo", mth, CN.ERR)
351
352
  if u > 5.678:
352
353
  return oslg.invalid(id + " Uo (> 5.678)", mth, 2, CN.ERR)
353
354
 
@@ -581,7 +582,7 @@ def genConstruction(model=None, specs=dict()):
581
582
  a["compo" ]["id" ] = "OSut." + mt + ".%03d" % int(d * 1000)
582
583
 
583
584
  elif specs["type"] == "window":
584
- a["glazing"]["u" ] = specs["uo"]
585
+ a["glazing"]["u" ] = u if u else uo()["window"]
585
586
  a["glazing"]["shgc"] = 0.450
586
587
  if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"]
587
588
  a["glazing"]["id" ] = "OSut.window"
@@ -589,7 +590,7 @@ def genConstruction(model=None, specs=dict()):
589
590
  a["glazing"]["id" ] += ".SHGC%d" % (a["glazing"]["shgc"]*100)
590
591
 
591
592
  elif specs["type"] == "skylight":
592
- a["glazing"]["u" ] = specs["uo"]
593
+ a["glazing"]["u" ] = u if u else uo()["skylight"]
593
594
  a["glazing"]["shgc"] = 0.450
594
595
  if "shgc" in specs: a["glazing"]["shgc"] = specs["shgc"]
595
596
  a["glazing"]["id" ] = "OSut.skylight"
@@ -599,14 +600,14 @@ def genConstruction(model=None, specs=dict()):
599
600
  if a["glazing"]:
600
601
  layers = openstudio.model.FenestrationMaterialVector()
601
602
 
602
- u = a["glazing"]["u" ]
603
+ u0 = a["glazing"]["u" ]
603
604
  shgc = a["glazing"]["shgc"]
604
605
  lyr = model.getSimpleGlazingByName(a["glazing"]["id"])
605
606
 
606
607
  if lyr:
607
608
  lyr = lyr.get()
608
609
  else:
609
- lyr = openstudio.model.SimpleGlazing(model, u, shgc)
610
+ lyr = openstudio.model.SimpleGlazing(model, u0, shgc)
610
611
  lyr.setName(a["glazing"]["id"])
611
612
 
612
613
  layers.append(lyr)
@@ -635,49 +636,54 @@ def genConstruction(model=None, specs=dict()):
635
636
 
636
637
  layers.append(lyr)
637
638
 
638
- c = openstudio.model.Construction(layers)
639
+ c = openstudio.model.Construction(layers)
639
640
  c.setName(ide)
640
641
 
641
642
  # Adjust insulating layer thickness or conductivity to match requested Uo.
642
- if not a["glazing"]:
643
- ro = 1 / specs["uo"] - film()[specs["type"]] if specs["uo"] else 0
643
+ if u and not a["glazing"]:
644
+ ro = 1 / u - flm
644
645
 
645
- if specs["type"] == "door": # 1x layer, adjust conductivity
646
- layer = c.getLayer(0).to_StandardOpaqueMaterial()
646
+ if ro > 0:
647
+ if specs["type"] == "door": # 1x layer, adjust conductivity
648
+ layer = c.getLayer(0).to_StandardOpaqueMaterial()
647
649
 
648
- if not layer:
649
- return oslg.invalid(id + " standard material?", mth, 0)
650
+ if not layer:
651
+ return oslg.invalid(id + " standard material?", mth, 0)
650
652
 
651
- layer = layer.get()
652
- k = layer.thickness() / ro
653
- layer.setConductivity(k)
653
+ layer = layer.get()
654
+ k = layer.thickness() / ro
655
+ layer.setConductivity(k)
654
656
 
655
- elif ro > 0: # multiple layers, adjust insulating layer thickness
656
- lyr = insulatingLayer(c)
657
+ else: # multiple layers, adjust insulating layer thickness
658
+ lyr = insulatingLayer(c)
657
659
 
658
- if not lyr["index"] or not lyr["type"] or not lyr["r"]:
659
- return oslg.invalid(id + " construction", mth, 0)
660
+ if not lyr["index"] or not lyr["type"] or not lyr["r"]:
661
+ return oslg.invalid(id + " construction", mth, 0)
660
662
 
661
- index = lyr["index"]
662
- layer = c.getLayer(index).to_StandardOpaqueMaterial()
663
+ index = lyr["index"]
664
+ layer = c.getLayer(index).to_StandardOpaqueMaterial()
663
665
 
664
- if not layer:
665
- return oslg.invalid(id + " material %d" % index, mth, 0)
666
+ if not layer:
667
+ return oslg.invalid(id + " material %d" % index, mth, 0)
666
668
 
667
- layer = layer.get()
668
- k = layer.conductivity()
669
- d = (ro - rsi(c) + lyr["r"]) * k
669
+ layer = layer.get()
670
+ k = layer.conductivity()
671
+ d = (ro - rsi(c) + lyr["r"]) * k
670
672
 
671
- if d < 0.03:
672
- return oslg.invalid(id + " adjusted material thickness", mth, 0)
673
+ if d < 0.03:
674
+ m = id + " adjusted material thickness"
675
+ return oslg.invalid(m, mth, 0)
673
676
 
674
- nom = re.sub(r'[^a-zA-Z]', '', layer.nameString())
675
- nom = re.sub(r'OSut', '', nom)
676
- nom = "OSut." + nom + ".%03d" % int(d * 1000)
677
+ nom = re.sub(r'[^a-zA-Z]', '', layer.nameString())
678
+ nom = re.sub(r'OSut', '', nom)
679
+ nom = "OSut." + nom + ".%03d" % int(d * 1000)
677
680
 
678
- if not model.getStandardOpaqueMaterialByName(nom):
679
- layer.setName(nom)
680
- layer.setThickness(d)
681
+ if model.getStandardOpaqueMaterialByName(nom):
682
+ omat = model.getStandardOpaqueMaterialByName(nom).get()
683
+ c.setLayer(index, omat)
684
+ else:
685
+ layer.setName(nom)
686
+ layer.setThickness(d)
681
687
 
682
688
  return c
683
689
 
@@ -1650,7 +1656,7 @@ def scheduleIntervalMinMax(sched=None) -> dict:
1650
1656
  - "min" (float): min temperature. (None if invalid inputs - see logs).
1651
1657
  - "max" (float): max temperature. (None if invalid inputs - see logs).
1652
1658
  """
1653
- mth = "osut.scheduleCompactMinMax"
1659
+ mth = "osut.scheduleIntervalMinMax"
1654
1660
  cl = openstudio.model.ScheduleInterval
1655
1661
  vals = []
1656
1662
  res = dict(min=None, max=None)
@@ -1658,10 +1664,19 @@ def scheduleIntervalMinMax(sched=None) -> dict:
1658
1664
  if not isinstance(sched, cl):
1659
1665
  return oslg.mismatch("sched", sched, cl, mth, CN.DBG, res)
1660
1666
 
1661
- vals = sched.timeSeries().values()
1667
+ values = sched.timeSeries().values()
1662
1668
 
1663
- res["min"] = min(values)
1664
- res["max"] = max(values)
1669
+ for i in range(len(values)):
1670
+ try:
1671
+ value = float(values[i])
1672
+ vals.append(value)
1673
+ except:
1674
+ oslg.invalid("numerical at %d" % i, mth, 1, CN.ERR)
1675
+
1676
+ if not vals: return res
1677
+
1678
+ res["min"] = min(vals)
1679
+ res["max"] = max(vals)
1665
1680
 
1666
1681
  try:
1667
1682
  res["min"] = float(res["min"])
@@ -2595,6 +2610,17 @@ def availabilitySchedule(model=None, avl=""):
2595
2610
 
2596
2611
  return schedule
2597
2612
 
2613
+ # ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- #
2614
+ # This final set of utilities targets OpenStudio geometry. Many of the
2615
+ # following geometry methods rely on Boost as an OpenStudio dependency.
2616
+ # As per Boost requirements, points (e.g. vertical polygon) must be 'aligned':
2617
+ # - first rotated/tilted as to lay flat along XY plane (Z-axis ~= 0)
2618
+ # - initial Z-axis values now become Y-axis values
2619
+ # - points with the lowest X-axis values are 'aligned' along X-axis (0)
2620
+ # - points with the lowest Z-axis values are 'aligned' along Y-axis (0)
2621
+ # - for several Boost methods, points must be clockwise in sequence
2622
+ #
2623
+ # Check OSut's poly() method, which offers such Boost-related options.
2598
2624
 
2599
2625
  def transforms(group=None) -> dict:
2600
2626
  """"Returns OpenStudio site/space transformation & rotation angle.
@@ -2698,7 +2724,7 @@ def p3Dv(pts=None) -> openstudio.Point3dVector:
2698
2724
  pts (list): OpenStudio 3D points.
2699
2725
 
2700
2726
  Returns:
2701
- openstudio.Point3dVector: Vector of 3D points (see logs if empty).
2727
+ openstudio.Point3dVector: Vector of 3D points (see 'p3Dv' logs if empty).
2702
2728
 
2703
2729
  """
2704
2730
  mth = "osut.p3Dv"
@@ -2711,7 +2737,10 @@ def p3Dv(pts=None) -> openstudio.Point3dVector:
2711
2737
  elif isinstance(pts, openstudio.Point3dVector):
2712
2738
  return pts
2713
2739
  elif isinstance(pts, openstudio.model.PlanarSurface):
2714
- pts = list(pts.vertices())
2740
+ for vt in pts.vertices():
2741
+ pt = openstudio.Point3d(vt.x(), vt.y(), vt.z())
2742
+ v.append(pt)
2743
+ return v
2715
2744
 
2716
2745
  try:
2717
2746
  pts = list(pts)
@@ -3030,7 +3059,7 @@ def nextUp(pts=None, pt=None):
3030
3059
  None: If invalid inputs (see logs).
3031
3060
 
3032
3061
  """
3033
- mth = "osut.nextUP"
3062
+ mth = "osut.nextUp"
3034
3063
  pts = p3Dv(pts)
3035
3064
  cl = openstudio.Point3d
3036
3065
 
@@ -3168,7 +3197,7 @@ def uniques(pts=None, n=0) -> openstudio.Point3dVector:
3168
3197
  Requested number of unique points (0 returns all).
3169
3198
 
3170
3199
  Returns:
3171
- openstudio.Point3dVector: Unique points (see logs if empty).
3200
+ openstudio.Point3dVector: Unique points (see logs).
3172
3201
 
3173
3202
  """
3174
3203
  mth = "osut.uniques"
@@ -3179,7 +3208,8 @@ def uniques(pts=None, n=0) -> openstudio.Point3dVector:
3179
3208
  try:
3180
3209
  n = int(n)
3181
3210
  except:
3182
- return oslg.mismatch("n unique points", n, int, mth, CN.DBG, v)
3211
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3212
+ n = 0
3183
3213
 
3184
3214
  for pt in pts:
3185
3215
  if not holds(v, pt): v.append(pt)
@@ -3314,7 +3344,7 @@ def isPointAlongSegment(p0=None, sg=[]) -> bool:
3314
3344
 
3315
3345
  Returns:
3316
3346
  bool: Whether a 3D point lies ~along a 3D point segment.
3317
- False: If invalid inputs.
3347
+ False: If invalid inputs (see logs).
3318
3348
 
3319
3349
  """
3320
3350
  mth = "osut.isPointAlongSegment"
@@ -3323,12 +3353,10 @@ def isPointAlongSegment(p0=None, sg=[]) -> bool:
3323
3353
 
3324
3354
  if not isinstance(p0, cl1):
3325
3355
  return oslg.mismatch("point", p0, cl1, mth, CN.DBG, False)
3326
- if not isSegment(sg):
3327
- return oslg.mismatch("segment", sg, cl2, mth, CN.DBG, False)
3328
-
3356
+ if not isSegment(sg): return False
3329
3357
  if holds(sg, p0): return True
3330
3358
 
3331
- a = sg[0]
3359
+ a = sg[ 0]
3332
3360
  b = sg[-1]
3333
3361
  ab = b - a
3334
3362
  abn = b - a
@@ -3370,7 +3398,7 @@ def isPointAlongSegments(p0=None, sgs=[]) -> bool:
3370
3398
  if not sgs:
3371
3399
  return oslg.empty("segments", mth, CN.DBG, False)
3372
3400
  if not isinstance(p0, cl1):
3373
- return oslg.mismatch("point", p0, cl, mth, CN.DBG, False)
3401
+ return oslg.mismatch("point", p0, cl1, mth, CN.DBG, False)
3374
3402
 
3375
3403
  for sg in sgs:
3376
3404
  if isPointAlongSegment(p0, sg): return True
@@ -3444,14 +3472,6 @@ def lineIntersection(s1=[], s2=[]):
3444
3472
  xa1b1 = a.cross(a1b1)
3445
3473
  xa1b2 = a.cross(a1b2)
3446
3474
 
3447
- if xa1b1.length() < CN.TOL2:
3448
- if isPointAlongSegment(a1, [a2, b1]): return None
3449
- if isPointAlongSegment(a2, [a1, b1]): return None
3450
-
3451
- if xa1b2.length() < CN.TOL2:
3452
- if isPointAlongSegment(a1, [a2, b2]): return None
3453
- if isPointAlongSegment(a2, [a1, b2]): return None
3454
-
3455
3475
  # Both segment endpoints can't be 'behind' point.
3456
3476
  if a.dot(a1b1) < 0 and a.dot(a1b2) < 0: return None
3457
3477
 
@@ -3632,7 +3652,7 @@ def blc(pts=None) -> openstudio.Point3dVector:
3632
3652
 
3633
3653
 
3634
3654
  def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3635
- """Returns sequential non-collinear points in an OpenStudio 3D point vector.
3655
+ """Returns non-collinear points in an OpenStudio 3D point vector.
3636
3656
 
3637
3657
  Args:
3638
3658
  pts (openstudio.Point3dVector):
@@ -3641,11 +3661,10 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3641
3661
  Requested number of non-collinears (0 returns all).
3642
3662
 
3643
3663
  Returns:
3644
- openstudio.Point3dVector: non-collinears (see logs if empty).
3664
+ openstudio.Point3dVector: non-collinears (see logs).
3645
3665
 
3646
3666
  """
3647
3667
  mth = "osut.nonCollinears"
3648
- v = openstudio.Point3dVector()
3649
3668
  a = []
3650
3669
  pts = uniques(pts)
3651
3670
  if len(pts) < 3: return pts
@@ -3653,12 +3672,8 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3653
3672
  try:
3654
3673
  n = int(n)
3655
3674
  except:
3656
- oslg.mismatch("n non-collinears", n, int, mth, CN.DBG, v)
3657
-
3658
- if n > len(pts):
3659
- return oslg.invalid("+n non-collinears", mth, 0, CN.ERR, v)
3660
- elif n < 0 and abs(n) > len(pts):
3661
- return oslg.invalid("-n non-collinears", mth, 0, CN.ERR, v)
3675
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3676
+ n = 0
3662
3677
 
3663
3678
  # Evaluate cross product of vectors of 3x sequential points.
3664
3679
  for i2, p2 in enumerate(pts):
@@ -3680,9 +3695,7 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3680
3695
  a.rotate(1)
3681
3696
  a = list(a)
3682
3697
 
3683
- if n > len(a): return p3Dv(a)
3684
- if n < 0 and abs(n) > len(a): return p3Dv(a)
3685
-
3698
+ if abs(n) > len(a): n = 0
3686
3699
  if n > 0: a = a[0:n]
3687
3700
  if n < 0: a = a[n:]
3688
3701
 
@@ -3691,7 +3704,7 @@ def nonCollinears(pts=None, n=0) -> openstudio.Point3dVector:
3691
3704
 
3692
3705
  def collinears(pts=None, n=0) -> openstudio.Point3dVector:
3693
3706
  """
3694
- Returns sequential collinear points in an OpenStudio 3D point vector.
3707
+ Returns collinear points in an OpenStudio 3D point vector.
3695
3708
 
3696
3709
  Args:
3697
3710
  pts (openstudio.Point3dVector):
@@ -3700,38 +3713,31 @@ def collinears(pts=None, n=0) -> openstudio.Point3dVector:
3700
3713
  Requested number of collinears (0 returns all).
3701
3714
 
3702
3715
  Returns:
3703
- openstudio.Point3dVector: collinears (see logs if empty).
3716
+ openstudio.Point3dVector: collinears (see logs).
3704
3717
 
3705
3718
  """
3706
3719
  mth = "osut.collinears"
3707
- v = openstudio.Point3dVector()
3708
- a = []
3720
+ a = openstudio.Point3dVector()
3709
3721
  pts = uniques(pts)
3710
3722
  if len(pts) < 3: return pts
3711
3723
 
3712
3724
  try:
3713
3725
  n = int(n)
3714
3726
  except:
3715
- oslg.mismatch("n collinears", n, int, mth, CN.DBG, v)
3716
-
3717
- if n > len(pts):
3718
- return oslg.invalid("+n collinears", mth, 0, CN.ERR, v)
3719
- elif n < 0 and abs(n) > len(pts):
3720
- return oslg.invalid("-n collinears", mth, 0, CN.ERR, v)
3727
+ oslg.mismatch("n points", n, int, mth, CN.DBG)
3728
+ n = 0
3721
3729
 
3722
3730
  ncolls = nonCollinears(pts)
3723
- if not ncolls: return pts
3731
+ if not ncolls: return a
3724
3732
 
3725
3733
  for pt in pts:
3726
3734
  if pt not in ncolls: a.append(pt)
3727
3735
 
3728
- if n > len(a): return p3Dv(a)
3729
- if n < 0 and abs(n) > len(a): return p3Dv(a)
3730
-
3736
+ if abs(n) > len(a): n = 0
3731
3737
  if n > 0: a = a[0:n]
3732
3738
  if n < 0: a = a[n:]
3733
3739
 
3734
- return p3Dv(a)
3740
+ return a
3735
3741
 
3736
3742
 
3737
3743
  def poly(pts=None, vx=False, uq=False, co=False, tt=False, sq="no") -> openstudio.Point3dVector:
@@ -5237,15 +5243,12 @@ def spaceHeight(space=None) -> float:
5237
5243
  (float): Full height of space (0.0 if invalid input).
5238
5244
 
5239
5245
  """
5240
- if not isinstance(space, openstudio.model.Space):
5241
- return 0
5246
+ hght = 0
5247
+ if not isinstance(space, openstudio.model.Space): return 0
5242
5248
 
5243
- hght = 0
5244
5249
  minZ = 10000
5245
5250
  maxZ = -10000
5246
5251
 
5247
- # The solution considers all surface types: "Floor", "Wall", "RoofCeiling".
5248
- # No presumption that floor are necessarily at ground level.
5249
5252
  for surface in space.surfaces():
5250
5253
  zs = [pt.z() for pt in surface.vertices()]
5251
5254
  minZ = min(minZ, min(zs))
@@ -5288,21 +5291,18 @@ def spaceWidth(space=None) -> float:
5288
5291
  # - retain only other floor surfaces sharing same 3D plane
5289
5292
  # - recover potential union between floor surfaces
5290
5293
  # - fall back to largest floor surface if invalid union
5294
+ # - return width of largest bounded box
5291
5295
  floors = sorted(floors, key=lambda fl: fl.grossArea(), reverse=True)
5292
5296
  floor = floors[0]
5293
5297
  plane = floor.plane()
5294
5298
  t = openstudio.Transformation.alignFace(floor.vertices())
5295
5299
  polyg = list(poly(floor, False, True, True, t, "ulc"))
5296
-
5297
- if not polyg:
5298
- oslg.clean()
5299
- return 0
5300
+ if not polyg: return 0
5300
5301
 
5301
5302
  polyg.reverse()
5302
- polyg = p3Dv(polyg)
5303
5303
 
5304
5304
  if len(floors) > 1:
5305
- floors = [flr for flr in floors if plane.equal(fl.plane(), 0.001)]
5305
+ floors = [flr for flr in floors if plane.equal(flr.plane(), 0.001)]
5306
5306
 
5307
5307
  if len(floors) > 1:
5308
5308
  polygs = [poly(flr, False, True, True, t, "ulc") for flr in floors]
@@ -5315,12 +5315,16 @@ def spaceWidth(space=None) -> float:
5315
5315
 
5316
5316
  union = openstudio.joinAll(polygs, 0.01)[0]
5317
5317
  polyg = poly(union, False, True, True)
5318
+ if not polyg: return 0
5319
+
5320
+ polyg = list(polyg)
5321
+ polyg.reverse()
5318
5322
 
5319
- box = boundedBox(polyg)
5320
- oslg.clean()
5323
+ res = realignedFace(polyg)
5324
+ if not res["box"]: return 0
5321
5325
 
5322
5326
  # A bounded box's 'height', at its narrowest, is its 'width'.
5323
- return height(box)
5327
+ return height(res["box"])
5324
5328
 
5325
5329
 
5326
5330
  def genAnchors(s=None, sset=[], tag="box") -> int:
@@ -6079,7 +6083,7 @@ def genSlab(pltz=[], z=0) -> openstudio.Point3dVector:
6079
6083
  slb = vtx
6080
6084
 
6081
6085
  # Once joined, re-adjust Z-axis coordinates.
6082
- if abs(z) > CN.TOL:
6086
+ if round(z, 2) != 0.00:
6083
6087
  vtx = openstudio.Point3dVector()
6084
6088
 
6085
6089
  for pt in slb: vtx.append(openstudio.Point3d(pt.x(), pt.y(), z))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osut
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: OpenStudio SDK utilities for Python
5
5
  Author-email: Denis Bourgeois <denis@rd2.ca>
6
6
  Maintainer-email: Denis Bourgeois <denis@rd2.ca>
@@ -16,7 +16,6 @@ Requires-Python: >=3.2
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: oslg
19
- Requires-Dist: openstudio>=3.6.1
20
19
  Dynamic: license-file
21
20
 
22
21
  # pyOSut
@@ -25,3 +24,27 @@ Python implementation of the _OSut_ Ruby gem for the OpenStudio SDK.
25
24
  - PyPi [package](https://pypi.org/project/osut/)
26
25
  - Ruby [gem](https://rubygems.org/gems/osut)
27
26
  - Ruby GitHub [repository](https://github.com/rd2/osut)
27
+
28
+ ----
29
+
30
+ _OSut_ interacts with _OpenStudio_:
31
+
32
+ `pip install openstudio`
33
+
34
+ ----
35
+
36
+ To download the _OSut_ Python package:
37
+
38
+ `pip install --upgrade osut`
39
+
40
+ ----
41
+
42
+ To import the _OSut_ module in a Python project:
43
+
44
+ `from osut import osut`
45
+
46
+ ____
47
+
48
+ To run the _OSut_ unit tests on a `git clone` of the repo:
49
+
50
+ `python -m unittest`
@@ -0,0 +1 @@
1
+ oslg
@@ -392,7 +392,7 @@ class TestOSutModuleMethods(unittest.TestCase):
392
392
  self.assertEqual(o.status(), 0)
393
393
  del model
394
394
 
395
- # Insulated (conditioned), parking garage roof (polyiso under 8" slab).
395
+ # Roof above conditioned parking garage (polyiso under 8" slab).
396
396
  specs = dict(type="roof", uo=0.214, clad="heavy", frame="medium", finish="none")
397
397
  model = openstudio.model.Model()
398
398
  c = osut.genConstruction(model, specs)
@@ -649,6 +649,41 @@ class TestOSutModuleMethods(unittest.TestCase):
649
649
  self.assertEqual(o.status(), 0)
650
650
  del model
651
651
 
652
+ # Invalid Uo (here, skylights and windows inherit default Uo values)
653
+ specs = dict(type="skylight", uo=None)
654
+ model = openstudio.model.Model()
655
+ c = osut.genConstruction(model, specs)
656
+ self.assertEqual(o.status(), 0)
657
+ self.assertFalse(o.logs())
658
+ self.assertTrue(c)
659
+ self.assertTrue(isinstance(c, openstudio.model.Construction))
660
+ self.assertEqual(c.nameString(), "OSut.CON.skylight")
661
+ self.assertTrue(c.layers())
662
+ self.assertEqual(len(c.layers()), 1)
663
+ self.assertEqual(c.layers()[0].nameString(), "OSut.skylight.U3.5.SHGC45")
664
+ r = osut.rsi(c)
665
+ self.assertAlmostEqual(r, 1/osut.uo()["skylight"], places=3)
666
+ self.assertFalse(o.logs())
667
+ self.assertEqual(o.status(), 0)
668
+ del model
669
+
670
+ # Invalid Uo (here, Uo-adjustments are ignored altogether)
671
+ specs = dict(type="wall", uo=None)
672
+ model = openstudio.model.Model()
673
+ c = osut.genConstruction(model, specs)
674
+ self.assertEqual(o.status(), 0)
675
+ self.assertFalse(o.logs())
676
+ self.assertTrue(c)
677
+ self.assertTrue(isinstance(c, openstudio.model.Construction))
678
+ self.assertEqual(c.nameString(), "OSut.CON.wall")
679
+ self.assertTrue(c.layers())
680
+ self.assertEqual(len(c.layers()), 4)
681
+ r = osut.rsi(c)
682
+ self.assertAlmostEqual(1/r, 2.23, places=2) # not matching any defaults
683
+ self.assertFalse(o.logs())
684
+ self.assertEqual(o.status(), 0)
685
+ del model
686
+
652
687
  def test06_internal_mass(self):
653
688
  o = osut.oslg
654
689
  self.assertEqual(o.status(), 0)
@@ -1696,9 +1731,27 @@ class TestOSutModuleMethods(unittest.TestCase):
1696
1731
  self.assertTrue(cc.setTemperatureCalculationRequestedAfterLayerNumber(1))
1697
1732
  self.assertTrue(floor.setConstruction(cc))
1698
1733
 
1734
+ # Test 'fixed interval' schedule. Annual time series - no variation.
1735
+ start = model.getYearDescription().makeDate(1, 1)
1736
+ inter = openstudio.Time(0, 1, 0, 0)
1737
+ values = openstudio.createVector([22.78] * 8760)
1738
+ series = openstudio.TimeSeries(start, inter, values, "")
1739
+ limits = openstudio.model.ScheduleTypeLimits(model)
1740
+ limits.setName("Radiant Electric Heating Setpoint Schedule Type Limits")
1741
+ self.assertTrue(limits.setNumericType("Continuous"))
1742
+ self.assertTrue(limits.setUnitType("Temperature"))
1743
+
1744
+ schedule = openstudio.model.ScheduleFixedInterval(model)
1745
+ schedule.setName("Radiant Electric Heating Setpoint Schedule")
1746
+ self.assertTrue(schedule.setTimeSeries(series))
1747
+ self.assertTrue(schedule.setTranslatetoScheduleFile(False))
1748
+ self.assertTrue(schedule.setScheduleTypeLimits(limits))
1749
+
1750
+ tvals = schedule.timeSeries().values()
1751
+ self.assertTrue(isinstance(tvals, openstudio.Vector))
1752
+ for i in range(len(tvals)): self.assertTrue(isinstance(tvals[i], float))
1753
+
1699
1754
  availability = osut.availabilitySchedule(model)
1700
- schedule = openstudio.model.ScheduleConstant(model)
1701
- self.assertTrue(schedule.setValue(22.78)) # reuse cooling setpoint
1702
1755
 
1703
1756
  # Create radiant electric heating.
1704
1757
  ht = (openstudio.model.ZoneHVACLowTemperatureRadiantElectric(
@@ -3255,49 +3308,202 @@ class TestOSutModuleMethods(unittest.TestCase):
3255
3308
  p7 = openstudio.Point3d(14, 20, -5)
3256
3309
  p8 = openstudio.Point3d(-9, -9, -5)
3257
3310
 
3258
- # Stress tests.
3259
- m1 = "Invalid '+n collinears' (osut.collinears)"
3260
- m2 = "Invalid '-n collinears' (osut.collinears)"
3311
+ # Stress test 'to_p3Dv'. 4 valid input cases.
3312
+ # Valid case #1: a single Point3d.
3313
+ vtx = osut.p3Dv(p0)
3314
+ self.assertTrue(isinstance(vtx, openstudio.Point3dVector))
3315
+ self.assertEqual(vtx[0], p0) # same object ID
3316
+
3317
+ # Valid case #2: a Point3dVector.
3318
+ vtxx = openstudio.Point3dVector()
3319
+ vtxx.append(p0)
3320
+ vtxx.append(p1)
3321
+ vtxx.append(p2)
3322
+ vtxx.append(p3)
3323
+ vtx = osut.p3Dv(vtxx)
3324
+ self.assertTrue(isinstance(vtx, openstudio.Point3dVector))
3325
+ self.assertEqual(vtx[ 0], p0) # same object ID
3326
+ self.assertEqual(vtx[ 1], p1) # same object ID
3327
+ self.assertEqual(vtx[ 2], p2) # same object ID
3328
+ self.assertEqual(vtx[-1], p3) # same object ID
3329
+
3330
+ # Valid case #3: Surface vertices.
3331
+ model = openstudio.model.Model()
3332
+ surface = openstudio.model.Surface(vtxx, model)
3333
+ self.assertTrue(isinstance(surface.vertices(), tuple)) # ! Point3dVector
3334
+ self.assertEqual(len(surface.vertices()), 4)
3335
+ vtx = osut.p3Dv(vtxx)
3336
+ self.assertTrue(isinstance(vtx, openstudio.Point3dVector))
3337
+ self.assertEqual(len(vtx), 4)
3338
+ self.assertEqual(vtx[0], p0)
3339
+ self.assertEqual(vtx[1], p1)
3340
+ self.assertEqual(vtx[2], p2)
3341
+ self.assertEqual(vtx[3], p3)
3342
+
3343
+ # Valid case #4: Array.
3344
+ vtx = osut.p3Dv([p0, p1, p2, p3])
3345
+ self.assertTrue(isinstance(vtx, openstudio.Point3dVector))
3346
+ self.assertEqual(len(vtx), 4)
3347
+ self.assertEqual(vtx[0], p0)
3348
+ self.assertEqual(vtx[1], p1)
3349
+ self.assertEqual(vtx[2], p2)
3350
+ self.assertEqual(vtx[3], p3)
3351
+
3352
+ # Stress test 'nextUp'.
3353
+ m0 = "Invalid 'points (2+)' arg #1 (osut.nextUp)"
3354
+
3355
+ # Invalid case.
3356
+ pt = osut.nextUp([], p0)
3357
+ self.assertFalse(pt)
3358
+ self.assertTrue(o.is_warn())
3359
+ self.assertEqual(len(o.logs()), 1)
3360
+ self.assertEqual(o.logs()[0]["message"], m0)
3361
+ self.assertEqual(o.clean(), DBG)
3362
+
3363
+ # Valid case.
3364
+ pt = osut.nextUp([p0, p1, p2, p3], p0)
3365
+ self.assertTrue(isinstance(pt, openstudio.Point3d))
3366
+ self.assertEqual(pt, p1)
3367
+
3368
+ pt = osut.nextUp([p0, p0, p0], p0)
3369
+ self.assertTrue(isinstance(pt, openstudio.Point3d))
3370
+ self.assertEqual(pt, p0)
3371
+
3372
+ # Stress test 'segments'. Invalid case.
3373
+ sgs = osut.segments(p3)
3374
+ self.assertTrue(isinstance(sgs, openstudio.Point3dVectorVector))
3375
+ self.assertFalse(sgs)
3376
+ self.assertEqual(o.status(), 0) # nothing logged
3377
+
3378
+ sgs = osut.segments([p3, p3])
3379
+ self.assertTrue(isinstance(sgs, openstudio.Point3dVectorVector))
3380
+ self.assertFalse(sgs)
3381
+ self.assertEqual(o.status(), 0) # nothing logged
3382
+
3383
+ # Valid case.
3384
+ sgs = osut.segments([p0, p1, p2, p3])
3385
+ self.assertTrue(isinstance(sgs, openstudio.Point3dVectorVector))
3386
+ self.assertEqual(len(sgs), 4)
3387
+ self.assertTrue(isinstance(sgs[-1], tuple)) # ! Point3dVector
3388
+
3389
+ # Stress test 'uniques'.
3390
+ m0 = "'n points' str? expecting int (osut.uniques)"
3261
3391
 
3392
+ # Invalid case.
3393
+ uniks = osut.uniques([p0, p1, p2, p3], "osut")
3394
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3395
+ self.assertEqual(len(uniks), 4)
3396
+ self.assertTrue(o.is_debug())
3397
+ self.assertEqual(len(o.logs()), 1)
3398
+ self.assertEqual(o.logs()[0]["message"], m0)
3399
+ self.assertEqual(o.clean(), DBG)
3400
+
3401
+ # Valid, basic case.
3402
+ uniks = osut.uniques([p0, p1, p2, p3])
3403
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3404
+ self.assertEqual(len(uniks), 4)
3405
+
3406
+ uniks = osut.uniques([p0, p1, p2, p3], 0)
3407
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3408
+ self.assertEqual(len(uniks), 4)
3409
+
3410
+ # Valid, first 3 points.
3411
+ uniks = osut.uniques([p0, p1, p2, p3], 3)
3412
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3413
+ self.assertEqual(len(uniks), 3)
3414
+
3415
+ # Valid, last 3 points.
3416
+ uniks = osut.uniques([p0, p1, p2, p3], -3)
3417
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3418
+ self.assertEqual(len(uniks), 3)
3419
+
3420
+ # Valid, n = 5: returns original 4 uniques points.
3421
+ uniks = osut.uniques([p0, p1, p2, p3], 5)
3422
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3423
+ self.assertEqual(len(uniks), 4)
3424
+
3425
+ # Valid, n = -5: returns original 4 uniques points.
3426
+ uniks = osut.uniques([p0, p1, p2, p3], -5)
3427
+ self.assertTrue(isinstance(uniks, openstudio.Point3dVector))
3428
+ self.assertEqual(len(uniks), 4)
3429
+
3430
+ # Stress tests collinears.
3431
+ m0 = "'n points' str? expecting int (osut.collinears)"
3432
+
3433
+ # Invalid case - raise DEBUG message, yet returns valid collinears.
3434
+ collinears = osut.collinears([p0, p1, p3, p8], "osut")
3435
+ self.assertTrue(isinstance(collinears, openstudio.Point3dVector))
3436
+ self.assertEqual(len(collinears), 1)
3437
+ self.assertTrue(osut.areSame(collinears[0], p0))
3438
+ self.assertTrue(o.is_debug())
3439
+ self.assertEqual(len(o.logs()), 1)
3440
+ self.assertEqual(o.logs()[0]["message"], m0)
3441
+ self.assertEqual(o.clean(), DBG)
3442
+
3443
+ # Valid, basic case
3262
3444
  collinears = osut.collinears([p0, p1, p3, p8])
3263
3445
  self.assertEqual(len(collinears), 1)
3264
3446
  self.assertTrue(osut.areSame(collinears[0], p0))
3265
3447
 
3448
+ collinears = osut.collinears([p0, p1, p3, p8], 0)
3449
+ self.assertEqual(len(collinears), 1)
3450
+ self.assertTrue(osut.areSame(collinears[0], p0))
3451
+
3266
3452
  collinears = osut.collinears([p0, p1, p2, p3, p8])
3267
3453
  self.assertEqual(len(collinears), 2)
3268
3454
  self.assertTrue(osut.areSame(collinears[0], p0))
3269
3455
  self.assertTrue(osut.areSame(collinears[1], p1))
3456
+ self.assertTrue(osut.isPointAlongSegment(p0, sgs[0]))
3270
3457
 
3458
+ # Only 2 collinears, so request for first 3 is ignored.
3271
3459
  collinears = osut.collinears([p0, p1, p2, p3, p8], 3)
3272
3460
  self.assertEqual(len(collinears), 2)
3273
3461
  self.assertTrue(osut.areSame(collinears[0], p0))
3274
3462
  self.assertTrue(osut.areSame(collinears[1], p1))
3275
3463
 
3464
+ # First collinear (out of 2).
3276
3465
  collinears = osut.collinears([p0, p1, p2, p3, p8], 1)
3277
3466
  self.assertEqual(len(collinears), 1)
3278
3467
  self.assertTrue(osut.areSame(collinears[0], p0))
3279
3468
 
3469
+ # Last collinear (out of 2).
3280
3470
  collinears = osut.collinears([p0, p1, p2, p3, p8], -1)
3281
3471
  self.assertEqual(len(collinears), 1)
3282
3472
  self.assertTrue(osut.areSame(collinears[0], p1))
3283
3473
 
3474
+ # First two vs last two: same result.
3284
3475
  collinears = osut.collinears([p0, p1, p2, p3, p8], -2)
3285
3476
  self.assertEqual(len(collinears), 2)
3286
3477
  self.assertTrue(osut.areSame(collinears[0], p0))
3287
3478
  self.assertTrue(osut.areSame(collinears[1], p1))
3288
3479
 
3480
+ # Ignore n request when abs(n) > number of actual collinears.
3289
3481
  collinears = osut.collinears([p0, p1, p2, p3, p8], 6)
3290
- self.assertTrue(o.is_error())
3291
- self.assertEqual(len(o.logs()), 1)
3292
- self.assertEqual(o.logs()[0]["message"], m1)
3293
- self.assertEqual(o.clean(), DBG)
3482
+ self.assertEqual(len(collinears), 2)
3483
+ self.assertTrue(osut.areSame(collinears[0], p0))
3484
+ self.assertTrue(osut.areSame(collinears[1], p1))
3294
3485
 
3295
3486
  collinears = osut.collinears([p0, p1, p2, p3, p8], -6)
3296
- self.assertTrue(o.is_error())
3487
+ self.assertEqual(len(collinears), 2)
3488
+ self.assertTrue(osut.areSame(collinears[0], p0))
3489
+ self.assertTrue(osut.areSame(collinears[1], p1))
3490
+
3491
+ # Stress test isPointAlongSegment.
3492
+ m0 = "'point' str? expecting Point3d (osut.p3Dv)"
3493
+
3494
+ # Invalid case.
3495
+ self.assertFalse(osut.isPointAlongSegment(p3, "osut"))
3496
+ self.assertTrue(o.is_debug())
3297
3497
  self.assertEqual(len(o.logs()), 1)
3298
- self.assertEqual(o.logs()[0]["message"], m2)
3498
+ self.assertEqual(o.logs()[0]["message"], m0)
3299
3499
  self.assertEqual(o.clean(), DBG)
3300
3500
 
3501
+ # Valid case.
3502
+ pts = openstudio.Point3dVector()
3503
+ pts.append(p0)
3504
+ pts.append(p1)
3505
+ self.assertFalse(osut.isPointAlongSegment(p3, pts))
3506
+
3301
3507
  # CASE a1: 2x end-to-end line segments (returns matching endpoints).
3302
3508
  self.assertTrue(osut.doesLineIntersect([p0, p1], [p1, p2]))
3303
3509
  pt = osut.lineIntersection([p0, p1], [p1, p2])
@@ -5349,15 +5555,15 @@ class TestOSutModuleMethods(unittest.TestCase):
5349
5555
 
5350
5556
  translator = openstudio.osversion.VersionTranslator()
5351
5557
 
5352
- path = openstudio.path("./tests/files/osms/out/seb2.osm")
5558
+ path = openstudio.path("./tests/files/osms/out/seb_ext2.osm")
5353
5559
  model = translator.loadModel(path)
5354
5560
  self.assertTrue(model)
5355
5561
  model = model.get()
5356
5562
  spaces = model.getSpaces()
5357
5563
  surfs = model.getSurfaces()
5358
5564
  subs = model.getSubSurfaces()
5359
- self.assertEqual(len(surfs), 56)
5360
- self.assertEqual(len(subs), 8)
5565
+ self.assertEqual(len(surfs), 59)
5566
+ self.assertEqual(len(subs), 14)
5361
5567
 
5362
5568
  # The solution is similar to:
5363
5569
  # OpenStudio::Model::Space::findSurfaces(minDegreesFromNorth,
@@ -5381,15 +5587,15 @@ class TestOSutModuleMethods(unittest.TestCase):
5381
5587
  roofs1 = osut.facets(spaces, "Outdoors", "RoofCeiling", "top")
5382
5588
  roofs2 = osut.facets(spaces, "Outdoors", "RoofCeiling", "foo")
5383
5589
 
5384
- self.assertEqual(len(windows), 8)
5385
- self.assertEqual(len(skylights), 0)
5386
- self.assertEqual(len(walls), 26)
5590
+ self.assertEqual(len(windows), 11)
5591
+ self.assertEqual(len(skylights), 3)
5592
+ self.assertEqual(len(walls), 28)
5387
5593
  self.assertFalse(northsouth)
5388
5594
  self.assertEqual(len(northeast), 8)
5389
5595
  self.assertEqual(len(north), 14)
5390
5596
  self.assertEqual(len(floors1a), 4)
5391
5597
  self.assertEqual(len(floors1b), 4)
5392
- self.assertEqual(len(roofs1), 4)
5598
+ self.assertEqual(len(roofs1), 5)
5393
5599
  self.assertFalse(roofs2)
5394
5600
 
5395
5601
  # Concise variants, same output. In the SEB model, floors face "Ground".
@@ -5574,7 +5780,7 @@ class TestOSutModuleMethods(unittest.TestCase):
5574
5780
  self.assertEqual(len(surface.vertices()), 12)
5575
5781
  self.assertAlmostEqual(surface.grossArea(), 5 * 20 - 1, places=2)
5576
5782
 
5577
- # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5783
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5578
5784
  # Same as previous, yet overlapping 'plate' has both negative dX & dY,
5579
5785
  # while XY origin is set at top-right (not bottom-left) corner.
5580
5786
  # ____ ____
@@ -5602,6 +5808,17 @@ class TestOSutModuleMethods(unittest.TestCase):
5602
5808
  self.assertEqual(o.status(), 0)
5603
5809
  del model
5604
5810
 
5811
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5812
+ # Invalid input case.
5813
+ plates = ["osut"]
5814
+ slab = osut.genSlab(plates, z0)
5815
+ self.assertTrue(o.is_debug())
5816
+ self.assertEqual(len(o.logs()), 1)
5817
+ self.assertTrue("str? expecting dict" in o.logs()[0]["message"])
5818
+ self.assertTrue(isinstance(slab, openstudio.Point3dVector))
5819
+ self.assertFalse(slab)
5820
+ self.assertEqual(o.clean(), DBG)
5821
+
5605
5822
  def test37_roller_shades(self):
5606
5823
  o = osut.oslg
5607
5824
  self.assertEqual(o.status(), 0)
@@ -5673,5 +5890,90 @@ class TestOSutModuleMethods(unittest.TestCase):
5673
5890
  del model
5674
5891
  self.assertEqual(o.status(), 0)
5675
5892
 
5893
+ def test38_space_height_width(self):
5894
+ o = osut.oslg
5895
+ self.assertEqual(o.status(), 0)
5896
+ self.assertEqual(o.reset(DBG), DBG)
5897
+ self.assertEqual(o.level(), DBG)
5898
+ translator = openstudio.osversion.VersionTranslator()
5899
+
5900
+ path = openstudio.path("./tests/files/osms/in/warehouse.osm")
5901
+ model = translator.loadModel(path)
5902
+ self.assertTrue(model)
5903
+ model = model.get()
5904
+
5905
+ fine = model.getSpaceByName("Zone2 Fine Storage")
5906
+ self.assertTrue(fine)
5907
+ fine = fine.get()
5908
+
5909
+ # The Fine Storage space has 2 floors, at different Z-axis levels:
5910
+ # - main ground floor (slab on grade), Z=0.00m
5911
+ # - mezzanine floor, adjacent to the office space ceiling below, Z=4.27m
5912
+ self.assertTrue(len(osut.facets(fine, "all", "floor")), 2)
5913
+ groundfloor = model.getSurfaceByName("Fine Storage Floor")
5914
+ mezzanine = model.getSurfaceByName("Office Roof Reversed")
5915
+ self.assertTrue(groundfloor)
5916
+ self.assertTrue(mezzanine)
5917
+ groundfloor = groundfloor.get()
5918
+ mezzanine = mezzanine.get()
5919
+
5920
+ # The ground floor is L-shaped, floor surfaces have differenet Z=axis
5921
+ # levels, etc. In the context of codes/standards like ASHRAE 90.1 or the
5922
+ # Canadian NECB, determining what constitutes a space's 'height' and/or
5923
+ # 'width' matters, namely with regards to geometry-based LPD rules
5924
+ # (e.g. adjustments based on corridor 'width'). Not stating here what
5925
+ # the definitive answers should be in all cases. There are however a few
5926
+ # OSut functions that may be helpful.
5927
+ #
5928
+ # OSut's 'aligned' height and width functions were initially developed
5929
+ # for non-flat surfaces, like walls and sloped roofs - particularly
5930
+ # useful when such surfaces are rotated in 3D space. It's somewhat less
5931
+ # intuitive when applied to horizontal surfaces like floors. In a
5932
+ # nutshell, the functions lay out the surface in a 2D grid, aligning it
5933
+ # along its 'bounded box'. It then determines a bounding box around the
5934
+ # surface, once aligned:
5935
+ # - 'aligned height' designates the narrowest edge of the bounding box
5936
+ # - 'aligned width' designates the widest edge of the bounding box
5937
+ #
5938
+ # Useful? In some circumstances, maybe. One can argue that these may be
5939
+ # of limited use for width-based LPD adjustment calculations.
5940
+ self.assertAlmostEqual(osut.alignedHeight(groundfloor), 30.48, places=2)
5941
+ self.assertAlmostEqual(osut.alignedWidth(groundfloor), 45.72, places=2)
5942
+ self.assertAlmostEqual(osut.alignedHeight(mezzanine), 9.14, places=2)
5943
+ self.assertAlmostEqual(osut.alignedWidth(mezzanine), 25.91, places=2)
5944
+
5945
+ # OSut's 'spaceHeight' and 'spaceWidth' are more suitable for height- or
5946
+ # width-based LPD adjustement calculations. OSut sets a space's width as
5947
+ # the length of the narrowest edge of the largest bounded box that fits
5948
+ # within a collection of neighbouring floor surfaces. This is considered
5949
+ # reasonable for a long corridor, with varying widths along its full
5950
+ # length (e.g. occasional alcoves).
5951
+ #
5952
+ # Achtung! The function can be time consuming (multiple iterations) for
5953
+ # very convoluted spaces (e.g. long corridors with multiple concavities).
5954
+ self.assertAlmostEqual(osut.spaceHeight(fine), 8.53, places=2)
5955
+ self.assertAlmostEqual(osut.spaceWidth(fine), 21.33, places=2)
5956
+
5957
+ # --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- #
5958
+ path = openstudio.path("./tests/files/osms/out/seb_sky.osm")
5959
+ model = translator.loadModel(path)
5960
+ self.assertTrue(model)
5961
+ model = model.get()
5962
+
5963
+ openarea = model.getSpaceByName("Open area 1")
5964
+ self.assertTrue(openarea)
5965
+ openarea = openarea.get()
5966
+
5967
+ floor = osut.facets(openarea, "all", "floor")
5968
+ self.assertEqual(len(floor), 1)
5969
+ floor = floor[0]
5970
+
5971
+ self.assertAlmostEqual(osut.alignedHeight(floor), 6.88, places=2)
5972
+ self.assertAlmostEqual(osut.alignedWidth(floor), 8.22, places=2)
5973
+ self.assertAlmostEqual(osut.spaceHeight(openarea), 3.96, places=2)
5974
+ self.assertAlmostEqual(osut.spaceWidth(openarea), 3.77, places=2)
5975
+
5976
+ self.assertEqual(o.status(), 0)
5977
+
5676
5978
  if __name__ == "__main__":
5677
5979
  unittest.main()
osut-0.6.0/README.md DELETED
@@ -1,6 +0,0 @@
1
- # pyOSut
2
- Python implementation of the _OSut_ Ruby gem for the OpenStudio SDK.
3
-
4
- - PyPi [package](https://pypi.org/project/osut/)
5
- - Ruby [gem](https://rubygems.org/gems/osut)
6
- - Ruby GitHub [repository](https://github.com/rd2/osut)
@@ -1,2 +0,0 @@
1
- oslg
2
- openstudio>=3.6.1
File without changes
File without changes
File without changes
File without changes