db-process 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- db_process-0.1.0/LICENSE +21 -0
- db_process-0.1.0/PKG-INFO +18 -0
- db_process-0.1.0/README.md +2 -0
- db_process-0.1.0/db_process/__init__.py +83 -0
- db_process-0.1.0/db_process/commands.py +449 -0
- db_process-0.1.0/db_process/executable.py +76 -0
- db_process-0.1.0/db_process/runner.py +298 -0
- db_process-0.1.0/db_process.egg-info/PKG-INFO +18 -0
- db_process-0.1.0/db_process.egg-info/SOURCES.txt +14 -0
- db_process-0.1.0/db_process.egg-info/dependency_links.txt +1 -0
- db_process-0.1.0/db_process.egg-info/requires.txt +5 -0
- db_process-0.1.0/db_process.egg-info/top_level.txt +1 -0
- db_process-0.1.0/pyproject.toml +29 -0
- db_process-0.1.0/setup.cfg +4 -0
- db_process-0.1.0/tests/test_commands.py +316 -0
- db_process-0.1.0/tests/test_executable.py +47 -0
db_process-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tokarzewski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: db-process
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python interface to DesignBuilder command line processing.
|
|
5
|
+
Author-email: Bartlomiej Tokarzewski <bartlomiej.tokarzewski@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/tokarzewski/db-process
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/tokarzewski/db-process/issues
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: psutil>=5.9.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff; extra == "dev"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# db-process
|
|
18
|
+
DesignBuilder command line processing
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
db-process
|
|
3
|
+
====================================
|
|
4
|
+
Python interface to DesignBuilder command line processing.
|
|
5
|
+
|
|
6
|
+
This package provides:
|
|
7
|
+
|
|
8
|
+
- **Executable discovery** — find DesignBuilder.exe across env vars,
|
|
9
|
+
default install paths, and the system PATH.
|
|
10
|
+
- **Typed command model** — dataclasses for every ``/process=`` command
|
|
11
|
+
documented in DesignBuilder Help, with a fluent builder API.
|
|
12
|
+
- **Subprocess execution** — blocking and non-blocking modes, timeout
|
|
13
|
+
handling, and idle-detection termination.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from db_process.commands import (
|
|
17
|
+
ChangeAttributeValue,
|
|
18
|
+
ExportAsXML,
|
|
19
|
+
ExternalCommand,
|
|
20
|
+
ImportLibraryData,
|
|
21
|
+
ImportModelData,
|
|
22
|
+
NoClose,
|
|
23
|
+
ProcessChain,
|
|
24
|
+
RunCalculation,
|
|
25
|
+
Screen,
|
|
26
|
+
SimEndDate,
|
|
27
|
+
SimStartDate,
|
|
28
|
+
SwitchScreen,
|
|
29
|
+
TabChange,
|
|
30
|
+
UseSimManager,
|
|
31
|
+
# Convenience factories
|
|
32
|
+
cfd_simulation,
|
|
33
|
+
daylighting,
|
|
34
|
+
eplus_simulation,
|
|
35
|
+
export_xml,
|
|
36
|
+
heating_and_cooling_design,
|
|
37
|
+
sbem_calculation,
|
|
38
|
+
)
|
|
39
|
+
from db_process.executable import find_designbuilder
|
|
40
|
+
from db_process.runner import (
|
|
41
|
+
RunHandle,
|
|
42
|
+
RunResult,
|
|
43
|
+
find_process,
|
|
44
|
+
kill_process,
|
|
45
|
+
kill_when_idle,
|
|
46
|
+
run,
|
|
47
|
+
run_async,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Executable
|
|
52
|
+
"find_designbuilder",
|
|
53
|
+
# Commands
|
|
54
|
+
"ChangeAttributeValue",
|
|
55
|
+
"ExportAsXML",
|
|
56
|
+
"ExternalCommand",
|
|
57
|
+
"ImportLibraryData",
|
|
58
|
+
"ImportModelData",
|
|
59
|
+
"NoClose",
|
|
60
|
+
"ProcessChain",
|
|
61
|
+
"RunCalculation",
|
|
62
|
+
"Screen",
|
|
63
|
+
"SimEndDate",
|
|
64
|
+
"SimStartDate",
|
|
65
|
+
"SwitchScreen",
|
|
66
|
+
"TabChange",
|
|
67
|
+
"UseSimManager",
|
|
68
|
+
# Convenience factories
|
|
69
|
+
"cfd_simulation",
|
|
70
|
+
"daylighting",
|
|
71
|
+
"eplus_simulation",
|
|
72
|
+
"export_xml",
|
|
73
|
+
"heating_and_cooling_design",
|
|
74
|
+
"sbem_calculation",
|
|
75
|
+
# Runner
|
|
76
|
+
"RunHandle",
|
|
77
|
+
"RunResult",
|
|
78
|
+
"find_process",
|
|
79
|
+
"kill_process",
|
|
80
|
+
"kill_when_idle",
|
|
81
|
+
"run",
|
|
82
|
+
"run_async",
|
|
83
|
+
]
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""
|
|
2
|
+
commands.py
|
|
3
|
+
====================================
|
|
4
|
+
Typed representation of DesignBuilder command line process commands.
|
|
5
|
+
|
|
6
|
+
DesignBuilder CLI syntax::
|
|
7
|
+
|
|
8
|
+
"DesignBuilder.exe" "MyFile.dsb" /process=Command1, Command2, ...
|
|
9
|
+
|
|
10
|
+
This module provides dataclasses for each command type and a
|
|
11
|
+
:class:`ProcessChain` builder to compose them into valid ``/process=`` strings.
|
|
12
|
+
|
|
13
|
+
Reference: DesignBuilder Help > Reference > Command Line Processing
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Optional, Union
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Screen / view commands
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
class Screen(str, Enum):
|
|
28
|
+
"""Available DesignBuilder screen commands."""
|
|
29
|
+
|
|
30
|
+
SIMULATION = "miGSS"
|
|
31
|
+
HEATING_DESIGN = "miGHL"
|
|
32
|
+
COOLING_DESIGN = "miGHG"
|
|
33
|
+
DAYLIGHTING = "miGDY"
|
|
34
|
+
SBEM_DSM = "miGCalculate"
|
|
35
|
+
CFD = "miGCFD"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Individual command dataclasses
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class SwitchScreen:
|
|
44
|
+
"""Change the view to a specific DesignBuilder screen.
|
|
45
|
+
|
|
46
|
+
Examples: miGSS, miGHL, miGHG, miGDY, miGCalculate, miGCFD
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
screen: Screen
|
|
50
|
+
|
|
51
|
+
def render(self) -> str:
|
|
52
|
+
return self.screen.value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class RunCalculation:
|
|
57
|
+
"""Run the calculation for the current screen (miTUpdate).
|
|
58
|
+
|
|
59
|
+
Generally this command should be placed after screen switching
|
|
60
|
+
and before any ChangeAttributeValue commands that follow it.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def render(self) -> str:
|
|
64
|
+
return "miTUpdate"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class TabChange:
|
|
69
|
+
"""Change the view to tab *n* on the current screen.
|
|
70
|
+
|
|
71
|
+
Example: ``TabChange_2`` on the Daylighting screen switches to
|
|
72
|
+
the Annual daylighting tab.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
tab: int
|
|
76
|
+
|
|
77
|
+
def __post_init__(self) -> None:
|
|
78
|
+
if self.tab < 1:
|
|
79
|
+
raise ValueError(f"Tab number must be >= 1, got {self.tab}")
|
|
80
|
+
|
|
81
|
+
def render(self) -> str:
|
|
82
|
+
return f"TabChange_{self.tab}"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class SimStartDate:
|
|
87
|
+
"""Set simulation start date.
|
|
88
|
+
|
|
89
|
+
Parameters are day and month as integers.
|
|
90
|
+
Example: ``SimStartDate 4 5`` sets start to May 4.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
day: int
|
|
94
|
+
month: int
|
|
95
|
+
|
|
96
|
+
def __post_init__(self) -> None:
|
|
97
|
+
if not (1 <= self.month <= 12):
|
|
98
|
+
raise ValueError(f"Month must be 1-12, got {self.month}")
|
|
99
|
+
if not (1 <= self.day <= 31):
|
|
100
|
+
raise ValueError(f"Day must be 1-31, got {self.day}")
|
|
101
|
+
|
|
102
|
+
def render(self) -> str:
|
|
103
|
+
return f"SimStartDate {self.day} {self.month}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class SimEndDate:
|
|
108
|
+
"""Set simulation end date.
|
|
109
|
+
|
|
110
|
+
Parameters are day and month as integers.
|
|
111
|
+
Example: ``SimEndDate 11 5`` sets end to May 11.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
day: int
|
|
115
|
+
month: int
|
|
116
|
+
|
|
117
|
+
def __post_init__(self) -> None:
|
|
118
|
+
if not (1 <= self.month <= 12):
|
|
119
|
+
raise ValueError(f"Month must be 1-12, got {self.month}")
|
|
120
|
+
if not (1 <= self.day <= 31):
|
|
121
|
+
raise ValueError(f"Day must be 1-31, got {self.day}")
|
|
122
|
+
|
|
123
|
+
def render(self) -> str:
|
|
124
|
+
return f"SimEndDate {self.day} {self.month}"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class ChangeAttributeValue:
|
|
129
|
+
"""Change a model attribute at building level.
|
|
130
|
+
|
|
131
|
+
Attribute value types (from documentation):
|
|
132
|
+
- Numeric enums: integer codes (e.g. ``Layout 0``)
|
|
133
|
+
- Booleans: ``1`` for True, ``0`` for False
|
|
134
|
+
- Real numbers: SI units, ``.`` decimal separator
|
|
135
|
+
- Database table selections: numeric Id (obtain via API)
|
|
136
|
+
|
|
137
|
+
Tip: Enable *Show attribute names in tooltips* in
|
|
138
|
+
Tools > Program Options > Interface to find attribute names.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
attribute: str
|
|
142
|
+
value: Union[str, int, float]
|
|
143
|
+
|
|
144
|
+
def render(self) -> str:
|
|
145
|
+
return f"ChangeAttributeValue {self.attribute} {self.value}"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class ExternalCommand:
|
|
150
|
+
"""Run an external command with optional arguments.
|
|
151
|
+
|
|
152
|
+
The external executable must be on the system PATH or in the
|
|
153
|
+
working directory.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
command: str
|
|
157
|
+
|
|
158
|
+
def render(self) -> str:
|
|
159
|
+
return f"ExternalCommand_{self.command}"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True)
|
|
163
|
+
class ImportModelData:
|
|
164
|
+
"""Import model data from a CSV file previously exported from the model."""
|
|
165
|
+
|
|
166
|
+
filepath: str
|
|
167
|
+
|
|
168
|
+
def render(self) -> str:
|
|
169
|
+
return f"ImportModelData_{self.filepath}"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(frozen=True)
|
|
173
|
+
class ImportLibraryData:
|
|
174
|
+
"""Import library data from a file."""
|
|
175
|
+
|
|
176
|
+
filepath: str
|
|
177
|
+
|
|
178
|
+
def render(self) -> str:
|
|
179
|
+
return f"ImportLibraryData_{self.filepath}"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True)
|
|
183
|
+
class ExportAsXML:
|
|
184
|
+
"""Export the model as a dsbXML file (miFExportAsXML)."""
|
|
185
|
+
|
|
186
|
+
def render(self) -> str:
|
|
187
|
+
return "miFExportAsXML"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class UseSimManager:
|
|
192
|
+
"""Ensure the simulation runs using the Simulation Manager.
|
|
193
|
+
|
|
194
|
+
Uses the Server and EnergyPlus server method settings
|
|
195
|
+
currently configured in the model.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def render(self) -> str:
|
|
199
|
+
return "UseSimManager"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass(frozen=True)
|
|
203
|
+
class NoClose:
|
|
204
|
+
"""Prevent DesignBuilder from shutting down after all commands."""
|
|
205
|
+
|
|
206
|
+
def render(self) -> str:
|
|
207
|
+
return "NoClose"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Union of all command types for type hints.
|
|
211
|
+
Command = Union[
|
|
212
|
+
SwitchScreen,
|
|
213
|
+
RunCalculation,
|
|
214
|
+
TabChange,
|
|
215
|
+
SimStartDate,
|
|
216
|
+
SimEndDate,
|
|
217
|
+
ChangeAttributeValue,
|
|
218
|
+
ExternalCommand,
|
|
219
|
+
ImportModelData,
|
|
220
|
+
ImportLibraryData,
|
|
221
|
+
ExportAsXML,
|
|
222
|
+
UseSimManager,
|
|
223
|
+
NoClose,
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
# ProcessChain builder
|
|
229
|
+
# ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
@dataclass
|
|
232
|
+
class ProcessChain:
|
|
233
|
+
"""Build a ``/process=`` command string from a sequence of commands.
|
|
234
|
+
|
|
235
|
+
Usage::
|
|
236
|
+
|
|
237
|
+
chain = (
|
|
238
|
+
ProcessChain()
|
|
239
|
+
.use_sim_manager()
|
|
240
|
+
.switch_screen(Screen.SIMULATION)
|
|
241
|
+
.sim_start_date(1, 1)
|
|
242
|
+
.sim_end_date(31, 12)
|
|
243
|
+
.change_attribute("OccupancyValue", 0.5)
|
|
244
|
+
.run()
|
|
245
|
+
)
|
|
246
|
+
print(chain.to_string())
|
|
247
|
+
# /process=UseSimManager, miGSS, SimStartDate 1 1, SimEndDate 31 12,
|
|
248
|
+
# ChangeAttributeValue OccupancyValue 0.5, miTUpdate
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
commands: list[Command] = field(default_factory=list)
|
|
252
|
+
|
|
253
|
+
# -- Fluent builder methods -----------------------------------------------
|
|
254
|
+
|
|
255
|
+
def add(self, command: Command) -> ProcessChain:
|
|
256
|
+
"""Add an arbitrary command to the chain."""
|
|
257
|
+
self.commands.append(command)
|
|
258
|
+
return self
|
|
259
|
+
|
|
260
|
+
def switch_screen(self, screen: Screen) -> ProcessChain:
|
|
261
|
+
"""Add a screen-switching command."""
|
|
262
|
+
return self.add(SwitchScreen(screen))
|
|
263
|
+
|
|
264
|
+
def run(self) -> ProcessChain:
|
|
265
|
+
"""Add miTUpdate (run calculation for current screen)."""
|
|
266
|
+
return self.add(RunCalculation())
|
|
267
|
+
|
|
268
|
+
def tab_change(self, tab: int) -> ProcessChain:
|
|
269
|
+
"""Switch to a specific tab on the current screen."""
|
|
270
|
+
return self.add(TabChange(tab))
|
|
271
|
+
|
|
272
|
+
def sim_start_date(self, day: int, month: int) -> ProcessChain:
|
|
273
|
+
"""Set simulation start date."""
|
|
274
|
+
return self.add(SimStartDate(day, month))
|
|
275
|
+
|
|
276
|
+
def sim_end_date(self, day: int, month: int) -> ProcessChain:
|
|
277
|
+
"""Set simulation end date."""
|
|
278
|
+
return self.add(SimEndDate(day, month))
|
|
279
|
+
|
|
280
|
+
def change_attribute(
|
|
281
|
+
self, attribute: str, value: Union[str, int, float]
|
|
282
|
+
) -> ProcessChain:
|
|
283
|
+
"""Change a model attribute value."""
|
|
284
|
+
return self.add(ChangeAttributeValue(attribute, value))
|
|
285
|
+
|
|
286
|
+
def external_command(self, command: str) -> ProcessChain:
|
|
287
|
+
"""Run an external command."""
|
|
288
|
+
return self.add(ExternalCommand(command))
|
|
289
|
+
|
|
290
|
+
def import_model_data(self, filepath: str) -> ProcessChain:
|
|
291
|
+
"""Import model data from a CSV file."""
|
|
292
|
+
return self.add(ImportModelData(filepath))
|
|
293
|
+
|
|
294
|
+
def import_library_data(self, filepath: str) -> ProcessChain:
|
|
295
|
+
"""Import library data from a file."""
|
|
296
|
+
return self.add(ImportLibraryData(filepath))
|
|
297
|
+
|
|
298
|
+
def export_as_xml(self) -> ProcessChain:
|
|
299
|
+
"""Export the model as dsbXML."""
|
|
300
|
+
return self.add(ExportAsXML())
|
|
301
|
+
|
|
302
|
+
def use_sim_manager(self) -> ProcessChain:
|
|
303
|
+
"""Use the Simulation Manager for the run."""
|
|
304
|
+
return self.add(UseSimManager())
|
|
305
|
+
|
|
306
|
+
def no_close(self) -> ProcessChain:
|
|
307
|
+
"""Prevent DesignBuilder from closing after processing."""
|
|
308
|
+
return self.add(NoClose())
|
|
309
|
+
|
|
310
|
+
# -- Rendering ------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
def to_string(self) -> str:
|
|
313
|
+
"""Render the chain as a ``/process=...`` argument string.
|
|
314
|
+
|
|
315
|
+
Raises
|
|
316
|
+
------
|
|
317
|
+
ValueError
|
|
318
|
+
If the chain contains no commands.
|
|
319
|
+
"""
|
|
320
|
+
if not self.commands:
|
|
321
|
+
raise ValueError("ProcessChain is empty — add at least one command.")
|
|
322
|
+
parts = [cmd.render() for cmd in self.commands]
|
|
323
|
+
return "/process=" + ", ".join(parts)
|
|
324
|
+
|
|
325
|
+
def to_list(self) -> list[str]:
|
|
326
|
+
"""Return the chain as a list of individual command strings."""
|
|
327
|
+
return [cmd.render() for cmd in self.commands]
|
|
328
|
+
|
|
329
|
+
def __str__(self) -> str:
|
|
330
|
+
return self.to_string()
|
|
331
|
+
|
|
332
|
+
def __repr__(self) -> str:
|
|
333
|
+
return f"ProcessChain({self.commands!r})"
|
|
334
|
+
|
|
335
|
+
def __len__(self) -> int:
|
|
336
|
+
return len(self.commands)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
# Convenience factory functions
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
def eplus_simulation(
|
|
344
|
+
*,
|
|
345
|
+
sim_start_date: Optional[tuple[int, int]] = None,
|
|
346
|
+
sim_end_date: Optional[tuple[int, int]] = None,
|
|
347
|
+
use_sim_manager: bool = False,
|
|
348
|
+
attributes: Optional[list[tuple[str, Union[str, int, float]]]] = None,
|
|
349
|
+
no_close: bool = False,
|
|
350
|
+
) -> ProcessChain:
|
|
351
|
+
"""Build a standard EnergyPlus simulation command chain.
|
|
352
|
+
|
|
353
|
+
This produces the equivalent of what ``db-batch`` creates for
|
|
354
|
+
``analysis_type="eplus"``.
|
|
355
|
+
"""
|
|
356
|
+
chain = ProcessChain()
|
|
357
|
+
|
|
358
|
+
if use_sim_manager:
|
|
359
|
+
chain.use_sim_manager()
|
|
360
|
+
|
|
361
|
+
if sim_start_date:
|
|
362
|
+
chain.sim_start_date(sim_start_date[0], sim_start_date[1])
|
|
363
|
+
|
|
364
|
+
if sim_end_date:
|
|
365
|
+
chain.sim_end_date(sim_end_date[0], sim_end_date[1])
|
|
366
|
+
|
|
367
|
+
if attributes:
|
|
368
|
+
for attr, val in attributes:
|
|
369
|
+
chain.change_attribute(attr, val)
|
|
370
|
+
|
|
371
|
+
chain.switch_screen(Screen.SIMULATION)
|
|
372
|
+
chain.run()
|
|
373
|
+
|
|
374
|
+
if no_close:
|
|
375
|
+
chain.no_close()
|
|
376
|
+
|
|
377
|
+
return chain
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def sbem_calculation(
|
|
381
|
+
*,
|
|
382
|
+
sim_start_date: Optional[tuple[int, int]] = None,
|
|
383
|
+
sim_end_date: Optional[tuple[int, int]] = None,
|
|
384
|
+
attributes: Optional[list[tuple[str, Union[str, int, float]]]] = None,
|
|
385
|
+
no_close: bool = False,
|
|
386
|
+
) -> ProcessChain:
|
|
387
|
+
"""Build a standard SBEM/DSM calculation command chain."""
|
|
388
|
+
chain = ProcessChain()
|
|
389
|
+
|
|
390
|
+
if sim_start_date:
|
|
391
|
+
chain.sim_start_date(sim_start_date[0], sim_start_date[1])
|
|
392
|
+
|
|
393
|
+
if sim_end_date:
|
|
394
|
+
chain.sim_end_date(sim_end_date[0], sim_end_date[1])
|
|
395
|
+
|
|
396
|
+
if attributes:
|
|
397
|
+
for attr, val in attributes:
|
|
398
|
+
chain.change_attribute(attr, val)
|
|
399
|
+
|
|
400
|
+
chain.switch_screen(Screen.SBEM_DSM)
|
|
401
|
+
chain.run()
|
|
402
|
+
|
|
403
|
+
if no_close:
|
|
404
|
+
chain.no_close()
|
|
405
|
+
|
|
406
|
+
return chain
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def export_xml() -> ProcessChain:
|
|
410
|
+
"""Build a command chain to export the model as dsbXML."""
|
|
411
|
+
return ProcessChain().export_as_xml()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def heating_and_cooling_design(*, no_close: bool = False) -> ProcessChain:
|
|
415
|
+
"""Run both Heating and Cooling Design calculations."""
|
|
416
|
+
chain = (
|
|
417
|
+
ProcessChain()
|
|
418
|
+
.switch_screen(Screen.HEATING_DESIGN)
|
|
419
|
+
.run()
|
|
420
|
+
.switch_screen(Screen.COOLING_DESIGN)
|
|
421
|
+
.run()
|
|
422
|
+
)
|
|
423
|
+
if no_close:
|
|
424
|
+
chain.no_close()
|
|
425
|
+
return chain
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def daylighting(
|
|
429
|
+
*, run_annual: bool = False, no_close: bool = False
|
|
430
|
+
) -> ProcessChain:
|
|
431
|
+
"""Run daylighting calculation, optionally including annual daylighting.
|
|
432
|
+
|
|
433
|
+
When *run_annual* is True, runs both Illuminance (tab 1) and
|
|
434
|
+
Annual daylighting (tab 2) calculations.
|
|
435
|
+
"""
|
|
436
|
+
chain = ProcessChain().switch_screen(Screen.DAYLIGHTING).run()
|
|
437
|
+
if run_annual:
|
|
438
|
+
chain.tab_change(2).run()
|
|
439
|
+
if no_close:
|
|
440
|
+
chain.no_close()
|
|
441
|
+
return chain
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def cfd_simulation(*, no_close: bool = False) -> ProcessChain:
|
|
445
|
+
"""Run a CFD simulation."""
|
|
446
|
+
chain = ProcessChain().switch_screen(Screen.CFD).run()
|
|
447
|
+
if no_close:
|
|
448
|
+
chain.no_close()
|
|
449
|
+
return chain
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
executable.py
|
|
3
|
+
====================================
|
|
4
|
+
Locate and validate the DesignBuilder executable.
|
|
5
|
+
|
|
6
|
+
Resolution order:
|
|
7
|
+
1. Explicit path argument
|
|
8
|
+
2. DESIGNBUILDER_EXE environment variable
|
|
9
|
+
3. Common Windows installation directories
|
|
10
|
+
4. System PATH (via shutil.which)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
# Environment variable to override the DesignBuilder executable path.
|
|
19
|
+
ENV_VAR = "DESIGNBUILDER_EXE"
|
|
20
|
+
|
|
21
|
+
# Common DesignBuilder installation paths (Windows).
|
|
22
|
+
DEFAULT_INSTALL_PATHS = [
|
|
23
|
+
Path(r"C:\Program Files (x86)\DesignBuilder\DesignBuilder.exe"),
|
|
24
|
+
Path(r"C:\Program Files\DesignBuilder\DesignBuilder.exe"),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def find_designbuilder(exe_path: Optional[str | Path] = None) -> Path:
|
|
29
|
+
"""Locate the DesignBuilder executable.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
exe_path : str or Path, optional
|
|
34
|
+
Explicit path to DesignBuilder.exe. Takes highest priority.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
Path
|
|
39
|
+
Resolved path to the executable.
|
|
40
|
+
|
|
41
|
+
Raises
|
|
42
|
+
------
|
|
43
|
+
FileNotFoundError
|
|
44
|
+
If DesignBuilder cannot be found via any method.
|
|
45
|
+
"""
|
|
46
|
+
# 1. Explicit argument
|
|
47
|
+
if exe_path is not None:
|
|
48
|
+
p = Path(exe_path)
|
|
49
|
+
if p.is_file():
|
|
50
|
+
return p
|
|
51
|
+
raise FileNotFoundError(f"DesignBuilder not found at: {p}")
|
|
52
|
+
|
|
53
|
+
# 2. Environment variable
|
|
54
|
+
env = os.environ.get(ENV_VAR)
|
|
55
|
+
if env:
|
|
56
|
+
p = Path(env)
|
|
57
|
+
if p.is_file():
|
|
58
|
+
return p
|
|
59
|
+
raise FileNotFoundError(
|
|
60
|
+
f"{ENV_VAR} is set to '{env}' but the file does not exist."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 3. Common install locations
|
|
64
|
+
for p in DEFAULT_INSTALL_PATHS:
|
|
65
|
+
if p.is_file():
|
|
66
|
+
return p
|
|
67
|
+
|
|
68
|
+
# 4. System PATH
|
|
69
|
+
which = shutil.which("DesignBuilder")
|
|
70
|
+
if which:
|
|
71
|
+
return Path(which)
|
|
72
|
+
|
|
73
|
+
raise FileNotFoundError(
|
|
74
|
+
"Could not find DesignBuilder.exe. "
|
|
75
|
+
f"Set the {ENV_VAR} environment variable or pass the path explicitly."
|
|
76
|
+
)
|