OpenSimula 0.6.1__tar.gz → 0.7.1__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.
Files changed (61) hide show
  1. {opensimula-0.6.1 → opensimula-0.7.1}/PKG-INFO +1 -2
  2. {opensimula-0.6.1 → opensimula-0.7.1}/pyproject.toml +2 -3
  3. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Project.py +8 -8
  4. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/__init__.py +1 -1
  5. opensimula-0.6.1/src/opensimula/components/HVAC_DX_equipment.py → opensimula-0.7.1/src/opensimula/components/DX_unit.py +3 -3
  6. opensimula-0.6.1/src/opensimula/components/HVAC_fan_equipment.py → opensimula-0.7.1/src/opensimula/components/Fan.py +3 -3
  7. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/HVAC_DX_system.py +1 -1
  8. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/HVAC_MZW_system.py +5 -5
  9. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/HVAC_SZW_system.py +4 -4
  10. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Surface.py +1 -1
  11. opensimula-0.6.1/src/opensimula/components/HVAC_coil_equipment.py → opensimula-0.7.1/src/opensimula/components/Water_coil.py +3 -3
  12. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/__init__.py +3 -3
  13. opensimula-0.7.1/src/opensimula/visual_3D/Environment_3D.py +492 -0
  14. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/visual_3D/Polygon_3D.py +21 -26
  15. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula.egg-info/PKG-INFO +1 -2
  16. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula.egg-info/SOURCES.txt +3 -3
  17. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula.egg-info/requires.txt +0 -1
  18. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_HVAC_DX_Equipment.py +2 -2
  19. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_HVAC_DX_system.py +1 -1
  20. opensimula-0.6.1/src/opensimula/visual_3D/Environment_3D.py +0 -257
  21. {opensimula-0.6.1 → opensimula-0.7.1}/LICENSE +0 -0
  22. {opensimula-0.6.1 → opensimula-0.7.1}/README.md +0 -0
  23. {opensimula-0.6.1 → opensimula-0.7.1}/setup.cfg +0 -0
  24. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Child.py +0 -0
  25. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Component.py +0 -0
  26. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Iterative_process.py +0 -0
  27. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Message.py +0 -0
  28. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Parameter_container.py +0 -0
  29. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Parameters.py +0 -0
  30. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Simulation.py +0 -0
  31. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/Variable.py +0 -0
  32. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Building.py +0 -0
  33. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Building_surface.py +0 -0
  34. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Calculator.py +0 -0
  35. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Construction.py +0 -0
  36. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Day_schedule.py +0 -0
  37. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/File_data.py +0 -0
  38. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/File_met.py +0 -0
  39. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Frame.py +0 -0
  40. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Glazing.py +0 -0
  41. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/HVAC_perfect_system.py +0 -0
  42. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Material.py +0 -0
  43. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Opening.py +0 -0
  44. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Opening_type.py +0 -0
  45. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Solar_surface.py +0 -0
  46. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Space.py +0 -0
  47. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Space_type.py +0 -0
  48. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Test_component.py +0 -0
  49. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Week_schedule.py +0 -0
  50. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula/components/Year_schedule.py +0 -0
  51. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula.egg-info/dependency_links.txt +0 -0
  52. {opensimula-0.6.1 → opensimula-0.7.1}/src/opensimula.egg-info/top_level.txt +0 -0
  53. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Calculator.py +0 -0
  54. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Case_600FF.py +0 -0
  55. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Case_610.py +0 -0
  56. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Construction.py +0 -0
  57. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_File_data.py +0 -0
  58. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_File_met.py +0 -0
  59. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Iterative_process.py +0 -0
  60. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Project.py +0 -0
  61. {opensimula-0.6.1 → opensimula-0.7.1}/test/test_Schedules.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensimula
3
- Version: 0.6.1
3
+ Version: 0.7.1
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
@@ -18,7 +18,6 @@ Requires-Dist: psychrolib>=2.5.0
18
18
  Requires-Dist: scipy>=1.16.2
19
19
  Requires-Dist: shapely>=2.1.1
20
20
  Requires-Dist: tqdm>=4.67.1
21
- Requires-Dist: triangle>=20250106
22
21
  Requires-Dist: vedo>=2025.5.4
23
22
  Dynamic: license-file
24
23
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opensimula"
3
- version = "0.6.1"
3
+ version = "0.7.1"
4
4
  requires-python = ">=3.12"
