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.
- {osut-0.6.0 → osut-0.7.0}/PKG-INFO +25 -2
- osut-0.7.0/README.md +30 -0
- {osut-0.6.0 → osut-0.7.0}/pyproject.toml +2 -2
- {osut-0.6.0 → osut-0.7.0}/src/osut/osut.py +106 -102
- {osut-0.6.0 → osut-0.7.0}/src/osut.egg-info/PKG-INFO +25 -2
- osut-0.7.0/src/osut.egg-info/requires.txt +1 -0
- {osut-0.6.0 → osut-0.7.0}/tests/test_osut.py +322 -20
- osut-0.6.0/README.md +0 -6
- osut-0.6.0/src/osut.egg-info/requires.txt +0 -2
- {osut-0.6.0 → osut-0.7.0}/LICENSE +0 -0
- {osut-0.6.0 → osut-0.7.0}/setup.cfg +0 -0
- {osut-0.6.0 → osut-0.7.0}/src/osut/__init__.py +0 -0
- {osut-0.6.0 → osut-0.7.0}/src/osut.egg-info/SOURCES.txt +0 -0
- {osut-0.6.0 → osut-0.7.0}/src/osut.egg-info/dependency_links.txt +0 -0
- {osut-0.6.0 → osut-0.7.0}/src/osut.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: osut
|
|
3
|
-
Version: 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.
|
|
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"
|
|
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,
|
|
111
|
-
partition = None,
|
|
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" ] =
|
|
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" ] =
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 /
|
|
643
|
+
if u and not a["glazing"]:
|
|
644
|
+
ro = 1 / u - flm
|
|
644
645
|
|
|
645
|
-
if
|
|
646
|
-
layer
|
|
646
|
+
if ro > 0:
|
|
647
|
+
if specs["type"] == "door": # 1x layer, adjust conductivity
|
|
648
|
+
layer = c.getLayer(0).to_StandardOpaqueMaterial()
|
|
647
649
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
+
if not layer:
|
|
651
|
+
return oslg.invalid(id + " standard material?", mth, 0)
|
|
650
652
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
653
|
+
layer = layer.get()
|
|
654
|
+
k = layer.thickness() / ro
|
|
655
|
+
layer.setConductivity(k)
|
|
654
656
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
+
else: # multiple layers, adjust insulating layer thickness
|
|
658
|
+
lyr = insulatingLayer(c)
|
|
657
659
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
+
if not lyr["index"] or not lyr["type"] or not lyr["r"]:
|
|
661
|
+
return oslg.invalid(id + " construction", mth, 0)
|
|
660
662
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
+
index = lyr["index"]
|
|
664
|
+
layer = c.getLayer(index).to_StandardOpaqueMaterial()
|
|
663
665
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
+
if not layer:
|
|
667
|
+
return oslg.invalid(id + " material %d" % index, mth, 0)
|
|
666
668
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
669
|
+
layer = layer.get()
|
|
670
|
+
k = layer.conductivity()
|
|
671
|
+
d = (ro - rsi(c) + lyr["r"]) * k
|
|
670
672
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
+
if d < 0.03:
|
|
674
|
+
m = id + " adjusted material thickness"
|
|
675
|
+
return oslg.invalid(m, mth, 0)
|
|
673
676
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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.
|
|
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
|
-
|
|
1667
|
+
values = sched.timeSeries().values()
|
|
1662
1668
|
|
|
1663
|
-
|
|
1664
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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):
|
|
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
|
|
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
|
|
3716
|
+
openstudio.Point3dVector: collinears (see logs).
|
|
3704
3717
|
|
|
3705
3718
|
"""
|
|
3706
3719
|
mth = "osut.collinears"
|
|
3707
|
-
|
|
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
|
|
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
|
|
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):
|
|
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
|
|
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
|
-
|
|
5241
|
-
|
|
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(
|
|
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
|
-
|
|
5320
|
-
|
|
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
|
|
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.
|
|
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
|
-
#
|
|
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
|
|
3259
|
-
|
|
3260
|
-
|
|
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.
|
|
3291
|
-
self.
|
|
3292
|
-
self.
|
|
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.
|
|
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"],
|
|
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/
|
|
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),
|
|
5360
|
-
self.assertEqual(len(subs),
|
|
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),
|
|
5385
|
-
self.assertEqual(len(skylights),
|
|
5386
|
-
self.assertEqual(len(walls),
|
|
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),
|
|
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
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|