OpenSimula 0.5.1__py3-none-any.whl
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.
- opensimula/Child.py +19 -0
- opensimula/Component.py +214 -0
- opensimula/Iterative_process.py +47 -0
- opensimula/Message.py +19 -0
- opensimula/Parameter_container.py +80 -0
- opensimula/Parameters.py +755 -0
- opensimula/Project.py +674 -0
- opensimula/Simulation.py +212 -0
- opensimula/Variable.py +37 -0
- opensimula/__init__.py +8 -0
- opensimula/components/Building.py +511 -0
- opensimula/components/Building_surface.py +318 -0
- opensimula/components/Calculator.py +51 -0
- opensimula/components/Construction.py +309 -0
- opensimula/components/Day_schedule.py +64 -0
- opensimula/components/File_data.py +117 -0
- opensimula/components/File_met.py +1035 -0
- opensimula/components/Frame.py +35 -0
- opensimula/components/Glazing.py +122 -0
- opensimula/components/HVAC_DX_equipment.py +210 -0
- opensimula/components/HVAC_DX_system.py +254 -0
- opensimula/components/HVAC_MZW_system.py +554 -0
- opensimula/components/HVAC_SZW_system.py +338 -0
- opensimula/components/HVAC_coil_equipment.py +206 -0
- opensimula/components/HVAC_fan_equipment.py +44 -0
- opensimula/components/HVAC_perfect_system.py +137 -0
- opensimula/components/Material.py +14 -0
- opensimula/components/Opening.py +409 -0
- opensimula/components/Opening_type.py +90 -0
- opensimula/components/Solar_surface.py +114 -0
- opensimula/components/Space.py +388 -0
- opensimula/components/Space_type.py +81 -0
- opensimula/components/Surface.py +129 -0
- opensimula/components/Test_component.py +76 -0
- opensimula/components/Week_schedule.py +30 -0
- opensimula/components/Year_schedule.py +65 -0
- opensimula/components/__init__.py +40 -0
- opensimula/visual_3D/Environment_3D.py +257 -0
- opensimula/visual_3D/Polygon_3D.py +262 -0
- opensimula-0.5.1.dist-info/METADATA +49 -0
- opensimula-0.5.1.dist-info/RECORD +44 -0
- opensimula-0.5.1.dist-info/WHEEL +5 -0
- opensimula-0.5.1.dist-info/licenses/LICENSE +21 -0
- opensimula-0.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
from opensimula.Component import Component
|
|
4
|
+
from opensimula.Message import Message
|
|
5
|
+
from opensimula.Parameters import Parameter_options, Parameter_float, Parameter_float_list
|
|
6
|
+
from shapely.geometry import Polygon
|
|
7
|
+
from opensimula.Variable import Variable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Surface(Component):
|
|
11
|
+
def __init__(self, name, project):
|
|
12
|
+
Component.__init__(self, name, project)
|
|
13
|
+
# Parameters
|
|
14
|
+
self.parameter("type").value = "Surface"
|
|
15
|
+
self.add_parameter(Parameter_float("azimuth", 0, "°", min=-180, max=180)) # Surface x vs Building x -> S: 0º, E: 90º, W: -90º, N: 180º
|
|
16
|
+
self.add_parameter(Parameter_float("altitude", 0, "°", min=-90, max=90)) # Surface y vs Building y -> vertical: 0º, facing up: 90º, facing down: -90º
|
|
17
|
+
self.add_parameter(Parameter_options(
|
|
18
|
+
"shape", "RECTANGLE", ["RECTANGLE", "POLYGON"]))
|
|
19
|
+
self.add_parameter(Parameter_float("width", 1, "m", min=0.0))
|
|
20
|
+
self.add_parameter(Parameter_float("height", 1, "m", min=0.0))
|
|
21
|
+
# Building Coordinate system
|
|
22
|
+
self.add_parameter(Parameter_float_list(
|
|
23
|
+
"ref_point", [0, 0, 0], "m", min=float("-inf")))
|
|
24
|
+
self.add_parameter(Parameter_float_list(
|
|
25
|
+
"x_polygon", [0, 10, 10, 0], "m", min=float("-inf")))
|
|
26
|
+
self.add_parameter(Parameter_float_list(
|
|
27
|
+
"y_polygon", [0, 0, 10, 10], "m", min=float("-inf")))
|
|
28
|
+
|
|
29
|
+
# Variables
|
|
30
|
+
self.add_variable(Variable("E_dir_sunny", "W/m²")) # Without shadows
|
|
31
|
+
self.add_variable(Variable("E_dir", "W/m²"))
|
|
32
|
+
self.add_variable(Variable("E_dif_sunny", "W/m²")) # Without shadows
|
|
33
|
+
self.add_variable(Variable("E_dif", "W/m²"))
|
|
34
|
+
|
|
35
|
+
def check(self):
|
|
36
|
+
errors = super().check()
|
|
37
|
+
# Test if Polygon shape that x_polygon and y_polygon has the same size
|
|
38
|
+
if self.parameter("shape").value == "POLYGON":
|
|
39
|
+
if len(self.parameter("x_polygon").value) != len(self.parameter("y_polygon").value):
|
|
40
|
+
msg = f"{self.parameter('name').value}, x_polygo and y_polygon must have the same size."
|
|
41
|
+
errors.append(Message(msg, "ERROR"))
|
|
42
|
+
return errors
|
|
43
|
+
|
|
44
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
45
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def area(self):
|
|
49
|
+
if (self.parameter("shape").value == "RECTANGLE"):
|
|
50
|
+
return self.parameter("width").value * self.parameter("height").value
|
|
51
|
+
elif (self.parameter("shape").value == "POLYGON"):
|
|
52
|
+
polygon = []
|
|
53
|
+
n = len(self.parameter("x_polygon").value)
|
|
54
|
+
for i in range(0, n):
|
|
55
|
+
polygon.append([self.parameter("x_polygon").value[i],
|
|
56
|
+
self.parameter("y_polygon").value[i]])
|
|
57
|
+
return Polygon(polygon).area
|
|
58
|
+
|
|
59
|
+
# exterior normal vector
|
|
60
|
+
def orientation_angle(self, angle, side, coordinate_system="global"):
|
|
61
|
+
if angle == "azimuth":
|
|
62
|
+
az = self.parameter("azimuth").value
|
|
63
|
+
if self.get_building() is not None and coordinate_system == "global":
|
|
64
|
+
azi_building = self.get_building().parameter("azimuth").value
|
|
65
|
+
az = az + azi_building
|
|
66
|
+
if side == 0:
|
|
67
|
+
return az
|
|
68
|
+
elif side == 1:
|
|
69
|
+
if az > 0:
|
|
70
|
+
return az-180
|
|
71
|
+
else:
|
|
72
|
+
return az+180
|
|
73
|
+
elif angle == "altitude":
|
|
74
|
+
alt = self.parameter("altitude").value
|
|
75
|
+
if side == 0:
|
|
76
|
+
return alt
|
|
77
|
+
elif side == 1:
|
|
78
|
+
return -alt
|
|
79
|
+
|
|
80
|
+
def get_building(self): # Default no building
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def radiant_property(self, prop, radiation_type, side, theta=0):
|
|
84
|
+
"""It must be redefined by child classes
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
prop (_type_): _description_
|
|
88
|
+
radiation_type (_type_): _description_
|
|
89
|
+
side (_type_): _description_
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
_type_: _description_
|
|
93
|
+
"""
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
def get_origin(self, coordinate_system="global"):
|
|
97
|
+
if self.get_building() is not None:
|
|
98
|
+
if coordinate_system == "global":
|
|
99
|
+
az_b = math.radians(self.get_building().parameter("azimuth").value)
|
|
100
|
+
local_origin = self.parameter("ref_point").value
|
|
101
|
+
global_origin = [local_origin[0]*math.cos(az_b)-local_origin[1]*math.sin(az_b),
|
|
102
|
+
local_origin[0]*math.sin(az_b) +
|
|
103
|
+
local_origin[1]*math.cos(az_b),
|
|
104
|
+
local_origin[2]]
|
|
105
|
+
return global_origin
|
|
106
|
+
elif coordinate_system == "building":
|
|
107
|
+
return np.array(self.parameter("ref_point").value)+ np.array(self.get_building().parameter("ref_point").value)
|
|
108
|
+
else:
|
|
109
|
+
return self.parameter("ref_point").value
|
|
110
|
+
else:
|
|
111
|
+
return self.parameter("ref_point").value
|
|
112
|
+
|
|
113
|
+
def get_polygon_2D(self): # Get polygon_2D
|
|
114
|
+
if (self.parameter("shape").value == "RECTANGLE"):
|
|
115
|
+
w = self.parameter("width").value
|
|
116
|
+
h = self.parameter("height").value
|
|
117
|
+
return [[0, 0], [w, 0], [w, h], [0, h]]
|
|
118
|
+
elif (self.parameter("shape").value == "POLYGON"):
|
|
119
|
+
polygon2D = []
|
|
120
|
+
n = len(self.parameter("x_polygon").value)
|
|
121
|
+
for i in range(0, n):
|
|
122
|
+
polygon2D.append([self.parameter("x_polygon").value[i],
|
|
123
|
+
self.parameter("y_polygon").value[i]])
|
|
124
|
+
return polygon2D
|
|
125
|
+
|
|
126
|
+
def get_polygon_3D(self): # Global coordenates
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from opensimula.Parameters import (
|
|
2
|
+
Parameter_boolean,
|
|
3
|
+
Parameter_float,
|
|
4
|
+
Parameter_int,
|
|
5
|
+
Parameter_string,
|
|
6
|
+
Parameter_options,
|
|
7
|
+
Parameter_component,
|
|
8
|
+
Parameter_boolean_list,
|
|
9
|
+
Parameter_string_list,
|
|
10
|
+
Parameter_int_list,
|
|
11
|
+
Parameter_float_list,
|
|
12
|
+
Parameter_options_list,
|
|
13
|
+
Parameter_component_list,
|
|
14
|
+
Parameter_variable,
|
|
15
|
+
Parameter_variable_list,
|
|
16
|
+
Parameter_math_exp,
|
|
17
|
+
Parameter_math_exp_list
|
|
18
|
+
)
|
|
19
|
+
from opensimula.Component import Component
|
|
20
|
+
from opensimula.Variable import Variable
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Test_component(Component):
|
|
24
|
+
"""Component for development testing"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, name, project):
|
|
27
|
+
Component.__init__(self, name, project)
|
|
28
|
+
self.parameter("type").value = "Test_component"
|
|
29
|
+
self.parameter("description").value = "Dummy component for testing"
|
|
30
|
+
|
|
31
|
+
self.add_parameter(Parameter_boolean("boolean", False))
|
|
32
|
+
self.add_parameter(Parameter_string("string", "Hello World"))
|
|
33
|
+
self.add_parameter(Parameter_int("int", 100, "h"))
|
|
34
|
+
self.add_parameter(Parameter_float("float", 0.1, "m"))
|
|
35
|
+
self.add_parameter(Parameter_options(
|
|
36
|
+
"options", "One", ["One", "Two", "Three"]))
|
|
37
|
+
self.add_parameter(Parameter_component("component", "not_defined"))
|
|
38
|
+
self.add_parameter(Parameter_variable("variable"))
|
|
39
|
+
self.add_parameter(Parameter_math_exp("math_exp"))
|
|
40
|
+
self.add_parameter(Parameter_boolean_list(
|
|
41
|
+
"boolean_list", [True, False]))
|
|
42
|
+
self.add_parameter(Parameter_string_list(
|
|
43
|
+
"string_list", ["Hello World 1", "Hello World 2"]))
|
|
44
|
+
self.add_parameter(Parameter_int_list("int_list", [50, 100], "h"))
|
|
45
|
+
self.add_parameter(Parameter_float_list("float_list", [0.1, 0.2], "m"))
|
|
46
|
+
self.add_parameter(Parameter_options_list(
|
|
47
|
+
"options_list", ["One", "Two"], ["One", "Two", "Three"]))
|
|
48
|
+
self.add_parameter(Parameter_component_list(
|
|
49
|
+
"component_list", ["not_defined", "not_defined"]))
|
|
50
|
+
self.add_parameter(Parameter_variable_list("variable_list"))
|
|
51
|
+
self.add_parameter(Parameter_math_exp_list("math_exp_list"))
|
|
52
|
+
|
|
53
|
+
# Variable
|
|
54
|
+
self.add_variable(Variable("t", unit="s"))
|
|
55
|
+
self.add_variable(Variable("exp_1"))
|
|
56
|
+
self.add_variable(Variable("exp_2"))
|
|
57
|
+
self.add_variable(Variable("exp_3"))
|
|
58
|
+
|
|
59
|
+
self._initial_date_ = None
|
|
60
|
+
|
|
61
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
62
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
63
|
+
|
|
64
|
+
def pre_iteration(self, time_index, date, daylight_saving):
|
|
65
|
+
super().pre_iteration(time_index, date, daylight_saving)
|
|
66
|
+
if time_index == 0:
|
|
67
|
+
self._initial_date_ = date
|
|
68
|
+
self.variable("t").values[time_index] = (
|
|
69
|
+
date - self._initial_date_).total_seconds()
|
|
70
|
+
|
|
71
|
+
variables_dic = {}
|
|
72
|
+
for key, var in self._variables_.items():
|
|
73
|
+
variables_dic[key] = var.values[time_index]
|
|
74
|
+
|
|
75
|
+
self.variable("exp_1").values[time_index] = self.parameter(
|
|
76
|
+
"math_exp").evaluate(variables_dic)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from opensimula.Message import Message
|
|
2
|
+
from opensimula.Parameters import Parameter_component_list
|
|
3
|
+
from opensimula.Component import Component
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Week_schedule(Component):
|
|
7
|
+
def __init__(self, name, project):
|
|
8
|
+
Component.__init__(self, name, project)
|
|
9
|
+
self.parameter("type").value = "Week_schedule"
|
|
10
|
+
self.parameter("description").value = "Time schedule for a week"
|
|
11
|
+
self.add_parameter(Parameter_component_list(
|
|
12
|
+
"days_schedules", ["not_defined"], ["Day_schedule"]))
|
|
13
|
+
|
|
14
|
+
def check(self):
|
|
15
|
+
errors = super().check()
|
|
16
|
+
# Test if 1 or 7 has been defined
|
|
17
|
+
if (
|
|
18
|
+
len(self.parameter("days_schedules").value) != 1
|
|
19
|
+
and len(self.parameter("days_schedules").value) != 7
|
|
20
|
+
):
|
|
21
|
+
msg = f"{self.parameter('name').value}, days_schedules parameter must contain 1 or 7 Day_schedule components"
|
|
22
|
+
errors.append(Message(msg, "ERROR"))
|
|
23
|
+
return errors
|
|
24
|
+
|
|
25
|
+
def get_value(self, date):
|
|
26
|
+
if len(self.parameter("days_schedules").value) == 1:
|
|
27
|
+
return self.parameter("days_schedules").component[0].get_value(date)
|
|
28
|
+
else:
|
|
29
|
+
index = date.weekday()
|
|
30
|
+
return self.parameter("days_schedules").component[index].get_value(date)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from bisect import bisect
|
|
3
|
+
from opensimula.Message import Message
|
|
4
|
+
from opensimula.Parameters import Parameter_component_list, Parameter_string_list
|
|
5
|
+
from opensimula.Component import Component
|
|
6
|
+
from opensimula.Variable import Variable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Year_schedule(Component):
|
|
10
|
+
def __init__(self, name, project):
|
|
11
|
+
Component.__init__(self, name, project)
|
|
12
|
+
self.parameter("type").value = "Year_schedule"
|
|
13
|
+
self.parameter("description").value = "Time schedule for a year"
|
|
14
|
+
self.add_parameter(Parameter_string_list("periods", ["01/06"]))
|
|
15
|
+
self.add_parameter(
|
|
16
|
+
Parameter_component_list(
|
|
17
|
+
"weeks_schedules", ["not_defined",
|
|
18
|
+
"not_defined"], ["Week_schedule"]
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
# Create Variable
|
|
22
|
+
self.add_variable(Variable(
|
|
23
|
+
"values", "", "Values obtained using year, weeks and days schedules for each of the simulation time steps."))
|
|
24
|
+
|
|
25
|
+
def check(self):
|
|
26
|
+
errors = super().check()
|
|
27
|
+
if (
|
|
28
|
+
len(self.parameter("periods").value)
|
|
29
|
+
!= len(self.parameter("weeks_schedules").value) - 1
|
|
30
|
+
):
|
|
31
|
+
msg = f"{self.parameter('name').value}, periods size must be weeks_schedules size minus 1"
|
|
32
|
+
errors.append(Message(msg, "ERROR"))
|
|
33
|
+
# Check periods format
|
|
34
|
+
try:
|
|
35
|
+
days = []
|
|
36
|
+
for period in self.parameter("periods").value:
|
|
37
|
+
datetime = dt.datetime.strptime(period, "%d/%m")
|
|
38
|
+
days.append(datetime.timetuple().tm_yday)
|
|
39
|
+
if sorted(days) != days:
|
|
40
|
+
msg = f"{self.parameter('name').value}, periods are not ordered"
|
|
41
|
+
errors.append(Message(msg, "ERROR"))
|
|
42
|
+
except ValueError:
|
|
43
|
+
msg = f"{self.parameter('name').value}, periods does not match format (dd/mm)"
|
|
44
|
+
errors.append(Message(msg, "ERROR"))
|
|
45
|
+
return errors
|
|
46
|
+
|
|
47
|
+
def pre_simulation(self, n_time_steps, delta_t):
|
|
48
|
+
super().pre_simulation(n_time_steps, delta_t)
|
|
49
|
+
|
|
50
|
+
# Create array of periods_days
|
|
51
|
+
self._periods_days_ = []
|
|
52
|
+
for period in self.parameter("periods").value:
|
|
53
|
+
datetime = dt.datetime.strptime(period, "%d/%m")
|
|
54
|
+
self._periods_days_.append(datetime.timetuple().tm_yday)
|
|
55
|
+
|
|
56
|
+
def pre_iteration(self, time_index, date, daylight_saving):
|
|
57
|
+
super().pre_iteration(time_index, date, daylight_saving)
|
|
58
|
+
if daylight_saving: # adding 1 h
|
|
59
|
+
date = date + dt.timedelta(0, 3600)
|
|
60
|
+
self.variable("values").values[time_index] = self.get_value(date)
|
|
61
|
+
|
|
62
|
+
def get_value(self, date):
|
|
63
|
+
year_day = date.timetuple().tm_yday
|
|
64
|
+
index = bisect(self._periods_days_, year_day)
|
|
65
|
+
return self.parameter("weeks_schedules").component[index].get_value(date)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from opensimula.components.Test_component import Test_component
|
|
2
|
+
from opensimula.components.Material import Material
|
|
3
|
+
from opensimula.components.Glazing import Glazing
|
|
4
|
+
from opensimula.components.Frame import Frame
|
|
5
|
+
from opensimula.components.Construction import Construction
|
|
6
|
+
from opensimula.components.Opening_type import Opening_type
|
|
7
|
+
from opensimula.components.File_data import File_data
|
|
8
|
+
from opensimula.components.Day_schedule import Day_schedule
|
|
9
|
+
from opensimula.components.Week_schedule import Week_schedule
|
|
10
|
+
from opensimula.components.Year_schedule import Year_schedule
|
|
11
|
+
from opensimula.components.File_met import File_met
|
|
12
|
+
from opensimula.components.Space_type import Space_type
|
|
13
|
+
from opensimula.components.Building import Building
|
|
14
|
+
from opensimula.components.Space import Space
|
|
15
|
+
from opensimula.components.Building_surface import Building_surface
|
|
16
|
+
from opensimula.components.Solar_surface import Solar_surface
|
|
17
|
+
from opensimula.components.Opening import Opening
|
|
18
|
+
from opensimula.components.Calculator import Calculator
|
|
19
|
+
from opensimula.components.HVAC_DX_equipment import HVAC_DX_equipment
|
|
20
|
+
from opensimula.components.HVAC_coil_equipment import HVAC_coil_equipment
|
|
21
|
+
from opensimula.components.HVAC_fan_equipment import HVAC_fan_equipment
|
|
22
|
+
from opensimula.components.HVAC_DX_system import HVAC_DX_system
|
|
23
|
+
from opensimula.components.HVAC_SZW_system import HVAC_SZW_system
|
|
24
|
+
from opensimula.components.HVAC_MZW_system import HVAC_MZW_system
|
|
25
|
+
from opensimula.components.HVAC_perfect_system import HVAC_perfect_system
|
|
26
|
+
|
|
27
|
+
DEFAULT_COMPONENTS_ORDER = [
|
|
28
|
+
"Space_type",
|
|
29
|
+
"Building_surface",
|
|
30
|
+
"Solar_surface",
|
|
31
|
+
"Opening",
|
|
32
|
+
"Space",
|
|
33
|
+
"Building",
|
|
34
|
+
"HVAC_MZW_system",
|
|
35
|
+
"HVAC_perfect_system",
|
|
36
|
+
"HVAC_DX_system",
|
|
37
|
+
"HVAC_SZW_system",
|
|
38
|
+
"Calculator"
|
|
39
|
+
]
|
|
40
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import numpy as np
|
|
3
|
+
import math
|
|
4
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import vedo as vedo
|
|
7
|
+
|
|
8
|
+
class Environment_3D:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.pol_3D = []
|
|
11
|
+
self.pol_sunny = []
|
|
12
|
+
self.pol_shadows = []
|
|
13
|
+
self.sunny_fraction = []
|
|
14
|
+
self.solar_tables_calculated = False
|
|
15
|
+
|
|
16
|
+
def add_polygon_3D(self, polygon_3D):
|
|
17
|
+
self.pol_3D.append(polygon_3D)
|
|
18
|
+
self.sunny_fraction.append(1.0)
|
|
19
|
+
|
|
20
|
+
def get_vedo_meshes(self, polygons_type="initial"):
|
|
21
|
+
meshes = []
|
|
22
|
+
if polygons_type == "initial":
|
|
23
|
+
for polygon_3D in self.pol_3D:
|
|
24
|
+
if polygon_3D.visible:
|
|
25
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
26
|
+
mesh.polygon_name = polygon_3D.name
|
|
27
|
+
meshes.append(mesh)
|
|
28
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
29
|
+
elif polygons_type == "sunny":
|
|
30
|
+
for polygon_3D in self.pol_sunny:
|
|
31
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
32
|
+
mesh.polygon_name = polygon_3D.name
|
|
33
|
+
meshes.append(mesh)
|
|
34
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
35
|
+
elif polygons_type == "shadows":
|
|
36
|
+
for polygon_3D in self.pol_shadows:
|
|
37
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
38
|
+
mesh.polygon_name = polygon_3D.name
|
|
39
|
+
meshes.append(mesh)
|
|
40
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
41
|
+
elif polygons_type == "sunny+shadows":
|
|
42
|
+
for polygon_3D in self.pol_sunny:
|
|
43
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
44
|
+
mesh.polygon_name = polygon_3D.name
|
|
45
|
+
meshes.append(mesh)
|
|
46
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
47
|
+
for polygon_3D in self.pol_shadows:
|
|
48
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
49
|
+
mesh.polygon_name = polygon_3D.name
|
|
50
|
+
meshes.append(mesh)
|
|
51
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
52
|
+
elif polygons_type == "Building_shadows":
|
|
53
|
+
for polygon_3D in self.pol_sunny:
|
|
54
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
55
|
+
mesh.polygon_name = polygon_3D.name
|
|
56
|
+
meshes.append(mesh)
|
|
57
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
58
|
+
for polygon_3D in self.pol_shadows:
|
|
59
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
60
|
+
mesh.polygon_name = polygon_3D.name
|
|
61
|
+
meshes.append(mesh)
|
|
62
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
63
|
+
for polygon_3D in self.pol_3D:
|
|
64
|
+
if polygon_3D.visible and polygon_3D.calculate_shadows == False:
|
|
65
|
+
mesh = polygon_3D.get_vedo_mesh()
|
|
66
|
+
mesh.polygon_name = polygon_3D.name
|
|
67
|
+
meshes.append(mesh)
|
|
68
|
+
meshes.append(mesh.silhouette("2d").c("black").linewidth(5))
|
|
69
|
+
|
|
70
|
+
return meshes
|
|
71
|
+
|
|
72
|
+
def show(self, polygons_type="initial"):
|
|
73
|
+
vedo.settings.default_backend = 'vtk'
|
|
74
|
+
meshes = self.get_vedo_meshes(polygons_type)
|
|
75
|
+
|
|
76
|
+
text_obj = [None] # Usamos una lista para que sea mutable en el callback
|
|
77
|
+
|
|
78
|
+
def on_left_click(evt):
|
|
79
|
+
if text_obj[0] is not None:
|
|
80
|
+
plt.remove(text_obj[0]) # Borra el texto anterior
|
|
81
|
+
msh = evt.object
|
|
82
|
+
if not msh:
|
|
83
|
+
text_obj[0] = vedo.Text2D(" ", pos='top-left')
|
|
84
|
+
else:
|
|
85
|
+
text_obj[0] = vedo.Text2D(f"{msh.polygon_name}", pos='top-left')
|
|
86
|
+
plt.add(text_obj[0])
|
|
87
|
+
plt.render()
|
|
88
|
+
|
|
89
|
+
plt = vedo.Plotter(title="opensimula")
|
|
90
|
+
plt.add_callback('mouse click', on_left_click)
|
|
91
|
+
plt.show(*meshes, axes=1, viewup="z").close()
|
|
92
|
+
|
|
93
|
+
def show_animation(self, texts, cosines , polygons_type="initial" ):
|
|
94
|
+
vedo.settings.default_backend = 'vtk'
|
|
95
|
+
meshes = []
|
|
96
|
+
def slider_func(widget, event):
|
|
97
|
+
idx = int(widget.value)
|
|
98
|
+
widget.title = texts[idx]
|
|
99
|
+
plt.remove( meshes.pop(0))
|
|
100
|
+
cos = cosines[idx]
|
|
101
|
+
self.calculate_shadows(cos, create_polygons=True)
|
|
102
|
+
meshes.append(self.get_vedo_meshes(polygons_type))
|
|
103
|
+
plt.add(meshes[0])
|
|
104
|
+
|
|
105
|
+
# Show first frame
|
|
106
|
+
self.calculate_shadows(cosines[0], create_polygons=True)
|
|
107
|
+
meshes.append(self.get_vedo_meshes(polygons_type))
|
|
108
|
+
plt = vedo.Plotter(title="opensimula", axes=1)
|
|
109
|
+
plt.add(meshes[0])
|
|
110
|
+
plt.add_slider(
|
|
111
|
+
slider_func,
|
|
112
|
+
0, len(cosines)-1, # slider range
|
|
113
|
+
value=0, # initial value
|
|
114
|
+
pos="bottom-right", # position of the slider
|
|
115
|
+
title=texts[0]
|
|
116
|
+
)
|
|
117
|
+
plt.show(viewup="z").close()
|
|
118
|
+
|
|
119
|
+
def delete_shadows(self):
|
|
120
|
+
self.pol_sunny = []
|
|
121
|
+
self.pol_shadows = []
|
|
122
|
+
self.sunny_fraction = []
|
|
123
|
+
|
|
124
|
+
def calculate_shadows(self, sun_position, create_polygons=True):
|
|
125
|
+
self.sunny_fraction = []
|
|
126
|
+
if create_polygons:
|
|
127
|
+
self.pol_sunny = []
|
|
128
|
+
self.pol_shadows = []
|
|
129
|
+
|
|
130
|
+
for polygon in self.pol_3D:
|
|
131
|
+
if polygon.calculate_shadows:
|
|
132
|
+
sunny_polygons, shadow_polygons = polygon.get_sunny_shadow_polygon3D(
|
|
133
|
+
self, sun_position
|
|
134
|
+
)
|
|
135
|
+
if sunny_polygons is not None:
|
|
136
|
+
sunny_area = 0
|
|
137
|
+
for sunny_polygon in sunny_polygons:
|
|
138
|
+
if create_polygons:
|
|
139
|
+
self.pol_sunny.append(sunny_polygon)
|
|
140
|
+
sunny_area += sunny_polygon.area
|
|
141
|
+
self.sunny_fraction.append(sunny_area / polygon.area)
|
|
142
|
+
else:
|
|
143
|
+
self.sunny_fraction.append(0) # No sunny area
|
|
144
|
+
if create_polygons:
|
|
145
|
+
if shadow_polygons is not None:
|
|
146
|
+
for shadow_polygon in shadow_polygons:
|
|
147
|
+
self.pol_shadows.append(shadow_polygon)
|
|
148
|
+
|
|
149
|
+
def get_sunny_polygon_list(self):
|
|
150
|
+
sunny_polygons = []
|
|
151
|
+
for polygon in self.pol_3D:
|
|
152
|
+
if polygon.calculate_shadows:
|
|
153
|
+
sunny_polygons.append(polygon)
|
|
154
|
+
return sunny_polygons
|
|
155
|
+
|
|
156
|
+
def get_sunny_index(self, name):
|
|
157
|
+
sunny_polygons = self.get_sunny_polygon_list()
|
|
158
|
+
for i in range(len(sunny_polygons)):
|
|
159
|
+
if sunny_polygons[i].name == name:
|
|
160
|
+
return i
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def calculate_solar_tables(self):
|
|
164
|
+
self._calculate_shadow_interpolation_table()
|
|
165
|
+
self._calculate_diffuse_shadow()
|
|
166
|
+
self.solar_tables_calculated = True
|
|
167
|
+
|
|
168
|
+
def _calculate_shadow_interpolation_table(self):
|
|
169
|
+
self.shadow_azimuth_grid = np.linspace(0, 350, 36)
|
|
170
|
+
self.shadow_altitude_grid = np.linspace(-85, 85, 18)
|
|
171
|
+
sunny_polygons = self.get_sunny_polygon_list()
|
|
172
|
+
self.sunny_fraction_tables = np.zeros((len(sunny_polygons), 36, 18))
|
|
173
|
+
j = 0
|
|
174
|
+
for azimuth in self.shadow_azimuth_grid:
|
|
175
|
+
azi_rd = math.radians(azimuth)
|
|
176
|
+
k = 0
|
|
177
|
+
for altitude in self.shadow_altitude_grid:
|
|
178
|
+
alt_rd = math.radians(altitude)
|
|
179
|
+
sun_position = np.array(
|
|
180
|
+
[
|
|
181
|
+
math.cos(alt_rd) * math.sin(azi_rd),
|
|
182
|
+
-math.cos(alt_rd) * math.cos(azi_rd),
|
|
183
|
+
math.sin(alt_rd),
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self.calculate_shadows(sun_position, create_polygons=False)
|
|
188
|
+
sunny_frac = self.sunny_fraction
|
|
189
|
+
for i in range(len(sunny_frac)):
|
|
190
|
+
self.sunny_fraction_tables[i][j][k] = sunny_frac[i]
|
|
191
|
+
k = k + 1
|
|
192
|
+
j = j + 1
|
|
193
|
+
|
|
194
|
+
self.sunny_interpolation_functions = []
|
|
195
|
+
for i in range(0, len(sunny_polygons)):
|
|
196
|
+
self.sunny_interpolation_functions.append(
|
|
197
|
+
RegularGridInterpolator(
|
|
198
|
+
(self.shadow_azimuth_grid, self.shadow_altitude_grid),
|
|
199
|
+
self.sunny_fraction_tables[i],
|
|
200
|
+
bounds_error=False,
|
|
201
|
+
fill_value=None,
|
|
202
|
+
method="cubic",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def _calculate_diffuse_shadow(self):
|
|
207
|
+
def integral(i,polygon_i):
|
|
208
|
+
sunny_value = 0
|
|
209
|
+
shadow_value = 0
|
|
210
|
+
n = 0
|
|
211
|
+
for j in range(len(self.shadow_azimuth_grid)):
|
|
212
|
+
for k in range(len(self.shadow_altitude_grid)):
|
|
213
|
+
theta = polygon_i.get_angle_with_normal(
|
|
214
|
+
self.shadow_azimuth_grid[j], self.shadow_altitude_grid[k]
|
|
215
|
+
)
|
|
216
|
+
if theta < math.pi / 2:
|
|
217
|
+
f = 0.5 * math.sin(2 * theta)
|
|
218
|
+
sunny_value = sunny_value + f
|
|
219
|
+
shadow_value = (
|
|
220
|
+
shadow_value + f * self.sunny_fraction_tables[i][j][k]
|
|
221
|
+
)
|
|
222
|
+
n = n + 1
|
|
223
|
+
return shadow_value / sunny_value
|
|
224
|
+
|
|
225
|
+
self.shadow_diffuse_fraction = []
|
|
226
|
+
sunny_polygon_list = self.get_sunny_polygon_list()
|
|
227
|
+
for i in range(0, len(sunny_polygon_list)):
|
|
228
|
+
polygon_i = sunny_polygon_list[i]
|
|
229
|
+
self.shadow_diffuse_fraction.append(integral(i, polygon_i))
|
|
230
|
+
|
|
231
|
+
def get_diffuse_sunny_fraction(self, sunny_i):
|
|
232
|
+
if self.solar_tables_calculated == True:
|
|
233
|
+
return self.shadow_diffuse_fraction[sunny_i]
|
|
234
|
+
else:
|
|
235
|
+
return 1
|
|
236
|
+
|
|
237
|
+
def get_direct_interpolated_sunny_fraction(self, sunny_i, azi, alt):
|
|
238
|
+
if self.solar_tables_calculated == True:
|
|
239
|
+
if azi < 0:
|
|
240
|
+
azi = azi + 360
|
|
241
|
+
return self.sunny_interpolation_functions[sunny_i]((azi, alt))
|
|
242
|
+
else:
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
def get_direct_sunny_fraction(self, sunny_i):
|
|
246
|
+
if self.sunny_fraction == []:
|
|
247
|
+
return 1
|
|
248
|
+
else:
|
|
249
|
+
return self.sunny_fraction[sunny_i]
|
|
250
|
+
|
|
251
|
+
def show_sunny_fraction(self, sunny_i):
|
|
252
|
+
fig, ax = plt.subplots()
|
|
253
|
+
X, Y = np.meshgrid(self.shadow_azimuth_grid, self.shadow_altitude_grid)
|
|
254
|
+
ax.imshow(self.sunny_fraction_tables[sunny_i], vmin=0, vmax=1)
|
|
255
|
+
plt.show()
|
|
256
|
+
|
|
257
|
+
|