OpenSimula 0.8.0__tar.gz → 0.8.5__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.
- {opensimula-0.8.0 → opensimula-0.8.5}/PKG-INFO +3 -4
- {opensimula-0.8.0 → opensimula-0.8.5}/pyproject.toml +8 -5
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Component.py +22 -15
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Parameters.py +31 -5
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Project.py +90 -89
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Simulation.py +0 -63
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Building_surface.py +41 -1
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Chiller_heat_pump.py +1 -1
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Construction.py +5 -3
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Opening.py +2 -1
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Opening_type.py +3 -3
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Space.py +118 -46
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Surface.py +14 -0
- opensimula-0.8.5/src/OpenSimula/editor/__init__.py +19 -0
- opensimula-0.8.5/src/OpenSimula/editor/schema.py +229 -0
- opensimula-0.8.5/src/OpenSimula/editor/static/editor.css +388 -0
- opensimula-0.8.5/src/OpenSimula/editor/static/editor.js +1047 -0
- opensimula-0.8.5/src/OpenSimula/editor/validation.py +91 -0
- opensimula-0.8.5/src/OpenSimula/editor/widget.py +246 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/visual_3D/Environment_3D.py +113 -28
- opensimula-0.8.5/src/OpenSimula/visual_3D/Polygon_3D.py +370 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula.egg-info/PKG-INFO +3 -4
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula.egg-info/SOURCES.txt +10 -1
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula.egg-info/requires.txt +2 -3
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Case_600FF.py +4 -3
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Case_610.py +5 -4
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_HVAC_DX_system.py +13 -12
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_HVAC_FAN_COIL_water_system.py +11 -10
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Project.py +43 -0
- opensimula-0.8.5/test/test_editor_schema.py +202 -0
- opensimula-0.8.5/test/test_editor_widget.py +513 -0
- opensimula-0.8.5/test/test_underground_repeated_simulation.py +202 -0
- opensimula-0.8.0/src/OpenSimula/visual_3D/Polygon_3D.py +0 -257
- {opensimula-0.8.0 → opensimula-0.8.5}/LICENSE +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/README.md +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/setup.cfg +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Child.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Iterative_process.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Message.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Parameter_container.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/Variable.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/__init__.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Building.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Calculator.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/DX_unit.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Day_schedule.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Fan.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/File_data.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/File_met.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Frame.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Glazing.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/HVAC_DX_system.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/HVAC_MZW_system.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/HVAC_SZW_system.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/HVAC_perfect_system.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/HVAC_water_system.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Material.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Pump.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Solar_surface.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Space_type.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Test_component.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Water_coil.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Week_schedule.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/Year_schedule.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula/components/__init__.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula.egg-info/dependency_links.txt +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/src/OpenSimula.egg-info/top_level.txt +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Calculator.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Construction.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_File_data.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_File_met.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_HVAC_DX_Equipment.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_HVAC_water_system_process.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Iterative_process.py +0 -0
- {opensimula-0.8.0 → opensimula-0.8.5}/test/test_Schedules.py +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opensimula
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.5
|
|
4
4
|
Summary: Component-based time simulation environment in Python
|
|
5
5
|
Author-email: "Juan F. Coronel" <jfc@us.es>
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist:
|
|
10
|
-
Requires-Dist:
|
|
11
|
-
Requires-Dist: dash-bootstrap-components>=2.0.4
|
|
9
|
+
Requires-Dist: anywidget>=0.11.0
|
|
10
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
12
11
|
Requires-Dist: nbformat>=5.10.4
|
|
13
12
|
Requires-Dist: numpy>=2.3.3
|
|
14
13
|
Requires-Dist: openpyxl>=3.1.5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "opensimula"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.5"
|
|
4
4
|
requires-python = ">=3.12"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Juan F. Coronel", email="jfc@us.es" },
|
|
@@ -8,9 +8,8 @@ authors = [
|
|
|
8
8
|
description = "Component-based time simulation environment in Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"dash-bootstrap-components>=2.0.4",
|
|
11
|
+
"anywidget>=0.11.0",
|
|
12
|
+
"jsonschema>=4.26.0",
|
|
14
13
|
"nbformat>=5.10.4",
|
|
15
14
|
"numpy>=2.3.3",
|
|
16
15
|
"openpyxl>=3.1.5",
|
|
@@ -20,7 +19,7 @@ dependencies = [
|
|
|
20
19
|
"scipy>=1.16.2",
|
|
21
20
|
"shapely>=2.1.1",
|
|
22
21
|
"tqdm>=4.67.1",
|
|
23
|
-
"vedo>=2025.5.4",
|
|
22
|
+
"vedo>=2025.5.4",
|
|
24
23
|
]
|
|
25
24
|
|
|
26
25
|
[dependency-groups]
|
|
@@ -31,6 +30,10 @@ dev = [
|
|
|
31
30
|
"marimo>=0.19.0",
|
|
32
31
|
]
|
|
33
32
|
|
|
33
|
+
[tool.setuptools.package-data]
|
|
34
|
+
# The widget loads these at runtime; without this they are left out of the wheel.
|
|
35
|
+
"opensimula.editor" = ["static/*.js", "static/*.css"]
|
|
36
|
+
|
|
34
37
|
[build-system]
|
|
35
38
|
requires = ["setuptools>=61.2", "wheel"]
|
|
36
39
|
|
|
@@ -82,36 +82,43 @@ class Component(Parameter_container):
|
|
|
82
82
|
|
|
83
83
|
# ____________ Functions that must be overwriten for time simulation _________________
|
|
84
84
|
|
|
85
|
-
def get_all_referenced_components(self):
|
|
85
|
+
def get_all_referenced_components(self, _seen=None):
|
|
86
86
|
"""Get list of all referenced components, first itself. Look recursively at the referenced components
|
|
87
87
|
|
|
88
|
+
Args:
|
|
89
|
+
_seen (set, optional): components already walked, used internally.
|
|
90
|
+
References can form a cycle, if only because a wrong name gets
|
|
91
|
+
typed into a reference, and without remembering where it has
|
|
92
|
+
been the walk follows it until the stack runs out.
|
|
93
|
+
|
|
88
94
|
Returns:
|
|
89
95
|
component_list (component[])
|
|
90
96
|
"""
|
|
97
|
+
if _seen is None:
|
|
98
|
+
_seen = set()
|
|
99
|
+
if id(self) in _seen:
|
|
100
|
+
return []
|
|
101
|
+
_seen.add(id(self))
|
|
102
|
+
|
|
91
103
|
comp_list = []
|
|
104
|
+
|
|
105
|
+
def walk(component):
|
|
106
|
+
if component is not None:
|
|
107
|
+
comp_list.extend(component.get_all_referenced_components(_seen))
|
|
108
|
+
|
|
92
109
|
for key, value in self.parameter_dict().items():
|
|
93
110
|
if value.type == "Parameter_component":
|
|
94
|
-
|
|
95
|
-
sublist = value.component.get_all_referenced_components()
|
|
96
|
-
for subcomp in sublist:
|
|
97
|
-
comp_list.append(subcomp)
|
|
111
|
+
walk(value.component)
|
|
98
112
|
elif value.type == "Parameter_component_list":
|
|
99
113
|
for comp in value.component:
|
|
100
|
-
|
|
101
|
-
sublist = comp.get_all_referenced_components()
|
|
102
|
-
for subcomp in sublist:
|
|
103
|
-
comp_list.append(subcomp)
|
|
114
|
+
walk(comp)
|
|
104
115
|
if value.type == "Parameter_variable":
|
|
105
116
|
if value.variable is not None:
|
|
106
|
-
|
|
107
|
-
for subcomp in sublist:
|
|
108
|
-
comp_list.append(subcomp)
|
|
117
|
+
walk(value.variable.parent)
|
|
109
118
|
elif value.type == "Parameter_variable_list":
|
|
110
119
|
for var in value.variable:
|
|
111
120
|
if var is not None:
|
|
112
|
-
|
|
113
|
-
for subcomp in sublist:
|
|
114
|
-
comp_list.append(subcomp)
|
|
121
|
+
walk(var.parent)
|
|
115
122
|
comp_list.append(self)
|
|
116
123
|
return comp_list
|
|
117
124
|
|
|
@@ -184,7 +184,9 @@ class Parameter_string_list(Parameter):
|
|
|
184
184
|
else:
|
|
185
185
|
for el in value:
|
|
186
186
|
el = str(el)
|
|
187
|
-
|
|
187
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
188
|
+
# outside, and would be shared by every parameter assigned from it
|
|
189
|
+
self._value_ = list(value)
|
|
188
190
|
|
|
189
191
|
|
|
190
192
|
# _____________ Parameter_int ___________________________
|
|
@@ -201,6 +203,14 @@ class Parameter_int(Parameter):
|
|
|
201
203
|
def unit(self):
|
|
202
204
|
return self._unit_
|
|
203
205
|
|
|
206
|
+
@property
|
|
207
|
+
def min(self):
|
|
208
|
+
return self._min_
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def max(self):
|
|
212
|
+
return self._max_
|
|
213
|
+
|
|
204
214
|
@property
|
|
205
215
|
def value(self):
|
|
206
216
|
return self._value_
|
|
@@ -235,6 +245,14 @@ class Parameter_int_list(Parameter):
|
|
|
235
245
|
def unit(self):
|
|
236
246
|
return self._unit_
|
|
237
247
|
|
|
248
|
+
@property
|
|
249
|
+
def min(self):
|
|
250
|
+
return self._min_
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def max(self):
|
|
254
|
+
return self._max_
|
|
255
|
+
|
|
238
256
|
@property
|
|
239
257
|
def value(self):
|
|
240
258
|
return self._value_
|
|
@@ -389,7 +407,9 @@ class Parameter_options_list(Parameter):
|
|
|
389
407
|
else:
|
|
390
408
|
for el in value:
|
|
391
409
|
el = str(el)
|
|
392
|
-
|
|
410
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
411
|
+
# outside, and would be shared by every parameter assigned from it
|
|
412
|
+
self._value_ = list(value)
|
|
393
413
|
|
|
394
414
|
@property
|
|
395
415
|
def options(self):
|
|
@@ -484,7 +504,9 @@ class Parameter_component_list(Parameter):
|
|
|
484
504
|
# Is a list
|
|
485
505
|
for el in value:
|
|
486
506
|
el = str(el)
|
|
487
|
-
|
|
507
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
508
|
+
# outside, and would be shared by every parameter assigned from it
|
|
509
|
+
self._value_ = list(value)
|
|
488
510
|
|
|
489
511
|
self._external_ = []
|
|
490
512
|
for el in self.value:
|
|
@@ -622,7 +644,9 @@ class Parameter_variable_list(Parameter):
|
|
|
622
644
|
# Is a list
|
|
623
645
|
for el in value:
|
|
624
646
|
el = str(el)
|
|
625
|
-
|
|
647
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
648
|
+
# outside, and would be shared by every parameter assigned from it
|
|
649
|
+
self._value_ = list(value)
|
|
626
650
|
|
|
627
651
|
format_error = False
|
|
628
652
|
self._symbol_ = []
|
|
@@ -744,7 +768,9 @@ class Parameter_math_exp_list(Parameter):
|
|
|
744
768
|
# Is a list
|
|
745
769
|
for el in value:
|
|
746
770
|
el = str(el)
|
|
747
|
-
|
|
771
|
+
# Own copy: keeping the caller list would let it be mutated from
|
|
772
|
+
# outside, and would be shared by every parameter assigned from it
|
|
773
|
+
self._value_ = list(value)
|
|
748
774
|
|
|
749
775
|
def evaluate(self, i, values_dic):
|
|
750
776
|
try:
|
|
@@ -2,9 +2,6 @@ import json
|
|
|
2
2
|
import datetime as dt
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pandas as pd
|
|
5
|
-
from dash import Dash, callback, Input, Output, html, State
|
|
6
|
-
import dash_ag_grid as dag
|
|
7
|
-
import dash_bootstrap_components as dbc
|
|
8
5
|
import psychrolib as sicro
|
|
9
6
|
from tqdm import trange
|
|
10
7
|
from opensimula.Message import Message
|
|
@@ -59,13 +56,14 @@ class Project(Parameter_container):
|
|
|
59
56
|
)
|
|
60
57
|
self.add_parameter(
|
|
61
58
|
Parameter_options(
|
|
62
|
-
"shadow_calculation", "
|
|
59
|
+
"shadow_calculation", "INTERPOLATION", ["NO", "INSTANT", "INTERPOLATION"]
|
|
63
60
|
)
|
|
64
61
|
)
|
|
65
62
|
self.add_parameter(Parameter_float("albedo", 0.2, "frac", min=0, max=1))
|
|
66
63
|
|
|
67
64
|
self._sim_ = sim
|
|
68
65
|
self._components_ = []
|
|
66
|
+
self._components_dict_ = {} # name -> component index, see component()
|
|
69
67
|
sicro.SetUnitSystem(sicro.SI)
|
|
70
68
|
atm_p = sicro.GetStandardAtmPressure(0)
|
|
71
69
|
w_50 = sicro.GetHumRatioFromRelHum(22.5, 0.5, atm_p)
|
|
@@ -93,6 +91,18 @@ class Project(Parameter_container):
|
|
|
93
91
|
"RHOCP_W": rhocp_water,
|
|
94
92
|
}
|
|
95
93
|
|
|
94
|
+
def clear(self):
|
|
95
|
+
"""Remove every component from the Project
|
|
96
|
+
|
|
97
|
+
The parameters of the project itself keep their values; only its
|
|
98
|
+
components are dropped. Needed before reloading a definition into a
|
|
99
|
+
project that already holds one, because read_dict() and read_json()
|
|
100
|
+
add components, they do not replace them.
|
|
101
|
+
"""
|
|
102
|
+
self._components_ = []
|
|
103
|
+
self._components_dict_ = {}
|
|
104
|
+
self._ordered_component_list_ = []
|
|
105
|
+
|
|
96
106
|
def del_component(self, component):
|
|
97
107
|
"""Delete component from Project
|
|
98
108
|
|
|
@@ -100,6 +110,13 @@ class Project(Parameter_container):
|
|
|
100
110
|
component (Component): Component to be removed from the project
|
|
101
111
|
"""
|
|
102
112
|
self._components_.remove(component)
|
|
113
|
+
# Drop every index entry pointing at it, so a later component reusing
|
|
114
|
+
# its name cannot resolve to the deleted one.
|
|
115
|
+
self._components_dict_ = {
|
|
116
|
+
key: comp
|
|
117
|
+
for key, comp in self._components_dict_.items()
|
|
118
|
+
if comp is not component
|
|
119
|
+
}
|
|
103
120
|
|
|
104
121
|
def component(self, name):
|
|
105
122
|
"""Find and return component with its name
|
|
@@ -110,8 +127,16 @@ class Project(Parameter_container):
|
|
|
110
127
|
Returns:
|
|
111
128
|
component (Component): component found, None if not found.
|
|
112
129
|
"""
|
|
130
|
+
# Components are looked up by name millions of times during a
|
|
131
|
+
# simulation, so hits are served from an index. The cached component is
|
|
132
|
+
# revalidated against its current name because components can be
|
|
133
|
+
# renamed after being indexed; a stale entry falls back to the scan.
|
|
134
|
+
comp = self._components_dict_.get(name)
|
|
135
|
+
if comp is not None and comp.parameter("name").value == name:
|
|
136
|
+
return comp
|
|
113
137
|
for comp in self._components_:
|
|
114
138
|
if comp.parameter("name").value == name:
|
|
139
|
+
self._components_dict_[name] = comp
|
|
115
140
|
return comp
|
|
116
141
|
return None
|
|
117
142
|
|
|
@@ -586,100 +611,76 @@ class Project(Parameter_container):
|
|
|
586
611
|
def simulation_dataframe(self):
|
|
587
612
|
return self._sim_df
|
|
588
613
|
|
|
589
|
-
def
|
|
590
|
-
editor
|
|
591
|
-
df = self.component_dataframe(comp_type=comp_type, string_format=True)
|
|
592
|
-
self._n_clicks_new_comp_ = 0
|
|
593
|
-
self._n_clicks_del_comp_ = 0
|
|
594
|
-
disabled_new = False
|
|
595
|
-
if comp_type == "all":
|
|
596
|
-
disabled_new = True
|
|
597
|
-
|
|
598
|
-
column_definition = [
|
|
599
|
-
{
|
|
600
|
-
"field": "name",
|
|
601
|
-
"checkboxSelection": True,
|
|
602
|
-
"headerCheckboxSelection": True,
|
|
603
|
-
}
|
|
604
|
-
]
|
|
605
|
-
for i in df.columns:
|
|
606
|
-
if i != "name":
|
|
607
|
-
column_definition.append({"field": i})
|
|
608
|
-
|
|
609
|
-
editor.layout = html.Div(
|
|
610
|
-
[
|
|
611
|
-
dbc.Label("Components editor:"),
|
|
612
|
-
html.Br(),
|
|
613
|
-
dbc.Button(
|
|
614
|
-
"New component",
|
|
615
|
-
id="btn-new-comp",
|
|
616
|
-
disabled=disabled_new,
|
|
617
|
-
n_clicks=0,
|
|
618
|
-
),
|
|
619
|
-
dbc.Button(
|
|
620
|
-
"Delete selected components",
|
|
621
|
-
id="btn-del-comp",
|
|
622
|
-
n_clicks=0,
|
|
623
|
-
style={"margin-left": "15px"},
|
|
624
|
-
),
|
|
625
|
-
html.Br(),
|
|
626
|
-
html.Br(),
|
|
627
|
-
dag.AgGrid(
|
|
628
|
-
id="comp-table",
|
|
629
|
-
rowData=df.to_dict("records"),
|
|
630
|
-
columnDefs=column_definition,
|
|
631
|
-
columnSize="sizeToFit",
|
|
632
|
-
defaultColDef={"filter": True, "editable": True},
|
|
633
|
-
style={"height": "500px"},
|
|
634
|
-
dashGridOptions={
|
|
635
|
-
"rowSelection": "multiple",
|
|
636
|
-
"suppressRowClickSelection": True,
|
|
637
|
-
"pagination": True,
|
|
638
|
-
},
|
|
639
|
-
),
|
|
640
|
-
]
|
|
641
|
-
)
|
|
614
|
+
def editor(self):
|
|
615
|
+
"""Interactive editor for the whole project definition.
|
|
642
616
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
Input("btn-new-comp", "n_clicks"),
|
|
647
|
-
Input("btn-del-comp", "n_clicks"),
|
|
648
|
-
State("comp-table", "selectedRows"),
|
|
649
|
-
prevent_initial_call=True,
|
|
650
|
-
)
|
|
651
|
-
def update_data(changed, n_clicks_new, n_clicks_del, selectedRows):
|
|
652
|
-
if self._n_clicks_new_comp_ < n_clicks_new:
|
|
653
|
-
self.new_component(comp_type, "new_comp_" + str(n_clicks_new))
|
|
654
|
-
self._n_clicks_new_comp_ = n_clicks_new
|
|
655
|
-
elif self._n_clicks_del_comp_ < n_clicks_del:
|
|
656
|
-
for row in selectedRows:
|
|
657
|
-
self.del_component(self.component(row["name"]))
|
|
658
|
-
self._n_clicks_del_comp_ = n_clicks_del
|
|
659
|
-
else:
|
|
660
|
-
if changed is not None:
|
|
661
|
-
if changed[0]["colId"] == "name":
|
|
662
|
-
self.component(changed[0]["oldValue"]).parameter(
|
|
663
|
-
"name"
|
|
664
|
-
).value = changed[0]["value"]
|
|
665
|
-
else:
|
|
666
|
-
self.component(changed[0]["data"]["name"]).parameter(
|
|
667
|
-
changed[0]["colId"]
|
|
668
|
-
).value = changed[0]["value"]
|
|
669
|
-
df_end = self.component_dataframe(comp_type=comp_type, string_format=True)
|
|
670
|
-
return df_end.to_dict("records")
|
|
617
|
+
Returns an anywidget that works in Jupyter and, wrapped in
|
|
618
|
+
mo.ui.anywidget(), in Marimo. The document is read with write_dict();
|
|
619
|
+
the widget does not write back to this project.
|
|
671
620
|
|
|
672
|
-
|
|
621
|
+
Returns:
|
|
622
|
+
ProjectEditor: widget holding the project document in .value
|
|
623
|
+
"""
|
|
624
|
+
# Imported here: opensimula.editor reaches Project through Simulation,
|
|
625
|
+
# so a module level import would close the cycle.
|
|
626
|
+
from opensimula.editor import ProjectEditor
|
|
627
|
+
|
|
628
|
+
return ProjectEditor(self)
|
|
673
629
|
|
|
674
630
|
def create_3D_environment(self, env_3D):
|
|
675
631
|
for component in self.component_list("all"):
|
|
676
|
-
if hasattr(component, "get_polygon_3D"):
|
|
677
|
-
|
|
632
|
+
if not hasattr(component, "get_polygon_3D"):
|
|
633
|
+
continue
|
|
634
|
+
try:
|
|
635
|
+
polygon = component.get_polygon_3D()
|
|
636
|
+
except (AttributeError, IndexError, TypeError):
|
|
637
|
+
# A surface whose space, or an opening whose surface, does not
|
|
638
|
+
# resolve has no place to be. It is left out rather than
|
|
639
|
+
# bringing the whole view down; check() names the reference.
|
|
640
|
+
continue
|
|
641
|
+
self._set_polygon_origin_(polygon, component)
|
|
642
|
+
env_3D.add_polygon_3D(polygon)
|
|
643
|
+
|
|
644
|
+
def _set_polygon_origin_(self, polygon, component):
|
|
645
|
+
"""Record which component a polygon came from, and which spaces it bounds.
|
|
646
|
+
|
|
647
|
+
Polygon_3D is geometry and knows nothing of the model, so the link is
|
|
648
|
+
made here, where the component is still at hand. It is what lets a
|
|
649
|
+
viewer show one space at a time; an interior partition belongs to the
|
|
650
|
+
two spaces on either side of it.
|
|
651
|
+
"""
|
|
652
|
+
polygon.component = component.parameter("name").value
|
|
653
|
+
polygon.component_type = component.parameter("type").value
|
|
654
|
+
|
|
655
|
+
surface = component
|
|
656
|
+
if polygon.component_type == "Opening":
|
|
657
|
+
surface = component.get_surface()
|
|
658
|
+
if surface is None:
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
parameters = surface.parameter_dict()
|
|
662
|
+
if "surface_type" in parameters:
|
|
663
|
+
polygon.surface_type = parameters["surface_type"].value
|
|
664
|
+
if "spaces" in parameters:
|
|
665
|
+
polygon.spaces = [
|
|
666
|
+
name for name in parameters["spaces"].value if name != "not_defined"
|
|
667
|
+
]
|
|
678
668
|
|
|
669
|
+
def geometry_dict(self):
|
|
670
|
+
"""Geometry of the project as plain data, for a 3D viewer
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
dict: {"meshes": [...], "spaces": [...]}, one mesh per polygon with
|
|
674
|
+
its triangles and the component and spaces it belongs to
|
|
675
|
+
"""
|
|
676
|
+
env_3D = Environment_3D()
|
|
677
|
+
self.create_3D_environment(env_3D)
|
|
678
|
+
return env_3D.geometry_dict()
|
|
679
|
+
|
|
679
680
|
def show_3D(self, jupyter=False):
|
|
680
681
|
env_3D = Environment_3D()
|
|
681
682
|
self.create_3D_environment(env_3D)
|
|
682
|
-
env_3D.show(polygons_type="initial", jupyter=jupyter)
|
|
683
|
+
return env_3D.show(polygons_type="initial", jupyter=jupyter)
|
|
683
684
|
|
|
684
685
|
def show_3D_shadows(self, date, jupyter=False):
|
|
685
686
|
env_3D = Environment_3D()
|
|
@@ -2,9 +2,6 @@ from opensimula.Project import Project
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import plotly.express as px
|
|
4
4
|
from plotly.subplots import make_subplots
|
|
5
|
-
from dash import Dash, callback, Input, Output, html, State
|
|
6
|
-
import dash_ag_grid as dag
|
|
7
|
-
import dash_bootstrap_components as dbc
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
class Simulation:
|
|
@@ -163,63 +160,3 @@ class Simulation:
|
|
|
163
160
|
# fig.update_traces(showlegend=True)
|
|
164
161
|
subfig.show()
|
|
165
162
|
|
|
166
|
-
def project_editor(self):
|
|
167
|
-
editor = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
|
168
|
-
df = self.project_dataframe(string_format=True)
|
|
169
|
-
self._n_clicks_new_project_ = 0
|
|
170
|
-
self._n_clicks_del_project_ = 0
|
|
171
|
-
column_definition = [
|
|
172
|
-
{"field": "name", "checkboxSelection": True, "headerCheckboxSelection": True}]
|
|
173
|
-
for i in df.columns:
|
|
174
|
-
if i != "name":
|
|
175
|
-
column_definition.append({"field": i})
|
|
176
|
-
|
|
177
|
-
editor.layout = html.Div([
|
|
178
|
-
dbc.Label("Projects editor: "),
|
|
179
|
-
html.Br(),
|
|
180
|
-
dbc.Button('New project', id='btn-new-project', n_clicks=0),
|
|
181
|
-
dbc.Button('Delete selected projects',
|
|
182
|
-
id='btn-del-project', n_clicks=0, style={"margin-left": "15px"}),
|
|
183
|
-
html.Br(),
|
|
184
|
-
html.Br(),
|
|
185
|
-
dag.AgGrid(
|
|
186
|
-
id="project-table",
|
|
187
|
-
rowData=df.to_dict("records"),
|
|
188
|
-
columnDefs=column_definition,
|
|
189
|
-
columnSize="sizeToFit",
|
|
190
|
-
defaultColDef={"filter": True, "editable": True},
|
|
191
|
-
style={"height": '250px'},
|
|
192
|
-
dashGridOptions={
|
|
193
|
-
"rowSelection": "multiple",
|
|
194
|
-
"suppressRowClickSelection": True,
|
|
195
|
-
"pagination": True,
|
|
196
|
-
})
|
|
197
|
-
])
|
|
198
|
-
|
|
199
|
-
@callback(
|
|
200
|
-
Output('project-table', 'rowData'),
|
|
201
|
-
Input('project-table', 'cellValueChanged'),
|
|
202
|
-
Input('btn-new-project', 'n_clicks'),
|
|
203
|
-
Input('btn-del-project', 'n_clicks'),
|
|
204
|
-
State('project-table', 'selectedRows'),
|
|
205
|
-
prevent_initial_call=True)
|
|
206
|
-
def update_data(changed, n_clicks_new, n_clicks_del, selectedRows):
|
|
207
|
-
if (self._n_clicks_new_project_ < n_clicks_new):
|
|
208
|
-
self.new_project("new_project_"+str(n_clicks_new))
|
|
209
|
-
self._n_clicks_new_project_ = n_clicks_new
|
|
210
|
-
elif (self._n_clicks_del_project_ < n_clicks_del):
|
|
211
|
-
for row in selectedRows:
|
|
212
|
-
self.del_project(self.project(row["name"]))
|
|
213
|
-
self._n_clicks_del_project_ = n_clicks_del
|
|
214
|
-
else:
|
|
215
|
-
if changed is not None:
|
|
216
|
-
if changed[0]["colId"] == "name":
|
|
217
|
-
self.project(changed[0]['oldValue']).parameter(
|
|
218
|
-
"name").value = changed[0]["value"]
|
|
219
|
-
else:
|
|
220
|
-
self.project(changed[0]['data']["name"]).parameter(
|
|
221
|
-
changed[0]["colId"]).value = changed[0]["value"]
|
|
222
|
-
df_end = self.project_dataframe(string_format=True)
|
|
223
|
-
return df_end.to_dict("records")
|
|
224
|
-
|
|
225
|
-
editor.run(jupyter_height=350)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import math
|
|
2
|
+
from shapely.validation import explain_validity
|
|
2
3
|
from opensimula.Message import Message
|
|
3
4
|
from opensimula.components.Surface import Surface
|
|
4
5
|
from opensimula.Parameters import (
|
|
@@ -106,10 +107,49 @@ class Building_surface(Surface):
|
|
|
106
107
|
# Create openings list
|
|
107
108
|
if self._surface_type_ == "EXTERIOR" or self._surface_type_ == "INTERIOR":
|
|
108
109
|
self._create_openings_list_()
|
|
110
|
+
errors.extend(self._check_polygon_validity_())
|
|
111
|
+
return errors
|
|
112
|
+
|
|
113
|
+
def _check_polygon_validity_(self):
|
|
114
|
+
"""Warn when the surface outline with its openings has no valid single-piece shape.
|
|
115
|
+
|
|
116
|
+
get_polygon_3D() already recovers an opening flush with the surface
|
|
117
|
+
edge (a door reaching the floor, a window flush with a wall edge, usual
|
|
118
|
+
in imported geometry), see Polygon_3D._build_shapely_polygon_, so that
|
|
119
|
+
alone is not worth a warning. What it cannot recover from is an opening
|
|
120
|
+
that splits the surface into disconnected pieces, or geometry that
|
|
121
|
+
stays invalid after that recovery.
|
|
122
|
+
"""
|
|
123
|
+
errors = []
|
|
124
|
+
if len(self.openings) == 0:
|
|
125
|
+
return errors
|
|
126
|
+
polygon = Polygon_3D._build_shapely_polygon_(
|
|
127
|
+
self.get_polygon_2D(), [op.get_polygon_2D() for op in self.openings]
|
|
128
|
+
)
|
|
129
|
+
if not polygon.is_valid:
|
|
130
|
+
msg = (
|
|
131
|
+
f"{self.parameter('name').value}, the surface outline with its openings "
|
|
132
|
+
f"is not a valid polygon ({explain_validity(polygon)}). Shadow results "
|
|
133
|
+
"for this surface may be unreliable."
|
|
134
|
+
)
|
|
135
|
+
errors.append(Message(msg, "WARNING"))
|
|
136
|
+
elif polygon.geom_type != "Polygon":
|
|
137
|
+
msg = (
|
|
138
|
+
f"{self.parameter('name').value}, its openings split the surface outline "
|
|
139
|
+
"into disconnected pieces. Shadow results for this surface may be unreliable."
|
|
140
|
+
)
|
|
141
|
+
errors.append(Message(msg, "WARNING"))
|
|
109
142
|
return errors
|
|
110
143
|
|
|
111
144
|
def get_building(self):
|
|
112
|
-
|
|
145
|
+
space = self.get_space()
|
|
146
|
+
# Only a Space knows its building directly. Following anything else
|
|
147
|
+
# chases its get_building() in turn, and a name that points back here,
|
|
148
|
+
# which "spaces" accepts because it is only checked later, would run
|
|
149
|
+
# the stack out before check() could report it.
|
|
150
|
+
if space is None or space.parameter("type").value != "Space":
|
|
151
|
+
return None
|
|
152
|
+
return space.get_building()
|
|
113
153
|
|
|
114
154
|
def get_space(self, side=0):
|
|
115
155
|
return self.parameter("spaces").component[side]
|
|
@@ -5,7 +5,7 @@ from opensimula.Component import Component
|
|
|
5
5
|
class Chiller_heat_pump(Component):
|
|
6
6
|
def __init__(self, name, project):
|
|
7
7
|
Component.__init__(self, name, project)
|
|
8
|
-
self.parameter("type").value = "
|
|
8
|
+
self.parameter("type").value = "Chiller_heat_pump"
|
|
9
9
|
self.parameter("description").value = "Chiller equipment manufacturer information"
|
|
10
10
|
self.add_parameter(Parameter_options("chiller_type", "CHILLER", ["CHILLER", "HEAT_PUMP", "CHILLER_HEAT_PUMP"]))
|
|
11
11
|
self.add_parameter(Parameter_options("condensation_type", "AIR_CONDENSED", ["AIR_CONDENSED", "WATER_CONDENSED"]))
|
|
@@ -17,7 +17,7 @@ class Construction(Component):
|
|
|
17
17
|
self.add_parameter(Parameter_float_list(
|
|
18
18
|
"lw_epsilon", [0.9, 0.9], "frac", min=0, max=1))
|
|
19
19
|
self.add_parameter(Parameter_component_list(
|
|
20
|
-
"materials", [], "Material"))
|
|
20
|
+
"materials", [], ["Material"]))
|
|
21
21
|
self.add_parameter(Parameter_float_list("thicknesses", [], "m", min=0))
|
|
22
22
|
|
|
23
23
|
def check(self):
|
|
@@ -35,8 +35,10 @@ class Construction(Component):
|
|
|
35
35
|
material_name (str): Name of the material component to add
|
|
36
36
|
thickness (float): Thickness of the layer in meters
|
|
37
37
|
"""
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
# Copies, because the getter returns the parameter own list and
|
|
39
|
+
# inserting into it would modify the construction before validation
|
|
40
|
+
materials = list(self.parameter("materials").value)
|
|
41
|
+
thicknesses = list(self.parameter("thicknesses").value)
|
|
40
42
|
materials.insert(0, material_name)
|
|
41
43
|
thicknesses.insert(0, thickness)
|
|
42
44
|
self.parameter("materials").value = materials
|
|
@@ -93,7 +93,8 @@ class Opening(Component):
|
|
|
93
93
|
return errors
|
|
94
94
|
|
|
95
95
|
def get_building(self):
|
|
96
|
-
|
|
96
|
+
surface = self.get_surface()
|
|
97
|
+
return None if surface is None else surface.get_building()
|
|
97
98
|
|
|
98
99
|
def get_space(self, side=0):
|
|
99
100
|
return self.get_surface().get_space(side)
|
|
@@ -11,11 +11,11 @@ class Opening_type(Component):
|
|
|
11
11
|
"description").value = "Window or doors with glazing, construction, frame and shading."
|
|
12
12
|
|
|
13
13
|
self.add_parameter(Parameter_component(
|
|
14
|
-
"glazing", "not_defined", "Glazing"))
|
|
14
|
+
"glazing", "not_defined", ["Glazing"]))
|
|
15
15
|
self.add_parameter(Parameter_component(
|
|
16
|
-
"frame", "not_defined", "Frame"))
|
|
16
|
+
"frame", "not_defined", ["Frame"]))
|
|
17
17
|
self.add_parameter(Parameter_component(
|
|
18
|
-
"construction", "not_defined", "Construction"))
|
|
18
|
+
"construction", "not_defined", ["Construction"]))
|
|
19
19
|
self.add_parameter(Parameter_float(
|
|
20
20
|
"glazing_fraction", 0.9, "frac", min=0, max=1))
|
|
21
21
|
self.add_parameter(Parameter_float(
|