idfpy 25.2.2__tar.gz → 25.2.4__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.
- idfpy-25.2.4/.github/workflows/docs.yml +88 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/sync-energyplus.yml +41 -35
- idfpy-25.2.4/.gitignore +18 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/PKG-INFO +8 -7
- {idfpy-25.2.2 → idfpy-25.2.4}/README.md +7 -6
- idfpy-25.2.4/docs/api/ext.md +28 -0
- idfpy-25.2.4/docs/api/idf.md +6 -0
- idfpy-25.2.4/docs/api/models.md +26 -0
- idfpy-25.2.4/docs/api/sim.md +15 -0
- idfpy-25.2.4/docs/getting-started.md +69 -0
- idfpy-25.2.4/docs/guides/cli.md +33 -0
- idfpy-25.2.4/docs/guides/extensions.md +63 -0
- idfpy-25.2.4/docs/guides/files.md +85 -0
- idfpy-25.2.4/docs/guides/navigation.md +55 -0
- idfpy-25.2.4/docs/guides/simulation.md +65 -0
- idfpy-25.2.4/docs/guides/validation.md +50 -0
- idfpy-25.2.4/docs/index.md +93 -0
- idfpy-25.2.4/docs/stylesheets/extra.css +75 -0
- idfpy-25.2.4/idfpy/ext/construction/__init__.py +7 -0
- idfpy-25.2.4/idfpy/ext/construction/functions.py +78 -0
- idfpy-25.2.4/idfpy/ext/construction/mixins.py +38 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_base.py +23 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/constructions.py +3 -1
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/_runner.py +2 -2
- {idfpy-25.2.2 → idfpy-25.2.4}/pyproject.toml +5 -1
- idfpy-25.2.4/scripts/docs/build_site.py +222 -0
- idfpy-25.2.4/scripts/docs/gen_reference.py +333 -0
- idfpy-25.2.4/tests/test_ext_construction.py +136 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_ref_system.py +35 -1
- idfpy-25.2.4/zensical.toml +92 -0
- idfpy-25.2.2/.gitignore +0 -11
- {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/publish-pypi.yml +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/release-version.yml +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/.vscode/launch.json +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/LICENSE +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/benchmarks/bench_idf.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/__main__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/cli.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/field_parser.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/model_generator.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/schema_parser.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/template_filters.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/idf_model_py.jinja2 +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/init_py.jinja2 +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/init_pyi.jinja2 +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/ref_meta_py.jinja2 +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/refs_py.jinja2 +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/functions.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/mixins.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/idf.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/__init__.pyi +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_errors.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_metadata.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_ref_meta.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_refs.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/advanced_construction.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/air_distribution.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/availability_managers.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/coils.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/condensers.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/curves.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/daylighting.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/demand_limiting.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/economics.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/electric_load.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/ems.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/evap_coolers.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/external_interface.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/fans.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/faults.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/fluids.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/hvac_design.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/hvac_templates.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/internal_gains.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/location.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/misc.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/node_branch.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/outputs.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/plant_control.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/plant_equipment.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/pumps.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/python_plugins.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/refrigeration.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/room_air.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/schedules.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/setpoint_managers.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/simulation.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/solar.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/thermal_zones.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/unitary.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/user_defined.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/water_heaters.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/water_systems.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_airflow.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_controls.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_equipment.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_forced_air.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_radiative.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_terminals.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/py.typed +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/config.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/result.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/__init__.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test.idf +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_api_unification.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_epjson_doe.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_ext.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_merge_dict.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_metadata.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_models.py +0 -0
- {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_typing_check.py +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
# Builds one documentation copy per release/ep-* branch and deploys the whole
|
|
4
|
+
# site to GitHub Pages. Guides and configuration always come from master.
|
|
5
|
+
# Both release workflows push with GITHUB_TOKEN, which does not trigger other
|
|
6
|
+
# workflows, so new releases are picked up through workflow_run. The weekly
|
|
7
|
+
# Sync EnergyPlus run also rebuilds when it finds nothing new; that costs a
|
|
8
|
+
# minute or two and keeps the trigger free of extra permissions.
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [master]
|
|
12
|
+
paths:
|
|
13
|
+
- "docs/**"
|
|
14
|
+
- "scripts/docs/**"
|
|
15
|
+
- "zensical.toml"
|
|
16
|
+
- ".github/workflows/docs.yml"
|
|
17
|
+
workflow_run:
|
|
18
|
+
workflows: ["Release EnergyPlus Version", "Sync EnergyPlus"]
|
|
19
|
+
types: [completed]
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
|
|
27
|
+
concurrency:
|
|
28
|
+
group: pages
|
|
29
|
+
cancel-in-progress: false
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
build:
|
|
33
|
+
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v7
|
|
37
|
+
with:
|
|
38
|
+
ref: master
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
|
|
41
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
42
|
+
|
|
43
|
+
- name: List EnergyPlus versions
|
|
44
|
+
id: versions
|
|
45
|
+
run: |
|
|
46
|
+
list=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/release/ep-*' \
|
|
47
|
+
| sed 's#^origin/release/ep-##' | sort -V | tr '\n' ' ')
|
|
48
|
+
echo "list=${list% }" >> "$GITHUB_OUTPUT"
|
|
49
|
+
|
|
50
|
+
- uses: actions/cache@v6
|
|
51
|
+
with:
|
|
52
|
+
path: schemas
|
|
53
|
+
key: ep-schemas-${{ steps.versions.outputs.list }}
|
|
54
|
+
restore-keys: ep-schemas-
|
|
55
|
+
|
|
56
|
+
# Only Energy+.schema.epJSON is extracted from each EnergyPlus release
|
|
57
|
+
# archive; the cache avoids repeating the ~200 MB downloads.
|
|
58
|
+
- name: Download EnergyPlus schemas
|
|
59
|
+
env:
|
|
60
|
+
GH_TOKEN: ${{ github.token }}
|
|
61
|
+
run: |
|
|
62
|
+
for version in ${{ steps.versions.outputs.list }}; do
|
|
63
|
+
dest="schemas/$version"
|
|
64
|
+
[ -f "$dest/Energy+.schema.epJSON" ] && continue
|
|
65
|
+
url=$(gh api "repos/NREL/EnergyPlus/releases/tags/v$version" --jq \
|
|
66
|
+
'[.assets[] | select(.name | test("Linux.*x86_64\\.tar\\.gz$"))][0].browser_download_url')
|
|
67
|
+
mkdir -p "$dest"
|
|
68
|
+
curl -fsSL "$url" | tar -xz --wildcards --strip-components=1 -C "$dest" '*/Energy+.schema.epJSON'
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
- run: uv sync --group docs
|
|
72
|
+
|
|
73
|
+
- name: Build site
|
|
74
|
+
run: uv run python scripts/docs/build_site.py --schema-pattern 'schemas/{version}/Energy+.schema.epJSON'
|
|
75
|
+
|
|
76
|
+
- uses: actions/upload-pages-artifact@v5
|
|
77
|
+
with:
|
|
78
|
+
path: _site
|
|
79
|
+
|
|
80
|
+
deploy:
|
|
81
|
+
needs: build
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment:
|
|
84
|
+
name: github-pages
|
|
85
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
86
|
+
steps:
|
|
87
|
+
- id: deployment
|
|
88
|
+
uses: actions/deploy-pages@v5
|
|
@@ -46,37 +46,40 @@ jobs:
|
|
|
46
46
|
} >> "$GITHUB_OUTPUT"
|
|
47
47
|
echo "EnergyPlus $VERSION -> $ASSET_URL"
|
|
48
48
|
|
|
49
|
-
- name: Skip if
|
|
49
|
+
- name: Skip if EnergyPlus line already released
|
|
50
50
|
id: check
|
|
51
51
|
run: |
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
TAG_EXISTS=false
|
|
56
|
-
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null 2>&1; then
|
|
57
|
-
TAG_EXISTS=true
|
|
58
|
-
fi
|
|
52
|
+
EP_VERSION="${{ steps.ep.outputs.version }}"
|
|
53
|
+
MAJOR_MINOR="${EP_VERSION%.*}"
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
fi
|
|
55
|
+
# idfpy versions are <EP major>.<EP minor>.<idfpy patch>; any release
|
|
56
|
+
# on this line means the EnergyPlus version is already covered.
|
|
57
|
+
LINE_TAGS=$(git tag -l "v${MAJOR_MINOR}.*")
|
|
58
|
+
LINE_PYPI=$(curl -fsS "https://pypi.org/pypi/idfpy/json" \
|
|
59
|
+
| jq -r --arg p "${MAJOR_MINOR}." '.releases | keys[] | select(startswith($p))')
|
|
66
60
|
|
|
67
61
|
SKIP=false
|
|
68
|
-
if [ "$
|
|
69
|
-
echo "
|
|
62
|
+
if [ -n "$LINE_TAGS" ] && [ -n "$LINE_PYPI" ]; then
|
|
63
|
+
echo "idfpy ${MAJOR_MINOR}.* already released for EnergyPlus ${EP_VERSION}; skipping"
|
|
70
64
|
SKIP=true
|
|
71
|
-
elif [ "$
|
|
72
|
-
echo "::error::
|
|
65
|
+
elif [ -n "$LINE_TAGS" ]; then
|
|
66
|
+
echo "::error::Tags v${MAJOR_MINOR}.* exist but PyPI has no ${MAJOR_MINOR}.* release; refusing to proceed"
|
|
73
67
|
exit 1
|
|
74
|
-
elif [ "$
|
|
75
|
-
echo "::error::PyPI ${
|
|
68
|
+
elif [ -n "$LINE_PYPI" ]; then
|
|
69
|
+
echo "::error::PyPI has ${MAJOR_MINOR}.* releases but tags v${MAJOR_MINOR}.* are missing; refusing to proceed"
|
|
76
70
|
exit 1
|
|
77
71
|
fi
|
|
78
72
|
|
|
79
|
-
|
|
73
|
+
# A new line starts at the current idfpy patch shared by all lines.
|
|
74
|
+
PATCH=$(git tag -l 'v*' \
|
|
75
|
+
| sed -nE 's/^v[0-9]+\.[0-9]+\.([0-9]+)$/\1/p' | sort -n | tail -1)
|
|
76
|
+
RELEASE_VERSION="${MAJOR_MINOR}.${PATCH:-0}"
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
echo "skip=$SKIP"
|
|
80
|
+
echo "release_version=$RELEASE_VERSION"
|
|
81
|
+
} >> "$GITHUB_OUTPUT"
|
|
82
|
+
echo "EnergyPlus ${EP_VERSION} -> idfpy ${RELEASE_VERSION} (skip=${SKIP})"
|
|
80
83
|
|
|
81
84
|
- name: Download schema
|
|
82
85
|
if: steps.check.outputs.skip == 'false'
|
|
@@ -107,6 +110,7 @@ jobs:
|
|
|
107
110
|
if: steps.check.outputs.skip == 'false'
|
|
108
111
|
run: |
|
|
109
112
|
EP_VERSION="${{ steps.ep.outputs.version }}"
|
|
113
|
+
RELEASE_VERSION="${{ steps.check.outputs.release_version }}"
|
|
110
114
|
MAJOR_MINOR="${EP_VERSION%.*}"
|
|
111
115
|
|
|
112
116
|
OBJ_COUNT=$(grep -rh '_idf_object_type: ClassVar' idfpy/models/*.py \
|
|
@@ -115,7 +119,7 @@ jobs:
|
|
|
115
119
|
PROP_COUNT=$(grep -rh '^ @property$' idfpy/models/*.py \
|
|
116
120
|
| grep -cv '_base.py')
|
|
117
121
|
|
|
118
|
-
sed -i "s/^version = .*/version = \"${
|
|
122
|
+
sed -i "s/^version = .*/version = \"${RELEASE_VERSION}\"/" pyproject.toml
|
|
119
123
|
|
|
120
124
|
# README badge: URL slug + alt text
|
|
121
125
|
sed -Ei "s|EnergyPlus-[0-9.]+-orange|EnergyPlus-${MAJOR_MINOR}-orange|" README.md
|
|
@@ -133,11 +137,15 @@ jobs:
|
|
|
133
137
|
sed -Ei "s|✅ [0-9]+ ref groups|✅ ${REF_COUNT} ref groups|" README.md
|
|
134
138
|
sed -Ei "s|✅ [0-9]+ properties|✅ ${PROP_COUNT} properties|" README.md
|
|
135
139
|
|
|
136
|
-
|
|
140
|
+
# Same branch layout as release-version.yml: one release/ep-X.Y.Z branch
|
|
141
|
+
# per EnergyPlus version, which the Docs workflow builds into the site.
|
|
142
|
+
- name: Publish to release branch and tag
|
|
137
143
|
id: push
|
|
138
144
|
if: steps.check.outputs.skip == 'false'
|
|
139
145
|
run: |
|
|
140
146
|
EP_VERSION="${{ steps.ep.outputs.version }}"
|
|
147
|
+
TAG="v${{ steps.check.outputs.release_version }}"
|
|
148
|
+
BRANCH="release/ep-${EP_VERSION}"
|
|
141
149
|
|
|
142
150
|
git config user.name "github-actions[bot]"
|
|
143
151
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
@@ -145,13 +153,11 @@ jobs:
|
|
|
145
153
|
# Capture codegen + metadata changes before switching branch
|
|
146
154
|
git stash push --include-untracked -m "sync-${EP_VERSION}"
|
|
147
155
|
|
|
148
|
-
#
|
|
149
|
-
if git ls-remote --exit-code --heads origin
|
|
150
|
-
git fetch origin
|
|
151
|
-
git switch -C latest origin/${{ github.ref_name }}
|
|
152
|
-
else
|
|
153
|
-
git switch -C latest
|
|
156
|
+
# Rebuild the EP support branch on top of the triggering branch
|
|
157
|
+
if git ls-remote --exit-code --heads origin "$BRANCH" > /dev/null; then
|
|
158
|
+
git fetch origin "$BRANCH"
|
|
154
159
|
fi
|
|
160
|
+
git switch -C "$BRANCH" origin/${{ github.ref_name }}
|
|
155
161
|
|
|
156
162
|
git stash pop
|
|
157
163
|
|
|
@@ -162,11 +168,11 @@ jobs:
|
|
|
162
168
|
exit 0
|
|
163
169
|
fi
|
|
164
170
|
|
|
165
|
-
git commit -m "feat(models): sync to EnergyPlus ${EP_VERSION}"
|
|
166
|
-
git tag -a "
|
|
171
|
+
git commit -m "feat(models): sync ${TAG} to EnergyPlus ${EP_VERSION}"
|
|
172
|
+
git tag -a "$TAG" -m "idfpy ${TAG} (EnergyPlus ${EP_VERSION})"
|
|
167
173
|
|
|
168
|
-
git push origin
|
|
169
|
-
git push origin "
|
|
174
|
+
git push origin "$BRANCH" --force-with-lease
|
|
175
|
+
git push origin "$TAG"
|
|
170
176
|
|
|
171
177
|
echo "updated=true" >> "$GITHUB_OUTPUT"
|
|
172
178
|
|
|
@@ -187,7 +193,7 @@ jobs:
|
|
|
187
193
|
BRANCH="chore/sync-readme"
|
|
188
194
|
TITLE="docs: sync README for EnergyPlus ${EP_VERSION}"
|
|
189
195
|
|
|
190
|
-
# README on
|
|
196
|
+
# README on the release branch (HEAD) already has the sed-updated content
|
|
191
197
|
cp README.md /tmp/README.new
|
|
192
198
|
|
|
193
199
|
git fetch origin master
|
|
@@ -207,7 +213,7 @@ jobs:
|
|
|
207
213
|
Auto-generated by the \`Sync EnergyPlus\` workflow.
|
|
208
214
|
|
|
209
215
|
Updates README badges, version line, and counts to match EnergyPlus ${EP_VERSION}.
|
|
210
|
-
Only \`README.md\` is touched — models and codegen products stay on the \`
|
|
216
|
+
Only \`README.md\` is touched — models and codegen products stay on the \`release/ep-${EP_VERSION}\` branch.
|
|
211
217
|
EOF
|
|
212
218
|
)
|
|
213
219
|
|
idfpy-25.2.4/.gitignore
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: idfpy
|
|
3
|
-
Version: 25.2.
|
|
3
|
+
Version: 25.2.4
|
|
4
4
|
Summary: Type-safe Pydantic models for all EnergyPlus IDF objects
|
|
5
5
|
Project-URL: Repository, https://github.com/ITOTI-Y/idfpy
|
|
6
6
|
Project-URL: Issues, https://github.com/ITOTI-Y/idfpy/issues
|
|
@@ -26,18 +26,19 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
[](https://pypi.org/project/idfpy/)
|
|
27
27
|
[](https://pypi.org/project/idfpy/)
|
|
28
28
|
[](LICENSE)
|
|
29
|
-
[](https://energyplus.net/)
|
|
30
30
|
[](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml)
|
|
31
|
+
[](https://itoti-y.github.io/idfpy/)
|
|
31
32
|
[](https://deepwiki.com/itoti-y/idfpy)
|
|
32
33
|
|
|
33
34
|
Type-safe [Pydantic](https://docs.pydantic.dev/) models for **all** [EnergyPlus](https://energyplus.net/) IDF object types, plus IDF file read/write and simulation execution, optimized for LLM tool calling and IDE auto-completion.
|
|
34
35
|
|
|
35
|
-
Auto-generated from `Energy+.schema.epJSON` version **26.
|
|
36
|
+
Auto-generated from `Energy+.schema.epJSON` version **26.2.0**.
|
|
36
37
|
|
|
37
38
|
## Features
|
|
38
39
|
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
40
|
+
- **881 object types** as Pydantic v2 models with full validation
|
|
41
|
+
- **287 reference types** with cross-object validation
|
|
41
42
|
- **Forward navigation** — `surface.zone` resolves one reference field; `surface.referenced()` returns every referenced object
|
|
42
43
|
- **Reverse navigation** — `zone.referencing("Lights")` finds all objects that reference a given object
|
|
43
44
|
- **Reference validation** — `idf.validate()` batch-checks all cross-object references for existence and type compatibility
|
|
@@ -57,8 +58,8 @@ Auto-generated from `Energy+.schema.epJSON` version **26.1.0**.
|
|
|
57
58
|
| No EnergyPlus IDD required at runtime | ✅ | ❌ |
|
|
58
59
|
| Type-safe field validation | ✅ Pydantic v2 | ❌ |
|
|
59
60
|
| epJSON read/write | ✅ | ❌ |
|
|
60
|
-
| Cross-reference validation | ✅
|
|
61
|
-
| Forward/reverse navigation | ✅
|
|
61
|
+
| Cross-reference validation | ✅ 287 ref groups | ❌ |
|
|
62
|
+
| Forward/reverse navigation | ✅ 2921 properties | ❌ |
|
|
62
63
|
| Surface geometry (area/normal) | ✅ ext plugin | ❌ |
|
|
63
64
|
| `to_dict()` / `from_dict()` for LLM | ✅ | ❌ |
|
|
64
65
|
| Dependencies | 4 (pydantic, jinja2, loguru, typer) | 12+ (lxml, pyparsing...) |
|
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
[](https://pypi.org/project/idfpy/)
|
|
4
4
|
[](https://pypi.org/project/idfpy/)
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
[](https://energyplus.net/)
|
|
7
7
|
[](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml)
|
|
8
|
+
[](https://itoti-y.github.io/idfpy/)
|
|
8
9
|
[](https://deepwiki.com/itoti-y/idfpy)
|
|
9
10
|
|
|
10
11
|
Type-safe [Pydantic](https://docs.pydantic.dev/) models for **all** [EnergyPlus](https://energyplus.net/) IDF object types, plus IDF file read/write and simulation execution, optimized for LLM tool calling and IDE auto-completion.
|
|
11
12
|
|
|
12
|
-
Auto-generated from `Energy+.schema.epJSON` version **26.
|
|
13
|
+
Auto-generated from `Energy+.schema.epJSON` version **26.2.0**.
|
|
13
14
|
|
|
14
15
|
## Features
|
|
15
16
|
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
17
|
+
- **881 object types** as Pydantic v2 models with full validation
|
|
18
|
+
- **287 reference types** with cross-object validation
|
|
18
19
|
- **Forward navigation** — `surface.zone` resolves one reference field; `surface.referenced()` returns every referenced object
|
|
19
20
|
- **Reverse navigation** — `zone.referencing("Lights")` finds all objects that reference a given object
|
|
20
21
|
- **Reference validation** — `idf.validate()` batch-checks all cross-object references for existence and type compatibility
|
|
@@ -34,8 +35,8 @@ Auto-generated from `Energy+.schema.epJSON` version **26.1.0**.
|
|
|
34
35
|
| No EnergyPlus IDD required at runtime | ✅ | ❌ |
|
|
35
36
|
| Type-safe field validation | ✅ Pydantic v2 | ❌ |
|
|
36
37
|
| epJSON read/write | ✅ | ❌ |
|
|
37
|
-
| Cross-reference validation | ✅
|
|
38
|
-
| Forward/reverse navigation | ✅
|
|
38
|
+
| Cross-reference validation | ✅ 287 ref groups | ❌ |
|
|
39
|
+
| Forward/reverse navigation | ✅ 2921 properties | ❌ |
|
|
39
40
|
| Surface geometry (area/normal) | ✅ ext plugin | ❌ |
|
|
40
41
|
| `to_dict()` / `from_dict()` for LLM | ✅ | ❌ |
|
|
41
42
|
| Dependencies | 4 (pydantic, jinja2, loguru, typer) | 12+ (lxml, pyparsing...) |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Extensions
|
|
2
|
+
|
|
3
|
+
Mixins injected into generated models. See [Extensions](../guides/extensions.md).
|
|
4
|
+
|
|
5
|
+
## Geometry
|
|
6
|
+
|
|
7
|
+
::: idfpy.ext.geometry.mixins._BaseGeometryMixin
|
|
8
|
+
options:
|
|
9
|
+
heading: Surface geometry
|
|
10
|
+
heading_level: 3
|
|
11
|
+
show_bases: false
|
|
12
|
+
|
|
13
|
+
::: idfpy.ext.geometry.functions
|
|
14
|
+
options:
|
|
15
|
+
heading: Geometry functions
|
|
16
|
+
heading_level: 3
|
|
17
|
+
|
|
18
|
+
## Construction
|
|
19
|
+
|
|
20
|
+
::: idfpy.ext.construction.mixins.ConstructionLayersMixin
|
|
21
|
+
options:
|
|
22
|
+
heading: ConstructionLayersMixin
|
|
23
|
+
heading_level: 3
|
|
24
|
+
|
|
25
|
+
::: idfpy.ext.construction.functions
|
|
26
|
+
options:
|
|
27
|
+
heading: Construction functions
|
|
28
|
+
heading_level: 3
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Models and errors
|
|
2
|
+
|
|
3
|
+
All generated object models derive from `IDFBaseModel`. Their fields are
|
|
4
|
+
documented per object type in the [object reference](../reference/index.md).
|
|
5
|
+
|
|
6
|
+
::: idfpy.models._base.IDFBaseModel
|
|
7
|
+
options:
|
|
8
|
+
show_root_full_path: false
|
|
9
|
+
heading: IDFBaseModel
|
|
10
|
+
|
|
11
|
+
## Errors
|
|
12
|
+
|
|
13
|
+
::: idfpy.models._errors.UnknownObjectTypeError
|
|
14
|
+
options:
|
|
15
|
+
heading: UnknownObjectTypeError
|
|
16
|
+
heading_level: 3
|
|
17
|
+
|
|
18
|
+
::: idfpy.models._errors.RefError
|
|
19
|
+
options:
|
|
20
|
+
heading: RefError
|
|
21
|
+
heading_level: 3
|
|
22
|
+
|
|
23
|
+
::: idfpy.models._errors.RefValidationError
|
|
24
|
+
options:
|
|
25
|
+
heading: RefValidationError
|
|
26
|
+
heading_level: 3
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Simulation
|
|
2
|
+
|
|
3
|
+
See [Running simulations](../guides/simulation.md) for examples.
|
|
4
|
+
|
|
5
|
+
::: idfpy.sim
|
|
6
|
+
options:
|
|
7
|
+
show_root_heading: false
|
|
8
|
+
members:
|
|
9
|
+
- simulate
|
|
10
|
+
- simulate_batch
|
|
11
|
+
- async_simulate
|
|
12
|
+
- async_simulate_batch
|
|
13
|
+
- SimJob
|
|
14
|
+
- SimResult
|
|
15
|
+
- ErrSummary
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
idfpy needs Python 3.12 or newer. EnergyPlus itself is only required for
|
|
6
|
+
running simulations; reading, editing and validating models works without it.
|
|
7
|
+
|
|
8
|
+
=== "pip"
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install "idfpy==26.2.*"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
=== "uv"
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv add "idfpy==26.2.*"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Pick the idfpy series that matches the EnergyPlus version of your models.
|
|
21
|
+
The installed version reports the EnergyPlus schema it was generated from:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from idfpy import IDF
|
|
25
|
+
|
|
26
|
+
IDF().version # '26.2'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Load, edit, save
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
from idfpy import IDF
|
|
35
|
+
from idfpy.models import Zone
|
|
36
|
+
|
|
37
|
+
idf = IDF.load(Path('existing.idf')) # format detected from the extension
|
|
38
|
+
|
|
39
|
+
zone = idf.get(Zone, 'Zone1') # typed as Zone | None
|
|
40
|
+
zone.multiplier = 2 # validated on assignment
|
|
41
|
+
|
|
42
|
+
idf.save(Path('modified.idf'))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Query methods accept the model class, its Python class name
|
|
46
|
+
(`'BuildingSurfaceDetailed'`) or the EnergyPlus type name
|
|
47
|
+
(`'BuildingSurface:Detailed'`). Passing the class keeps precise types in your
|
|
48
|
+
editor.
|
|
49
|
+
|
|
50
|
+
## Run a simulation
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
from idfpy.sim import simulate
|
|
56
|
+
|
|
57
|
+
result = simulate(
|
|
58
|
+
Path('modified.idf'),
|
|
59
|
+
weather=Path('USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw'),
|
|
60
|
+
output_dir=Path('results'),
|
|
61
|
+
)
|
|
62
|
+
print(result.success, result.err.severe_count if result.err else None)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Where next
|
|
66
|
+
|
|
67
|
+
- [Files and dictionaries](guides/files.md) covers every way in and out of an `IDF`.
|
|
68
|
+
- [Navigating references](guides/navigation.md) shows how objects point at each other.
|
|
69
|
+
- The [object reference](reference/index.md) lists every field of every object type.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Command line
|
|
2
|
+
|
|
3
|
+
Installing idfpy provides the `idfpy` command.
|
|
4
|
+
|
|
5
|
+
## `idfpy run`
|
|
6
|
+
|
|
7
|
+
Runs one EnergyPlus simulation and exits with the EnergyPlus return code.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
idfpy run --idf model.idf --weather weather.epw --output results --readvars
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Option | Short | Description |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `--idf` | `-i` | IDF file to simulate |
|
|
16
|
+
| `--weather` | `-w` | EPW weather file |
|
|
17
|
+
| `--output` | `-o` | Output directory, defaults to the IDF file's directory |
|
|
18
|
+
| `--readvars` | `-r` | Convert ESO output to CSV with ReadVarsESO |
|
|
19
|
+
| `--output-prefix` | `-p` | Prefix of the output file names |
|
|
20
|
+
|
|
21
|
+
## `idfpy codegen`
|
|
22
|
+
|
|
23
|
+
Regenerates the models from an EnergyPlus schema. You only need this when
|
|
24
|
+
developing idfpy or adding an [extension](extensions.md#writing-a-plugin).
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
idfpy codegen --schema /path/to/Energy+.schema.epJSON --output idfpy/models
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Option | Short | Description |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `--schema` | `-s` | Path to `Energy+.schema.epJSON` |
|
|
33
|
+
| `--output` | `-o` | Output directory, defaults to `idfpy/models` |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Extensions
|
|
2
|
+
|
|
3
|
+
Extensions add computed properties to generated models. They are mixed into
|
|
4
|
+
the model classes at code generation time, so editors auto-complete them like
|
|
5
|
+
ordinary fields.
|
|
6
|
+
|
|
7
|
+
## Geometry
|
|
8
|
+
|
|
9
|
+
Surface objects gain geometry computed from their vertices with Newell's
|
|
10
|
+
method:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
surface = idf.get('BuildingSurface:Detailed', 'Wall1')
|
|
14
|
+
|
|
15
|
+
surface.area # 30.0, in m²
|
|
16
|
+
surface.normal # (0.0, -1.0, 0.0), outward unit normal
|
|
17
|
+
surface.centroid # (5.0, 0.0, 1.5)
|
|
18
|
+
surface.tilt # 90.0, degrees from horizontal
|
|
19
|
+
surface.azimuth # 180.0, degrees clockwise from north
|
|
20
|
+
surface.perimeter
|
|
21
|
+
surface.bounding_box
|
|
22
|
+
surface.is_convex
|
|
23
|
+
surface.vertices_as_tuples # [(0, 0, 3), (0, 0, 0), (10, 0, 0), (10, 0, 3)]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Supported types: `BuildingSurface:Detailed`, `FenestrationSurface:Detailed`,
|
|
27
|
+
`Floor:Detailed`, `RoofCeiling:Detailed`, `Wall:Detailed`,
|
|
28
|
+
`Shading:Building:Detailed`, `Shading:Site:Detailed` and
|
|
29
|
+
`Shading:Zone:Detailed`.
|
|
30
|
+
|
|
31
|
+
## Constructions
|
|
32
|
+
|
|
33
|
+
`Construction` gains `layers`, `is_window_construction` and
|
|
34
|
+
`is_opaque_construction`; see [Navigating references](navigation.md#constructions-and-layers).
|
|
35
|
+
|
|
36
|
+
## Writing a plugin
|
|
37
|
+
|
|
38
|
+
An extension is a sub-package of `idfpy.ext` that exposes `MIXIN_MAP`,
|
|
39
|
+
mapping generated class names to mixin classes:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# idfpy/ext/thermal/__init__.py
|
|
43
|
+
from .mixins import ThermalPropertyMixin
|
|
44
|
+
|
|
45
|
+
MIXIN_MAP: dict[str, type] = {
|
|
46
|
+
'BuildingSurfaceDetailed': ThermalPropertyMixin,
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# idfpy/ext/thermal/mixins.py
|
|
52
|
+
class ThermalPropertyMixin:
|
|
53
|
+
@property
|
|
54
|
+
def u_value(self) -> float:
|
|
55
|
+
"""U-value of the surface construction, in W/(m²·K)."""
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Regenerate the models so the code generator injects the mixin:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
idfpy codegen --schema Energy+.schema.epJSON --output idfpy/models
|
|
63
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Files and dictionaries
|
|
2
|
+
|
|
3
|
+
An `IDF` is a container of model objects keyed by object type and name. It
|
|
4
|
+
reads and writes both the classic IDF text format and epJSON.
|
|
5
|
+
|
|
6
|
+
## Loading and saving
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from idfpy import IDF
|
|
12
|
+
|
|
13
|
+
idf = IDF.load(Path('model.idf')) # IDF text
|
|
14
|
+
idf = IDF.load(Path('model.epjson')) # epJSON, chosen by extension
|
|
15
|
+
|
|
16
|
+
idf.save(Path('out.idf')) # default: IDF text
|
|
17
|
+
idf.save(Path('out.epjson'), output_type='epjson')
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Adding, querying and removing objects
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from idfpy.models import Lights, Zone
|
|
24
|
+
|
|
25
|
+
idf.add(Zone(name='Office')) # ValueError if 'Office' already exists
|
|
26
|
+
|
|
27
|
+
idf.get(Zone, 'Office') # Zone | None
|
|
28
|
+
idf.has('Zone', 'Office') # True
|
|
29
|
+
idf.all_of_type(Lights) # dict[str, Lights], keyed by name
|
|
30
|
+
list(idf.types()) # object types present in the model
|
|
31
|
+
|
|
32
|
+
idf.remove(Zone, 'Office') # also unregisters its references
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Objects without a name field, such as `Version` or `Timestep`, receive an
|
|
36
|
+
internal key automatically when added.
|
|
37
|
+
|
|
38
|
+
Type names are checked: a misspelt type raises `UnknownObjectTypeError`
|
|
39
|
+
instead of silently returning nothing. Pass `strict=False` to get `None` or an
|
|
40
|
+
empty result instead.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
idf.get('BuildingSurface:detailed', 'Wall1')
|
|
44
|
+
# UnknownObjectTypeError: Unknown object type: 'BuildingSurface:detailed'. ...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Dictionaries for LLM tool calls
|
|
48
|
+
|
|
49
|
+
`to_dict()` returns the epJSON structure, object type → object name →
|
|
50
|
+
fields, which maps directly onto JSON tool arguments. Only fields that were
|
|
51
|
+
set explicitly are included, which keeps the payload small.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
data = idf.to_dict()
|
|
55
|
+
# {'Building': {'MyBuilding': {'north_axis': 0.0, 'terrain': 'Suburbs'}},
|
|
56
|
+
# 'Zone': {'Zone1': {'multiplier': 2}}, ...}
|
|
57
|
+
|
|
58
|
+
idf = IDF.from_dict(data)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`merge_dict()` applies a partial dictionary to an existing model. The merge is
|
|
62
|
+
atomic: every object is validated before anything changes, so a rejected tool
|
|
63
|
+
call leaves the model untouched.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
idf.merge_dict(
|
|
67
|
+
{'Zone': {'Zone2': {'multiplier': 3}}},
|
|
68
|
+
on_conflict='replace', # 'raise' (default), 'replace' or 'skip'
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Field names may use either the Python `snake_case` names or the original
|
|
73
|
+
schema keys.
|
|
74
|
+
|
|
75
|
+
## Logging
|
|
76
|
+
|
|
77
|
+
idfpy logs through Loguru. Messages at `INFO` and above are visible by
|
|
78
|
+
default; per-object progress uses the `TRACE` level and needs an explicit
|
|
79
|
+
sink:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from loguru import logger
|
|
83
|
+
|
|
84
|
+
logger.add('idfpy.log', level='TRACE')
|
|
85
|
+
```
|