5
5
  authors = [
6
6
  { name="Juan F. Coronel", email="jfc@us.es" },
@@ -20,8 +20,7 @@ dependencies = [
20
20
  "scipy>=1.16.2",
21
21
  "shapely>=2.1.1",
22
22
  "tqdm>=4.67.1",
23
- "triangle>=20250106",
24
- "vedo>=2025.5.4",
23
+ "vedo>=2025.5.4",
25
24
  ]
26
25
 
27
26
  [dependency-groups]
@@ -676,23 +676,23 @@ class Project(Parameter_container):
676
676
  if hasattr(component, "get_polygon_3D"):
677
677
  env_3D.add_polygon_3D(component.get_polygon_3D())
678
678
 
679
- def show_3D(self):
679
+ def show_3D(self, jupyter=False):
680
680
  env_3D = Environment_3D()
681
681
  self.create_3D_environment(env_3D)
682
- env_3D.show(polygons_type="initial")
683
-
684
- def show_3D_shadows(self, date):
682
+ env_3D.show(polygons_type="initial", jupyter=jupyter)
683
+
684
+ def show_3D_shadows(self, date, jupyter=False):
685
685
  env_3D = Environment_3D()
686
686
  self.create_3D_environment(env_3D)
687
687
  file_met = self.parameter("simulation_file_met").component
688
688
  cos = file_met.sun_cosines(date)
689
689
  if len(cos) == 3:
690
690
  env_3D.calculate_shadows(cos)
691
- env_3D.show(polygons_type="Building_shadows")
691
+ env_3D.show(polygons_type="Building_shadows", jupyter=jupyter)
692
692
  else:
693
693
  self._sim_.message(Message(date.strftime("%H:%M, %d/%m/%Y") + " is night", "WARNING"))
694
694
 
695
- def show_3D_shadows_animation(self, date):
695
+ def show_3D_shadows_animation(self, date, jupyter=False):
696
696
  env_3D = Environment_3D()
697
697
  self.create_3D_environment(env_3D)
698
698
  file_met = self.parameter("simulation_file_met").component
@@ -701,9 +701,9 @@ class Project(Parameter_container):
701
701
  for hour in range(24):
702
702
  new_date = date.replace(hour=hour, minute=0, second=0)
703
703
  cos = file_met.sun_cosines(new_date)
704
- if len(cos) == 3: # Day time
704
+ if len(cos) == 3: # Day time
705
705
  texts.append(new_date.strftime("%H:%M, %d/%m/%Y"))
706
706
  cosines.append(cos)
707
- env_3D.show_animation(texts, cosines, polygons_type="Building_shadows")
707
+ env_3D.show_animation(texts, cosines, polygons_type="Building_shadows", jupyter=jupyter)
708
708
 
709
709
 
@@ -5,4 +5,4 @@ opensimula is a Python Package for perform time simulations
5
5
  """
6
6
  from opensimula.Simulation import Simulation
7
7
  from opensimula.Iterative_process import Iterative_process
8
- VERSION = "0.6.1"
8
+ VERSION = "0.7.1"
@@ -3,11 +3,11 @@ from opensimula.Parameters import Parameter_float, Parameter_float_list, Paramet
3
3
  from opensimula.Component import Component
4
4
  from scipy.optimize import fsolve
5
5
 
6
- class HVAC_DX_equipment(Component):
6
+ class DX_unit(Component):
7
7
  def __init__(self, name, project):
8
8
  Component.__init__(self, name, project)
9
- self.parameter("type").value = "HVAC_DX_equipment"
10
- self.parameter("description").value = "HVAC Direct Expansion equipment manufacturer information"
9
+ self.parameter("type").value = "DX_unit"
10
+ self.parameter("description").value = "Direct Expansion unit manufacturer information"
11
11
  self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
12
12
  self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
13
13
  self.add_parameter(Parameter_float("nominal_sensible_cooling_capacity", 0, "W", min=0))
@@ -1,11 +1,11 @@
1
1
  from opensimula.Parameters import Parameter_float, Parameter_math_exp
2
2
  from opensimula.Component import Component
3
3
 
4
- class HVAC_fan_equipment(Component):
4
+ class Fan(Component):
5
5
  def __init__(self, name, project):
6
6
  Component.__init__(self, name, project)
7
- self.parameter("type").value = "HVAC_fan_equipment"
8
- self.parameter("description").value = "HVAC Fan equipment manufacturer information"
7
+ self.parameter("type").value = "Fan"
8
+ self.parameter("description").value = "Fan equipment manufacturer information"
9
9
  self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
10
10
  self.add_parameter(Parameter_float("nominal_pressure", 1, "Pa", min=0))
11
11
  self.add_parameter(Parameter_float("nominal_power", 1, "W", min=0))
@@ -10,7 +10,7 @@ class HVAC_DX_system(Component):
10
10
  Component.__init__(self, name, project)
11
11
  self.parameter("type").value = "HVAC_DX_system"
12
12
  self.parameter("description").value = "HVAC Direct Expansion system for time simulation"
13
- self.add_parameter(Parameter_component("equipment", "not_defined", ["HVAC_DX_equipment"]))
13
+ self.add_parameter(Parameter_component("equipment", "not_defined", ["DX_unit"]))
14
14
  self.add_parameter(Parameter_component("spaces", "not_defined", ["Space"])) # Space, TODO: Add Air_distribution, Energy_load
15
15
  self.add_parameter(Parameter_float("air_flow", 1, "m³/s", min=0))
16
16
  self.add_parameter(Parameter_math_exp("outdoor_air_fraction", "0", "frac"))
@@ -32,16 +32,16 @@ class HVAC_MZW_system(Component): # HVAC Multizone Water system
32
32
  )
33
33
  )
34
34
  self.add_parameter(
35
- Parameter_component("cooling_coil", "not_defined", ["HVAC_coil_equipment"])
35
+ Parameter_component("cooling_coil", "not_defined", ["Water_coil"])
36
36
  )
37
37
  self.add_parameter(
38
- Parameter_component("heating_coil", "not_defined", ["HVAC_coil_equipment"])
38
+ Parameter_component("heating_coil", "not_defined", ["Water_coil"])
39
39
  )
40
40
  self.add_parameter(
41
- Parameter_component("supply_fan", "not_defined", ["HVAC_fan_equipment"])
41
+ Parameter_component("supply_fan", "not_defined", ["Fan"])
42
42
  )
43
43
  self.add_parameter(
44
- Parameter_component("return_fan", "not_defined", ["HVAC_fan_equipment"])
44
+ Parameter_component("return_fan", "not_defined", ["Fan"])
45
45
  )
46
46
  self.add_parameter(Parameter_float("air_flow", 1, "m³/s", min=0))
47
47
  self.add_parameter(
@@ -108,7 +108,7 @@ class HVAC_MZW_system(Component): # HVAC Multizone Water system
108
108
  self.add_parameter(Parameter_boolean("vav", False))
109
109
  self.add_parameter(
110
110
  Parameter_component_list(
111
- "reheat_coils", ["not_defined", "not_defined"], ["HVAC_coil_equipment"]
111
+ "reheat_coils", ["not_defined", "not_defined"], ["Water_coil"]
112
112
  )
113
113
  )
114
114
 
@@ -20,16 +20,16 @@ class HVAC_SZW_system(Component): # HVAC Single Zone Water system
20
20
  Parameter_component("space", "not_defined", ["Space"])
21
21
  ) # Space
22
22
  self.add_parameter(
23
- Parameter_component("cooling_coil", "not_defined", ["HVAC_coil_equipment"])
23
+ Parameter_component("cooling_coil", "not_defined", ["Water_coil"])
24
24
  )
25
25
  self.add_parameter(
26
- Parameter_component("heating_coil", "not_defined", ["HVAC_coil_equipment"])
26
+ Parameter_component("heating_coil", "not_defined", ["Water_coil"])
27
27
  )
28
28
  self.add_parameter(
29
- Parameter_component("supply_fan", "not_defined", ["HVAC_fan_equipment"])
29
+ Parameter_component("supply_fan", "not_defined", ["Fan"])
30
30
  )
31
31
  self.add_parameter(
32
- Parameter_component("return_fan", "not_defined", ["HVAC_fan_equipment"])
32
+ Parameter_component("return_fan", "not_defined", ["Fan"])
33
33
  )
34
34
  self.add_parameter(Parameter_float("air_flow", 1, "m³/s", min=0))
35
35
  self.add_parameter(Parameter_float("return_air_flow", 1, "m³/s", min=0))
@@ -12,7 +12,7 @@ class Surface(Component):
12
12
  Component.__init__(self, name, project)
13
13
  # Parameters
14
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º
15
+ self.add_parameter(Parameter_float("azimuth", 0, "°", min=-180, max=180)) # Angle from building -Y axis to horizontal projection of exterior normal -> S: 0º, E: 90º, W: -90º, N: 180º
16
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
17
  self.add_parameter(Parameter_options(
18
18
  "shape", "RECTANGLE", ["RECTANGLE", "POLYGON"]))
@@ -4,11 +4,11 @@ from opensimula.Component import Component
4
4
  from scipy.optimize import fsolve
5
5
  import psychrolib as sicro
6
6
 
7
- class HVAC_coil_equipment(Component):
7
+ class Water_coil(Component):
8
8
  def __init__(self, name, project):
9
9
  Component.__init__(self, name, project)
10
- self.parameter("type").value = "HVAC_coil_equipment"
11
- self.parameter("description").value = "HVAC coil equipment manufacturer information"
10
+ self.parameter("type").value = "Water_coil"
11
+ self.parameter("description").value = "Water coil equipment manufacturer information"
12
12
  self.add_parameter(Parameter_float("nominal_air_flow", 1, "m³/s", min=0))
13
13
  self.add_parameter(Parameter_float("nominal_cooling_water_flow", 1, "m³/s", min=0))
14
14
  self.add_parameter(Parameter_float("nominal_total_cooling_capacity", 0, "W", min=0))
@@ -16,9 +16,9 @@ from opensimula.components.Building_surface import Building_surface
16
16
  from opensimula.components.Solar_surface import Solar_surface
17
17
  from opensimula.components.Opening import Opening
18
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
19
+ from opensimula.components.DX_unit import DX_unit
20
+ from opensimula.components.Water_coil import Water_coil
21
+ from opensimula.components.Fan import Fan
22
22
  from opensimula.components.HVAC_DX_system import HVAC_DX_system
23
23
  from opensimula.components.HVAC_SZW_system import HVAC_SZW_system
24
24
  from opensimula.components.HVAC_MZW_system import HVAC_MZW_system
@@ -0,0 +1,492 @@
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
+ # ── Plotly helpers (jupyter mode) ──────────────────────────────────────
73
+
74
+ def _rgb_str_(self, color_name):
75
+ rgb = vedo.colors.get_color(color_name)
76
+ return f"rgb({int(rgb[0]*255)},{int(rgb[1]*255)},{int(rgb[2]*255)})"
77
+
78
+ def _polygon_to_plotly_(self, polygon_3D):
79
+ """Returns (go.Mesh3d, go.Scatter3d outline) for a single Polygon_3D."""
80
+ import plotly.graph_objects as go
81
+ points, faces = polygon_3D._triangulate_()
82
+ pts = np.array(points)
83
+ fcs = np.array(faces)
84
+ color = self._rgb_str_(polygon_3D.color)
85
+ mesh = go.Mesh3d(
86
+ x=pts[:, 0], y=pts[:, 1], z=pts[:, 2],
87
+ i=fcs[:, 0], j=fcs[:, 1], k=fcs[:, 2],
88
+ color=color, opacity=polygon_3D.opacity,
89
+ name=polygon_3D.name,
90
+ hovertemplate=f"<b>{polygon_3D.name}</b><extra></extra>",
91
+ flatshading=True,
92
+ lighting=dict(ambient=0.85, diffuse=0.5, specular=0.05),
93
+ )
94
+ boundary = np.array(polygon_3D.polygon3D)
95
+ xs = list(boundary[:, 0]) + [boundary[0, 0], None]
96
+ ys = list(boundary[:, 1]) + [boundary[0, 1], None]
97
+ zs = list(boundary[:, 2]) + [boundary[0, 2], None]
98
+ outline = go.Scatter3d(
99
+ x=xs, y=ys, z=zs,
100
+ mode="lines",
101
+ line=dict(color="black", width=2),
102
+ showlegend=False, hoverinfo="skip",
103
+ )
104
+ return mesh, outline
105
+
106
+ def _merge_to_plotly_(self, polygon_list, name=""):
107
+ """Merge multiple Polygon_3D into a single go.Mesh3d (for animation frames)."""
108
+ import plotly.graph_objects as go
109
+ if not polygon_list:
110
+ return go.Mesh3d(x=[0], y=[0], z=[0], i=[], j=[], k=[],
111
+ visible=False, showlegend=False, hoverinfo="skip")
112
+ all_pts, all_i, all_j, all_k, all_colors = [], [], [], [], []
113
+ offset = 0
114
+ for pol in polygon_list:
115
+ points, faces = pol._triangulate_()
116
+ pts = np.array(points)
117
+ fcs = np.array(faces)
118
+ all_pts.append(pts)
119
+ all_i.extend((fcs[:, 0] + offset).tolist())
120
+ all_j.extend((fcs[:, 1] + offset).tolist())
121
+ all_k.extend((fcs[:, 2] + offset).tolist())
122
+ c = self._rgb_str_(pol.color)
123
+ all_colors.extend([c] * len(fcs))
124
+ offset += len(pts)
125
+ pts_arr = np.concatenate(all_pts, axis=0)
126
+ return go.Mesh3d(
127
+ x=pts_arr[:, 0], y=pts_arr[:, 1], z=pts_arr[:, 2],
128
+ i=all_i, j=all_j, k=all_k,
129
+ facecolor=all_colors,
130
+ flatshading=True, name=name,
131
+ hoverinfo="skip",
132
+ lighting=dict(ambient=0.85, diffuse=0.5, specular=0.05),
133
+ )
134
+
135
+ def _merge_outlines_to_plotly_(self, polygon_list):
136
+ """Merge outlines of multiple Polygon_3D into a single go.Scatter3d."""
137
+ import plotly.graph_objects as go
138
+ xs, ys, zs = [], [], []
139
+ for pol in polygon_list:
140
+ boundary = np.array(pol.polygon3D)
141
+ if len(boundary) == 0:
142
+ continue
143
+ xs.extend(list(boundary[:, 0]) + [boundary[0, 0], None])
144
+ ys.extend(list(boundary[:, 1]) + [boundary[0, 1], None])
145
+ zs.extend(list(boundary[:, 2]) + [boundary[0, 2], None])
146
+ return go.Scatter3d(
147
+ x=xs, y=ys, z=zs,
148
+ mode="lines",
149
+ line=dict(color="black", width=2),
150
+ showlegend=False, hoverinfo="skip",
151
+ )
152
+
153
+ def _scene_bounds_(self, pols):
154
+ """Return (xmin, xmax, ymin, ymax, zmin, zmax) for a list of Polygon_3D."""
155
+ all_pts = np.concatenate([np.array(p.polygon3D) for p in pols if len(p.polygon3D) > 0], axis=0)
156
+ return (all_pts[:, 0].min(), all_pts[:, 0].max(),
157
+ all_pts[:, 1].min(), all_pts[:, 1].max(),
158
+ all_pts[:, 2].min(), all_pts[:, 2].max())
159
+
160
+ def _zero_plane_traces_(self, pols):
161
+ """Return thin-line Scatter3d outlines for the x=0, y=0, z=0 planes."""
162
+ import plotly.graph_objects as go
163
+ if not pols:
164
+ return []
165
+ xmin, xmax, ymin, ymax, zmin, zmax = self._scene_bounds_(pols)
166
+ style = dict(color="rgba(100,100,100,0.5)", width=1)
167
+ traces = []
168
+
169
+ def rect_trace(xs, ys, zs):
170
+ return go.Scatter3d(
171
+ x=xs, y=ys, z=zs,
172
+ mode="lines",
173
+ line=style,
174
+ showlegend=False, hoverinfo="skip",
175
+ )
176
+
177
+ # z=0 plane outline (only if 0 is within z range)
178
+ if zmin <= 0 <= zmax:
179
+ traces.append(rect_trace(
180
+ [xmin, xmax, xmax, xmin, xmin],
181
+ [ymin, ymin, ymax, ymax, ymin],
182
+ [0, 0, 0, 0, 0],
183
+ ))
184
+ # x=0 plane outline
185
+ if xmin <= 0 <= xmax:
186
+ traces.append(rect_trace(
187
+ [0, 0, 0, 0, 0],
188
+ [ymin, ymax, ymax, ymin, ymin],
189
+ [zmin, zmin, zmax, zmax, zmin],
190
+ ))
191
+ # y=0 plane outline
192
+ if ymin <= 0 <= ymax:
193
+ traces.append(rect_trace(
194
+ [xmin, xmax, xmax, xmin, xmin],
195
+ [0, 0, 0, 0, 0],
196
+ [zmin, zmin, zmax, zmax, zmin],
197
+ ))
198
+ return traces
199
+
200
+ def _plotly_scene_layout_(self):
201
+ import plotly.graph_objects as go
202
+ axis_style = dict(
203
+ showbackground=True,
204
+ backgroundcolor="rgb(220, 220, 220)",
205
+ tickfont=dict(size=9, color="gray"),
206
+ title_font=dict(size=10, color="gray"),
207
+ )
208
+ return go.Layout(
209
+ paper_bgcolor="rgb(235, 235, 235)",
210
+ scene=dict(
211
+ xaxis=dict(title="X [m]", **axis_style),
212
+ yaxis=dict(title="Y [m]", **axis_style),
213
+ zaxis=dict(title="Z [m]", **axis_style),
214
+ aspectmode="data",
215
+ bgcolor="rgb(235, 235, 235)",
216
+ ),
217
+ margin=dict(l=0, r=0, t=0, b=0),
218
+ showlegend=False,
219
+ )
220
+
221
+ # ── Public show methods ─────────────────────────────────────────────────
222
+
223
+ def show(self, polygons_type="initial", jupyter=False):
224
+ if jupyter:
225
+ import plotly.graph_objects as go
226
+ if polygons_type == "initial":
227
+ pols = [p for p in self.pol_3D if p.visible]
228
+ elif polygons_type == "Building_shadows":
229
+ pols = (list(self.pol_sunny) + list(self.pol_shadows) +
230
+ [p for p in self.pol_3D if p.visible and not p.calculate_shadows])
231
+ elif polygons_type == "sunny":
232
+ pols = list(self.pol_sunny)
233
+ elif polygons_type == "shadows":
234
+ pols = list(self.pol_shadows)
235
+ else:
236
+ pols = list(self.pol_sunny) + list(self.pol_shadows)
237
+ traces = []
238
+ for pol in pols:
239
+ mesh, outline = self._polygon_to_plotly_(pol)
240
+ traces.extend([mesh, outline])
241
+ traces.extend(self._zero_plane_traces_(pols))
242
+ go.Figure(data=traces, layout=self._plotly_scene_layout_()).show()
243
+ else:
244
+ vedo.settings.default_backend = "vtk"
245
+ meshes = self.get_vedo_meshes(polygons_type)
246
+ text_obj = [None]
247
+
248
+ def on_left_click(evt):
249
+ if text_obj[0] is not None:
250
+ vp.remove(text_obj[0])
251
+ msh = evt.object
252
+ if not msh:
253
+ text_obj[0] = vedo.Text2D(" ", pos='top-left')
254
+ else:
255
+ text_obj[0] = vedo.Text2D(f"{msh.polygon_name}", pos='top-left')
256
+ vp.add(text_obj[0])
257
+ vp.render()
258
+
259
+ vp = vedo.Plotter(title="opensimula")
260
+ vp.add_callback('mouse click', on_left_click)
261
+ vp.show(*meshes, axes=1, viewup="z").close()
262
+
263
+ def show_animation(self, texts, cosines, polygons_type="initial", jupyter=False):
264
+ if jupyter:
265
+ import plotly.graph_objects as go
266
+
267
+ # Static traces: surfaces that don't participate in shadow calculation
268
+ static_pols = [p for p in self.pol_3D if p.visible and not p.calculate_shadows]
269
+ static_traces = []
270
+ for pol in static_pols:
271
+ mesh, outline = self._polygon_to_plotly_(pol)
272
+ static_traces.extend([mesh, outline])
273
+ all_pols = list(self.pol_3D) + list(self.pol_sunny) + list(self.pol_shadows)
274
+ static_traces.extend(self._zero_plane_traces_([p for p in all_pols if len(p.polygon3D) > 0]))
275
+ n_static = len(static_traces)
276
+
277
+ # Build frames (sunny + shadow merged meshes update per frame)
278
+ self.calculate_shadows(cosines[0], create_polygons=True)
279
+ init_sunny = self._merge_to_plotly_(self.pol_sunny, "sunny")
280
+ init_sunny_outline = self._merge_outlines_to_plotly_(self.pol_sunny)
281
+ init_shadow = self._merge_to_plotly_(self.pol_shadows, "shadow")
282
+ init_shadow_outline = self._merge_outlines_to_plotly_(self.pol_shadows)
283
+
284
+ frames = []
285
+ for i, (cos, text) in enumerate(zip(cosines, texts)):
286
+ self.calculate_shadows(cos, create_polygons=True)
287
+ frames.append(go.Frame(
288
+ data=[self._merge_to_plotly_(self.pol_sunny, "sunny"),
289
+ self._merge_outlines_to_plotly_(self.pol_sunny),
290
+ self._merge_to_plotly_(self.pol_shadows, "shadow"),
291
+ self._merge_outlines_to_plotly_(self.pol_shadows)],
292
+ traces=[n_static, n_static + 1, n_static + 2, n_static + 3],
293
+ name=str(i),
294
+ layout=go.Layout(title_text=text),
295
+ ))
296
+
297
+ layout = self._plotly_scene_layout_()
298
+ layout.update(
299
+ title_text=texts[0],
300
+ margin=dict(l=0, r=0, t=30, b=80),
301
+ updatemenus=[dict(
302
+ type="buttons", direction="left",
303
+ pad={"r": 10, "t": 20}, x=0.1, y=0,
304
+ buttons=[
305
+ dict(label="▶ Play", method="animate",
306
+ args=[None, {"frame": {"duration": 800, "redraw": True},
307
+ "fromcurrent": True}]),
308
+ dict(label="⏸ Pause", method="animate",
309
+ args=[[None], {"frame": {"duration": 0}, "mode": "immediate"}]),
310
+ ],
311
+ )],
312
+ sliders=[dict(
313
+ active=0,
314
+ steps=[dict(
315
+ method="animate",
316
+ args=[[str(i)], {"frame": {"duration": 0, "redraw": True},
317
+ "mode": "immediate"}],
318
+ label=texts[i],
319
+ ) for i in range(len(texts))],
320
+ x=0, y=0, len=1.0, pad={"b": 10, "t": 30},
321
+ )],
322
+ )
323
+ go.Figure(
324
+ data=static_traces + [init_sunny, init_sunny_outline, init_shadow, init_shadow_outline],
325
+ layout=layout,
326
+ frames=frames,
327
+ ).show()
328
+ else:
329
+ vedo.settings.default_backend = "vtk"
330
+ meshes = []
331
+
332
+ def slider_func(widget, event):
333
+ idx = int(widget.value)
334
+ widget.title = texts[idx]
335
+ vp.remove(meshes.pop(0))
336
+ cos = cosines[idx]
337
+ self.calculate_shadows(cos, create_polygons=True)
338
+ meshes.append(self.get_vedo_meshes(polygons_type))
339
+ vp.add(meshes[0])
340
+
341
+ self.calculate_shadows(cosines[0], create_polygons=True)
342
+ meshes.append(self.get_vedo_meshes(polygons_type))
343
+ vp = vedo.Plotter(title="opensimula", axes=1)
344
+ vp.add(meshes[0])
345
+ vp.add_slider(
346
+ slider_func,
347
+ 0, len(cosines) - 1,
348
+ value=0,
349
+ pos="bottom-right",
350
+ title=texts[0],
351
+ )
352
+ vp.show(viewup="z").close()
353
+
354
+ def delete_shadows(self):
355
+ self.pol_sunny = []
356
+ self.pol_shadows = []
357
+ self.sunny_fraction = []
358
+
359
+ def calculate_shadows(self, sun_position, create_polygons=True):
360
+ self.sunny_fraction = []
361
+ if create_polygons:
362
+ self.pol_sunny = []
363
+ self.pol_shadows = []
364
+
365
+ for polygon in self.pol_3D:
366
+ if polygon.calculate_shadows:
367
+ sunny_polygons, shadow_polygons = polygon.get_sunny_shadow_polygon3D(
368
+ self, sun_position
369
+ )
370
+ if sunny_polygons is not None:
371
+ sunny_area = 0
372
+ for sunny_polygon in sunny_polygons:
373
+ if create_polygons:
374
+ self.pol_sunny.append(sunny_polygon)
375
+ sunny_area += sunny_polygon.area
376
+ self.sunny_fraction.append(sunny_area / polygon.area)
377
+ else:
378
+ self.sunny_fraction.append(0) # No sunny area
379
+ if create_polygons:
380
+ if shadow_polygons is not None:
381
+ for shadow_polygon in shadow_polygons:
382
+ self.pol_shadows.append(shadow_polygon)
383
+
384
+ def get_sunny_polygon_list(self):
385
+ sunny_polygons = []
386
+ for polygon in self.pol_3D:
387
+ if polygon.calculate_shadows:
388
+ sunny_polygons.append(polygon)
389
+ return sunny_polygons
390
+
391
+ def get_sunny_index(self, name):
392
+ sunny_polygons = self.get_sunny_polygon_list()
393
+ for i in range(len(sunny_polygons)):
394
+ if sunny_polygons[i].name == name:
395
+ return i
396
+ return None
397
+
398
+ def calculate_solar_tables(self):
399
+ self._calculate_shadow_interpolation_table()
400
+ self._calculate_diffuse_shadow()
401
+ self.solar_tables_calculated = True
402
+
403
+ def _calculate_shadow_interpolation_table(self):
404
+ self.shadow_azimuth_grid = np.linspace(0, 350, 36)
405
+ self.shadow_altitude_grid = np.linspace(-85, 85, 18)
406
+ sunny_polygons = self.get_sunny_polygon_list()
407
+ self.sunny_fraction_tables = np.zeros((len(sunny_polygons), 36, 18))
408
+ j = 0
409
+ for azimuth in self.shadow_azimuth_grid:
410
+ azi_rd = math.radians(azimuth)
411
+ k = 0
412
+ for altitude in self.shadow_altitude_grid:
413
+ alt_rd = math.radians(altitude)
414
+ sun_position = np.array(
415
+ [
416
+ math.cos(alt_rd) * math.sin(azi_rd),
417
+ -math.cos(alt_rd) * math.cos(azi_rd),
418
+ math.sin(alt_rd),
419
+ ]
420
+ )
421
+
422
+ self.calculate_shadows(sun_position, create_polygons=False)
423
+ sunny_frac = self.sunny_fraction
424
+ for i in range(len(sunny_frac)):
425
+ self.sunny_fraction_tables[i][j][k] = sunny_frac[i]
426
+ k = k + 1
427
+ j = j + 1
428
+
429
+ self.sunny_interpolation_functions = []
430
+ for i in range(0, len(sunny_polygons)):
431
+ self.sunny_interpolation_functions.append(
432
+ RegularGridInterpolator(
433
+ (self.shadow_azimuth_grid, self.shadow_altitude_grid),
434
+ self.sunny_fraction_tables[i],
435
+ bounds_error=False,
436
+ fill_value=None,
437
+ method="cubic",
438
+ )
439
+ )
440
+
441
+ def _calculate_diffuse_shadow(self):
442
+ def integral(i,polygon_i):
443
+ sunny_value = 0
444
+ shadow_value = 0
445
+ n = 0
446
+ for j in range(len(self.shadow_azimuth_grid)):
447
+ for k in range(len(self.shadow_altitude_grid)):
448
+ theta = polygon_i.get_angle_with_normal(
449
+ self.shadow_azimuth_grid[j], self.shadow_altitude_grid[k]
450
+ )
451
+ if theta < math.pi / 2:
452
+ f = 0.5 * math.sin(2 * theta)
453
+ sunny_value = sunny_value + f
454
+ shadow_value = (
455
+ shadow_value + f * self.sunny_fraction_tables[i][j][k]
456
+ )
457
+ n = n + 1
458
+ return shadow_value / sunny_value
459
+
460
+ self.shadow_diffuse_fraction = []
461
+ sunny_polygon_list = self.get_sunny_polygon_list()
462
+ for i in range(0, len(sunny_polygon_list)):
463
+ polygon_i = sunny_polygon_list[i]
464
+ self.shadow_diffuse_fraction.append(integral(i, polygon_i))
465
+
466
+ def get_diffuse_sunny_fraction(self, sunny_i):
467
+ if self.solar_tables_calculated == True:
468
+ return self.shadow_diffuse_fraction[sunny_i]
469
+ else:
470
+ return 1
471
+
472
+ def get_direct_interpolated_sunny_fraction(self, sunny_i, azi, alt):
473
+ if self.solar_tables_calculated == True:
474
+ if azi < 0:
475
+ azi = azi + 360
476
+ return self.sunny_interpolation_functions[sunny_i]((azi, alt))
477
+ else:
478
+ return 1
479
+
480
+ def get_direct_sunny_fraction(self, sunny_i):
481
+ if self.sunny_fraction == []:
482
+ return 1
483
+ else:
484
+ return self.sunny_fraction[sunny_i]
485
+
486
+ def show_sunny_fraction(self, sunny_i):
487
+ fig, ax = plt.subplots()
488
+ X, Y = np.meshgrid(self.shadow_azimuth_grid, self.shadow_altitude_grid)
489
+ ax.imshow(self.sunny_fraction_tables[sunny_i], vmin=0, vmax=1)
490
+ plt.show()
491
+
492
+
@@ -1,8 +1,8 @@
1
1
  import math
2
2
  import numpy as np
3
- from shapely.geometry import Polygon
3
+ from shapely.geometry import Polygon, MultiPoint
4
+ from shapely.ops import triangulate as shapely_triangulate
4
5
  import vedo
5
- from triangle import triangulate
6
6
 
7
7
  class Polygon_3D():
8
8
  def __init__(self, name, origin, azimuth, altitude, polygon2D, holes2D=[], color="white", opacity=1.0, visible=True,shading=True,calculate_shadows=True):
@@ -74,32 +74,27 @@ class Polygon_3D():
74
74
  return mesh
75
75
 
76
76
  def _triangulate_(self):
77
- def edge_idxs(nv):
78
- i = np.append(np.arange(nv), 0)
79
- return np.stack([i[:-1], i[1:]], axis=1)
77
+ all_verts = list(self.polygon2D)
78
+ for hole in self.holes2D:
79
+ all_verts.extend(hole)
80
80
 
81
- nv = 0
82
- verts, edges = [], []
83
- for loop in (self.polygon2D, *self.holes2D):
84
- verts.append(loop)
85
- edges.append(nv + edge_idxs(len(loop)))
86
- nv += len(loop)
81
+ tris = [t for t in shapely_triangulate(MultiPoint(all_verts))
82
+ if self.shapely_polygon.contains(t.centroid)]
87
83
 
88
- verts, edges = np.concatenate(verts), np.concatenate(edges)
89
- # Triangulate needs to know a single interior point for each hole
90
- holes = np.array([np.mean(h, axis=0) for h in self.holes2D])
91
- # Because triangulate is a wrapper around a C library the syntax is a little weird, 'p' here means planar straight line graph
92
- if self.has_holes():
93
- d = triangulate(dict(vertices=verts, segments=edges, holes=holes), opts='p')
94
- else:
95
- d = triangulate(dict(vertices=verts, segments=edges), opts='p')
96
- # Convert back
97
- v, f = d['vertices'], d['triangles']
98
- nv, nf = len(v), len(f)
99
- points = np.concatenate([v, np.zeros((nv, 1))], axis=1)
100
- # Creo que lo tengo que hacer en 2D y luego pasarlo a 3D
101
- #faces = np.concatenate([np.full((nf, 1), 3), f], axis=1).reshape(-1)
102
- return (self._convert_2D_to_3D_(points), f)
84
+ vert_map = {}
85
+ verts = []
86
+ faces = []
87
+ for tri in tris:
88
+ face = []
89
+ for c in list(tri.exterior.coords)[:-1]:
90
+ key = (round(c[0], 10), round(c[1], 10))
91
+ if key not in vert_map:
92
+ vert_map[key] = len(verts)
93
+ verts.append(c)
94
+ face.append(vert_map[key])
95
+ faces.append(face)
96
+
97
+ return (self._convert_2D_to_3D_(np.array(verts)), np.array(faces))
103
98
 
104
99
  def _are_vertices_counterclockwise_(self,puntos):
105
100
  # Se suma el primer punto al final para cerrar el polígono.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensimula
3
- Version: 0.6.1
3
+ Version: 0.7.1
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
@@ -18,7 +18,6 @@ Requires-Dist: psychrolib>=2.5.0
18
18
  Requires-Dist: scipy>=1.16.2
19
19
  Requires-Dist: shapely>=2.1.1
20
20
  Requires-Dist: tqdm>=4.67.1
21
- Requires-Dist: triangle>=20250106
22
21
  Requires-Dist: vedo>=2025.5.4
23
22
  Dynamic: license-file
24
23
 
@@ -20,17 +20,16 @@ src/opensimula/components/Building.py
20
20
  src/opensimula/components/Building_surface.py
21
21
  src/opensimula/components/Calculator.py
22
22
  src/opensimula/components/Construction.py
23
+ src/opensimula/components/DX_unit.py
23
24
  src/opensimula/components/Day_schedule.py
25
+ src/opensimula/components/Fan.py
24
26
  src/opensimula/components/File_data.py
25
27
  src/opensimula/components/File_met.py
26
28
  src/opensimula/components/Frame.py
27
29
  src/opensimula/components/Glazing.py
28
- src/opensimula/components/HVAC_DX_equipment.py
29
30
  src/opensimula/components/HVAC_DX_system.py
30
31
  src/opensimula/components/HVAC_MZW_system.py
31
32
  src/opensimula/components/HVAC_SZW_system.py
32
- src/opensimula/components/HVAC_coil_equipment.py
33
- src/opensimula/components/HVAC_fan_equipment.py
34
33
  src/opensimula/components/HVAC_perfect_system.py
35
34
  src/opensimula/components/Material.py
36
35
  src/opensimula/components/Opening.py
@@ -40,6 +39,7 @@ src/opensimula/components/Space.py
40
39
  src/opensimula/components/Space_type.py
41
40
  src/opensimula/components/Surface.py
42
41
  src/opensimula/components/Test_component.py
42
+ src/opensimula/components/Water_coil.py
43
43
  src/opensimula/components/Week_schedule.py
44
44
  src/opensimula/components/Year_schedule.py
45
45
  src/opensimula/components/__init__.py
@@ -10,5 +10,4 @@ psychrolib>=2.5.0
10
10
  scipy>=1.16.2
11
11
  shapely>=2.1.1
12
12
  tqdm>=4.67.1
13
- triangle>=20250106
14
13
  vedo>=2025.5.4
@@ -2,10 +2,10 @@ import opensimula as osim
2
2
  import pytest
3
3
 
4
4
  case_dict = {
5
- "name": "Test_HVAC_DX_equipment",
5
+ "name": "Test_DX_unit",
6
6
  "components": [
7
7
  {
8
- "type": "HVAC_DX_equipment",
8
+ "type": "DX_unit",
9
9
  "name": "HVAC_equipment",
10
10
  "nominal_air_flow": 0.4248,
11
11
  "nominal_total_cooling_capacity": 7951,
@@ -323,7 +323,7 @@ case610_dict = {
323
323
  "altitude": 90
324
324
  },
325
325
  {
326
- "type": "HVAC_DX_equipment",
326
+ "type": "DX_unit",
327
327
  "name": "HVAC_equipment",
328
328
  "nominal_air_flow": 0.417,
329
329
  "nominal_total_cooling_capacity": 6900,
@@ -1,257 +0,0 @@
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
-
File without changes
File without changes
File without changes