idfpy 25.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- idfpy/__init__.py +23 -0
- idfpy/__main__.py +3 -0
- idfpy/cli.py +49 -0
- idfpy/codegen/__init__.py +4 -0
- idfpy/codegen/field_parser.py +264 -0
- idfpy/codegen/model_generator.py +645 -0
- idfpy/codegen/schema_parser.py +334 -0
- idfpy/codegen/template_filters.py +711 -0
- idfpy/codegen/templates/idf_model_py.jinja2 +104 -0
- idfpy/codegen/templates/init_py.jinja2 +114 -0
- idfpy/codegen/templates/ref_meta_py.jinja2 +48 -0
- idfpy/codegen/templates/refs_py.jinja2 +49 -0
- idfpy/ext/__init__.py +8 -0
- idfpy/ext/geometry/__init__.py +14 -0
- idfpy/ext/geometry/functions.py +189 -0
- idfpy/ext/geometry/mixins.py +100 -0
- idfpy/idf.py +1370 -0
- idfpy/models/__init__.py +18487 -0
- idfpy/models/_base.py +203 -0
- idfpy/models/_errors.py +54 -0
- idfpy/models/_metadata.py +116 -0
- idfpy/models/_ref_meta.py +10060 -0
- idfpy/models/_refs.py +982 -0
- idfpy/models/advanced_construction.py +3950 -0
- idfpy/models/air_distribution.py +711 -0
- idfpy/models/availability_managers.py +784 -0
- idfpy/models/coils.py +16932 -0
- idfpy/models/condensers.py +2734 -0
- idfpy/models/constructions.py +4644 -0
- idfpy/models/curves.py +1147 -0
- idfpy/models/daylighting.py +528 -0
- idfpy/models/demand_limiting.py +507 -0
- idfpy/models/economics.py +1417 -0
- idfpy/models/electric_load.py +3673 -0
- idfpy/models/ems.py +386 -0
- idfpy/models/evap_coolers.py +562 -0
- idfpy/models/external_interface.py +341 -0
- idfpy/models/fans.py +905 -0
- idfpy/models/faults.py +1165 -0
- idfpy/models/fluids.py +1528 -0
- idfpy/models/hvac_design.py +1120 -0
- idfpy/models/hvac_templates.py +9841 -0
- idfpy/models/internal_gains.py +3454 -0
- idfpy/models/location.py +1666 -0
- idfpy/models/misc.py +8223 -0
- idfpy/models/node_branch.py +905 -0
- idfpy/models/outputs.py +1733 -0
- idfpy/models/plant_control.py +3620 -0
- idfpy/models/plant_equipment.py +5276 -0
- idfpy/models/pumps.py +750 -0
- idfpy/models/python_plugins.py +236 -0
- idfpy/models/refrigeration.py +2644 -0
- idfpy/models/room_air.py +1315 -0
- idfpy/models/schedules.py +594 -0
- idfpy/models/setpoint_managers.py +1318 -0
- idfpy/models/simulation.py +672 -0
- idfpy/models/solar.py +711 -0
- idfpy/models/thermal_zones.py +3445 -0
- idfpy/models/unitary.py +2571 -0
- idfpy/models/user_defined.py +749 -0
- idfpy/models/water_heaters.py +2061 -0
- idfpy/models/water_systems.py +548 -0
- idfpy/models/zone_airflow.py +2030 -0
- idfpy/models/zone_controls.py +1117 -0
- idfpy/models/zone_equipment.py +580 -0
- idfpy/models/zone_forced_air.py +4335 -0
- idfpy/models/zone_radiative.py +2005 -0
- idfpy/models/zone_terminals.py +2099 -0
- idfpy/py.typed +0 -0
- idfpy/sim/__init__.py +173 -0
- idfpy/sim/_runner.py +189 -0
- idfpy/sim/config.py +17 -0
- idfpy/sim/result.py +81 -0
- idfpy-25.1.0.dist-info/METADATA +261 -0
- idfpy-25.1.0.dist-info/RECORD +78 -0
- idfpy-25.1.0.dist-info/WHEEL +4 -0
- idfpy-25.1.0.dist-info/entry_points.txt +2 -0
- idfpy-25.1.0.dist-info/licenses/LICENSE +21 -0
idfpy/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""idfpy - EnergyPlus IDF models and file handling.
|
|
2
|
+
|
|
3
|
+
Type-safe Pydantic models for all EnergyPlus IDF object types,
|
|
4
|
+
plus IDF file read/write functionality.
|
|
5
|
+
|
|
6
|
+
Generated from Energy+.schema.epJSON version 26.1.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
|
|
11
|
+
from idfpy.idf import IDF
|
|
12
|
+
from idfpy.models._base import IDFBaseModel
|
|
13
|
+
from idfpy.models._errors import RefError, RefValidationError, UnknownObjectTypeError
|
|
14
|
+
|
|
15
|
+
__version__ = version('idfpy')
|
|
16
|
+
__all__ = [
|
|
17
|
+
'IDF',
|
|
18
|
+
'IDFBaseModel',
|
|
19
|
+
'RefError',
|
|
20
|
+
'RefValidationError',
|
|
21
|
+
'UnknownObjectTypeError',
|
|
22
|
+
'__version__',
|
|
23
|
+
]
|
idfpy/__main__.py
ADDED
idfpy/cli.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""idfpy command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
from typer import Option, Typer
|
|
9
|
+
|
|
10
|
+
app = Typer(name='idfpy', help='EnergyPlus IDF toolkit')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def codegen(
|
|
15
|
+
schema: Annotated[
|
|
16
|
+
Path, Option('--schema', '-s', help='Path to Energy+.schema.epJSON')
|
|
17
|
+
],
|
|
18
|
+
output: Annotated[
|
|
19
|
+
Path, Option('--output', '-o', help='Output directory for generated models')
|
|
20
|
+
] = Path('idfpy/models'),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Generate Pydantic models from EnergyPlus schema."""
|
|
23
|
+
from idfpy.codegen import ModelGenerator, SchemaParser
|
|
24
|
+
|
|
25
|
+
parser = SchemaParser(schema_path=schema)
|
|
26
|
+
specs = parser.parse()
|
|
27
|
+
schema_version = parser.get_version()
|
|
28
|
+
generator = ModelGenerator(output_dir=output)
|
|
29
|
+
generator.generate_all(specs, schema_version=schema_version)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command()
|
|
33
|
+
def run(
|
|
34
|
+
idf: Annotated[Path, Option('--idf', '-i', help='Path to IDF file')],
|
|
35
|
+
weather: Annotated[
|
|
36
|
+
Path, Option('--weather', '-w', help='Path to EPW weather file')
|
|
37
|
+
],
|
|
38
|
+
output: Annotated[
|
|
39
|
+
Path | None, Option('--output', '-o', help='Output directory')
|
|
40
|
+
] = None,
|
|
41
|
+
readvars: Annotated[
|
|
42
|
+
bool, Option('--readvars', '-r', help='Run ReadVarsESO to convert ESO to CSV')
|
|
43
|
+
] = False,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Run EnergyPlus simulation."""
|
|
46
|
+
from idfpy.sim import simulate
|
|
47
|
+
|
|
48
|
+
result = simulate(idf, weather=weather, output_dir=output, readvars=readvars)
|
|
49
|
+
raise SystemExit(result.return_code)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""EnergyPlus JSON Schema parser for code generation.
|
|
2
|
+
|
|
3
|
+
This module parses Energy+.schema.epJSON to extract field and object
|
|
4
|
+
specifications for generating type-safe Pydantic models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
_UNSET = object()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class FieldSpec:
|
|
18
|
+
"""EnergyPlus field specification.
|
|
19
|
+
|
|
20
|
+
Represents a single field definition extracted from the schema,
|
|
21
|
+
including type information, constraints, and metadata.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name: Original field name from schema (e.g., "direction_of_relative_north").
|
|
25
|
+
python_name: Python-compatible name in snake_case.
|
|
26
|
+
field_type: JSON schema type ("number", "string", "integer", "array").
|
|
27
|
+
default: Default value if specified.
|
|
28
|
+
required: Whether the field is required.
|
|
29
|
+
enum_values: List of allowed values for enum fields.
|
|
30
|
+
units: Physical units (e.g., "m", "deg", "W").
|
|
31
|
+
minimum: Minimum allowed value (inclusive).
|
|
32
|
+
maximum: Maximum allowed value (inclusive).
|
|
33
|
+
exclusive_minimum: Exclusive minimum value.
|
|
34
|
+
exclusive_maximum: Exclusive maximum value.
|
|
35
|
+
object_list: Reference to other object types (for object_list fields).
|
|
36
|
+
items_spec: Nested FieldSpec for array item types.
|
|
37
|
+
note: Field description/note from schema.
|
|
38
|
+
data_type: Additional data type hint (e.g., "object_list").
|
|
39
|
+
anyof_specs: List of alternative type specs for anyOf fields.
|
|
40
|
+
nested_fields: List of nested field specs for object fields.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
python_name: str
|
|
45
|
+
field_type: str
|
|
46
|
+
default: Any = _UNSET
|
|
47
|
+
required: bool = False
|
|
48
|
+
enum_values: list[str] | None = None
|
|
49
|
+
units: str | None = None
|
|
50
|
+
minimum: float | None = None
|
|
51
|
+
maximum: float | None = None
|
|
52
|
+
exclusive_minimum: float | None = None
|
|
53
|
+
exclusive_maximum: float | None = None
|
|
54
|
+
object_list: list[str] | None = None
|
|
55
|
+
reference: list[str] | None = None
|
|
56
|
+
reference_class_name: list[str] | None = None
|
|
57
|
+
items_spec: FieldSpec | None = None
|
|
58
|
+
item_class_name: str | None = None
|
|
59
|
+
note: str | None = None
|
|
60
|
+
data_type: str | None = None
|
|
61
|
+
anyof_specs: list[FieldSpec] | None = None
|
|
62
|
+
nested_fields: list[FieldSpec] | None = None
|
|
63
|
+
is_name: bool = False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class FieldParser:
|
|
67
|
+
"""Parser for EnergyPlus schema field definitions.
|
|
68
|
+
|
|
69
|
+
Extracts FieldSpec instances from JSON schema field definitions,
|
|
70
|
+
handling various field types including nested objects and arrays.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
_CAMEL_TO_SNAKE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])')
|
|
74
|
+
|
|
75
|
+
def parse_field(self, name: str, field_schema: dict[str, Any]) -> FieldSpec:
|
|
76
|
+
"""Parse a single field definition from schema.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: Original field name.
|
|
80
|
+
field_schema: Field definition dictionary from schema.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Parsed FieldSpec instance.
|
|
84
|
+
"""
|
|
85
|
+
python_name = self._to_python_name(name)
|
|
86
|
+
|
|
87
|
+
if 'anyOf' in field_schema:
|
|
88
|
+
return self._parse_anyof_field(name, python_name, field_schema)
|
|
89
|
+
|
|
90
|
+
field_type = field_schema.get('type', 'string')
|
|
91
|
+
|
|
92
|
+
spec = FieldSpec(
|
|
93
|
+
name=name,
|
|
94
|
+
python_name=python_name,
|
|
95
|
+
field_type=field_type,
|
|
96
|
+
default=field_schema.get('default', _UNSET),
|
|
97
|
+
enum_values=field_schema.get('enum'),
|
|
98
|
+
units=field_schema.get('units'),
|
|
99
|
+
minimum=field_schema.get('minimum'),
|
|
100
|
+
maximum=field_schema.get('maximum'),
|
|
101
|
+
exclusive_minimum=field_schema.get('exclusiveMinimum'),
|
|
102
|
+
exclusive_maximum=field_schema.get('exclusiveMaximum'),
|
|
103
|
+
object_list=field_schema.get('object_list'),
|
|
104
|
+
reference=field_schema.get('reference'),
|
|
105
|
+
reference_class_name=field_schema.get('reference-class-name'),
|
|
106
|
+
note=field_schema.get('note'),
|
|
107
|
+
data_type=field_schema.get('data_type'),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Normalize: string field with numeric default → convert to str
|
|
111
|
+
# (e.g., Version.version_identifier has type="string" but default=25.1)
|
|
112
|
+
if spec.field_type == 'string' and type(spec.default) in (int, float):
|
|
113
|
+
spec.default = str(spec.default)
|
|
114
|
+
|
|
115
|
+
if field_type == 'array' and 'items' in field_schema:
|
|
116
|
+
spec.items_spec = self._parse_array_items(field_schema['items'])
|
|
117
|
+
|
|
118
|
+
return spec
|
|
119
|
+
|
|
120
|
+
def _parse_anyof_field(
|
|
121
|
+
self, name: str, python_name: str, field_schema: dict[str, Any]
|
|
122
|
+
) -> FieldSpec:
|
|
123
|
+
"""Parse a field with anyOf type alternatives.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
name: Original field name.
|
|
127
|
+
python_name: Python-compatible field name.
|
|
128
|
+
field_schema: Field definition with anyOf.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
FieldSpec with anyof_specs populated.
|
|
132
|
+
"""
|
|
133
|
+
anyof_specs = []
|
|
134
|
+
primary_type = 'string' # Default fallback
|
|
135
|
+
|
|
136
|
+
for alt_schema in field_schema.get('anyOf', []):
|
|
137
|
+
alt_type = alt_schema.get('type', 'string')
|
|
138
|
+
|
|
139
|
+
if alt_type != 'null' and primary_type == 'string':
|
|
140
|
+
primary_type = alt_type
|
|
141
|
+
|
|
142
|
+
alt_spec = FieldSpec(
|
|
143
|
+
name=name,
|
|
144
|
+
python_name=python_name,
|
|
145
|
+
field_type=alt_type,
|
|
146
|
+
enum_values=alt_schema.get('enum'),
|
|
147
|
+
minimum=alt_schema.get('minimum'),
|
|
148
|
+
maximum=alt_schema.get('maximum'),
|
|
149
|
+
)
|
|
150
|
+
anyof_specs.append(alt_spec)
|
|
151
|
+
|
|
152
|
+
default = field_schema.get('default', _UNSET)
|
|
153
|
+
# Normalize: string field with numeric default → convert to str
|
|
154
|
+
if primary_type == 'string' and type(default) in (int, float):
|
|
155
|
+
default = str(default)
|
|
156
|
+
|
|
157
|
+
return FieldSpec(
|
|
158
|
+
name=name,
|
|
159
|
+
python_name=python_name,
|
|
160
|
+
field_type=primary_type,
|
|
161
|
+
default=default,
|
|
162
|
+
units=field_schema.get('units'),
|
|
163
|
+
reference=field_schema.get('reference'),
|
|
164
|
+
note=field_schema.get('note'),
|
|
165
|
+
anyof_specs=anyof_specs,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def _parse_array_items(self, items_schema: dict[str, Any]) -> FieldSpec:
|
|
169
|
+
"""Parse array items specification.
|
|
170
|
+
|
|
171
|
+
Handles nested object definitions within array items,
|
|
172
|
+
such as vertices with x/y/z coordinates.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
items_schema: Array items definition from schema.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
FieldSpec representing the array item type.
|
|
179
|
+
"""
|
|
180
|
+
item_type = items_schema.get('type', 'object')
|
|
181
|
+
|
|
182
|
+
if item_type == 'object' and 'properties' in items_schema:
|
|
183
|
+
nested_fields = []
|
|
184
|
+
required_set = set(items_schema.get('required', []))
|
|
185
|
+
for prop_name, prop_schema in items_schema['properties'].items():
|
|
186
|
+
nested_spec = self.parse_field(prop_name, prop_schema)
|
|
187
|
+
nested_spec.required = prop_name in required_set
|
|
188
|
+
nested_fields.append(nested_spec)
|
|
189
|
+
|
|
190
|
+
return FieldSpec(
|
|
191
|
+
name='_item',
|
|
192
|
+
python_name='_item',
|
|
193
|
+
field_type='object',
|
|
194
|
+
nested_fields=nested_fields,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return FieldSpec(
|
|
198
|
+
name='_item',
|
|
199
|
+
python_name='_item',
|
|
200
|
+
field_type=item_type,
|
|
201
|
+
enum_values=items_schema.get('enum'),
|
|
202
|
+
minimum=items_schema.get('minimum'),
|
|
203
|
+
maximum=items_schema.get('maximum'),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def _to_python_name(self, name: str) -> str:
|
|
207
|
+
"""Convert field name to Python snake_case.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
name: Original field name (may contain spaces, hyphens, etc.).
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Python-compatible snake_case name.
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
>>> parser = FieldParser()
|
|
217
|
+
>>> parser._to_python_name('direction_of_relative_north')
|
|
218
|
+
'direction_of_relative_north'
|
|
219
|
+
>>> parser._to_python_name('X Origin')
|
|
220
|
+
'x_origin'
|
|
221
|
+
>>> parser._to_python_name('vertex-x-coordinate')
|
|
222
|
+
'vertex_x_coordinate'
|
|
223
|
+
>>> parser._to_python_name('100% Outdoor Air in Cooling')
|
|
224
|
+
'n100_outdoor_air_in_cooling'
|
|
225
|
+
"""
|
|
226
|
+
result = name.replace(' ', '_').replace('-', '_')
|
|
227
|
+
|
|
228
|
+
result = self._CAMEL_TO_SNAKE_PATTERN.sub('_', result)
|
|
229
|
+
|
|
230
|
+
result = result.lower()
|
|
231
|
+
|
|
232
|
+
result = re.sub(r'_+', '_', result)
|
|
233
|
+
|
|
234
|
+
result = result.strip('_')
|
|
235
|
+
|
|
236
|
+
# Python identifiers cannot start with a digit - prefix with 'n'
|
|
237
|
+
if result and result[0].isdigit():
|
|
238
|
+
result = 'n' + result
|
|
239
|
+
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
def parse_fields_from_properties(
|
|
243
|
+
self,
|
|
244
|
+
properties: dict[str, Any],
|
|
245
|
+
required_fields: list[str] | None = None,
|
|
246
|
+
) -> list[FieldSpec]:
|
|
247
|
+
"""Parse all fields from a properties dictionary.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
properties: Dictionary of field name to field schema.
|
|
251
|
+
required_fields: List of required field names.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
List of parsed FieldSpec instances.
|
|
255
|
+
"""
|
|
256
|
+
required_set = set(required_fields or [])
|
|
257
|
+
fields = []
|
|
258
|
+
|
|
259
|
+
for name, schema in properties.items():
|
|
260
|
+
spec = self.parse_field(name, schema)
|
|
261
|
+
spec.required = name in required_set
|
|
262
|
+
fields.append(spec)
|
|
263
|
+
|
|
264
|
+
return fields
|