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.
Files changed (117) hide show
  1. idfpy-25.2.4/.github/workflows/docs.yml +88 -0
  2. {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/sync-energyplus.yml +41 -35
  3. idfpy-25.2.4/.gitignore +18 -0
  4. {idfpy-25.2.2 → idfpy-25.2.4}/PKG-INFO +8 -7
  5. {idfpy-25.2.2 → idfpy-25.2.4}/README.md +7 -6
  6. idfpy-25.2.4/docs/api/ext.md +28 -0
  7. idfpy-25.2.4/docs/api/idf.md +6 -0
  8. idfpy-25.2.4/docs/api/models.md +26 -0
  9. idfpy-25.2.4/docs/api/sim.md +15 -0
  10. idfpy-25.2.4/docs/getting-started.md +69 -0
  11. idfpy-25.2.4/docs/guides/cli.md +33 -0
  12. idfpy-25.2.4/docs/guides/extensions.md +63 -0
  13. idfpy-25.2.4/docs/guides/files.md +85 -0
  14. idfpy-25.2.4/docs/guides/navigation.md +55 -0
  15. idfpy-25.2.4/docs/guides/simulation.md +65 -0
  16. idfpy-25.2.4/docs/guides/validation.md +50 -0
  17. idfpy-25.2.4/docs/index.md +93 -0
  18. idfpy-25.2.4/docs/stylesheets/extra.css +75 -0
  19. idfpy-25.2.4/idfpy/ext/construction/__init__.py +7 -0
  20. idfpy-25.2.4/idfpy/ext/construction/functions.py +78 -0
  21. idfpy-25.2.4/idfpy/ext/construction/mixins.py +38 -0
  22. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_base.py +23 -0
  23. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/constructions.py +3 -1
  24. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/_runner.py +2 -2
  25. {idfpy-25.2.2 → idfpy-25.2.4}/pyproject.toml +5 -1
  26. idfpy-25.2.4/scripts/docs/build_site.py +222 -0
  27. idfpy-25.2.4/scripts/docs/gen_reference.py +333 -0
  28. idfpy-25.2.4/tests/test_ext_construction.py +136 -0
  29. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_ref_system.py +35 -1
  30. idfpy-25.2.4/zensical.toml +92 -0
  31. idfpy-25.2.2/.gitignore +0 -11
  32. {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/publish-pypi.yml +0 -0
  33. {idfpy-25.2.2 → idfpy-25.2.4}/.github/workflows/release-version.yml +0 -0
  34. {idfpy-25.2.2 → idfpy-25.2.4}/.vscode/launch.json +0 -0
  35. {idfpy-25.2.2 → idfpy-25.2.4}/LICENSE +0 -0
  36. {idfpy-25.2.2 → idfpy-25.2.4}/benchmarks/bench_idf.py +0 -0
  37. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/__init__.py +0 -0
  38. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/__main__.py +0 -0
  39. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/cli.py +0 -0
  40. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/__init__.py +0 -0
  41. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/field_parser.py +0 -0
  42. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/model_generator.py +0 -0
  43. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/schema_parser.py +0 -0
  44. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/template_filters.py +0 -0
  45. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/idf_model_py.jinja2 +0 -0
  46. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/init_py.jinja2 +0 -0
  47. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/init_pyi.jinja2 +0 -0
  48. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/ref_meta_py.jinja2 +0 -0
  49. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/codegen/templates/refs_py.jinja2 +0 -0
  50. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/__init__.py +0 -0
  51. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/__init__.py +0 -0
  52. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/functions.py +0 -0
  53. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/ext/geometry/mixins.py +0 -0
  54. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/idf.py +0 -0
  55. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/__init__.py +0 -0
  56. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/__init__.pyi +0 -0
  57. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_errors.py +0 -0
  58. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_metadata.py +0 -0
  59. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_ref_meta.py +0 -0
  60. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/_refs.py +0 -0
  61. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/advanced_construction.py +0 -0
  62. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/air_distribution.py +0 -0
  63. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/availability_managers.py +0 -0
  64. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/coils.py +0 -0
  65. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/condensers.py +0 -0
  66. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/curves.py +0 -0
  67. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/daylighting.py +0 -0
  68. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/demand_limiting.py +0 -0
  69. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/economics.py +0 -0
  70. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/electric_load.py +0 -0
  71. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/ems.py +0 -0
  72. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/evap_coolers.py +0 -0
  73. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/external_interface.py +0 -0
  74. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/fans.py +0 -0
  75. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/faults.py +0 -0
  76. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/fluids.py +0 -0
  77. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/hvac_design.py +0 -0
  78. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/hvac_templates.py +0 -0
  79. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/internal_gains.py +0 -0
  80. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/location.py +0 -0
  81. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/misc.py +0 -0
  82. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/node_branch.py +0 -0
  83. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/outputs.py +0 -0
  84. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/plant_control.py +0 -0
  85. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/plant_equipment.py +0 -0
  86. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/pumps.py +0 -0
  87. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/python_plugins.py +0 -0
  88. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/refrigeration.py +0 -0
  89. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/room_air.py +0 -0
  90. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/schedules.py +0 -0
  91. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/setpoint_managers.py +0 -0
  92. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/simulation.py +0 -0
  93. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/solar.py +0 -0
  94. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/thermal_zones.py +0 -0
  95. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/unitary.py +0 -0
  96. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/user_defined.py +0 -0
  97. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/water_heaters.py +0 -0
  98. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/water_systems.py +0 -0
  99. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_airflow.py +0 -0
  100. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_controls.py +0 -0
  101. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_equipment.py +0 -0
  102. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_forced_air.py +0 -0
  103. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_radiative.py +0 -0
  104. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/models/zone_terminals.py +0 -0
  105. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/py.typed +0 -0
  106. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/__init__.py +0 -0
  107. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/config.py +0 -0
  108. {idfpy-25.2.2 → idfpy-25.2.4}/idfpy/sim/result.py +0 -0
  109. {idfpy-25.2.2 → idfpy-25.2.4}/tests/__init__.py +0 -0
  110. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test.idf +0 -0
  111. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_api_unification.py +0 -0
  112. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_epjson_doe.py +0 -0
  113. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_ext.py +0 -0
  114. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_merge_dict.py +0 -0
  115. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_metadata.py +0 -0
  116. {idfpy-25.2.2 → idfpy-25.2.4}/tests/test_models.py +0 -0
  117. {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 version already released
49
+ - name: Skip if EnergyPlus line already released
50
50
  id: check
51
51
  run: |
52
- VERSION="${{ steps.ep.outputs.version }}"
53
- TAG="v${VERSION}"
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
- PYPI_EXISTS=false
61
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
62
- "https://pypi.org/pypi/idfpy/${VERSION}/json")
63
- if [ "$HTTP_CODE" = "200" ]; then
64
- PYPI_EXISTS=true
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 [ "$TAG_EXISTS" = "true" ] && [ "$PYPI_EXISTS" = "true" ]; then
69
- echo "Tag $TAG and PyPI ${VERSION} both already exist; skipping"
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 [ "$TAG_EXISTS" = "true" ]; then
72
- echo "::error::Tag $TAG already exists but PyPI ${VERSION} is missing; refusing to proceed"
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 [ "$PYPI_EXISTS" = "true" ]; then
75
- echo "::error::PyPI ${VERSION} already exists but tag $TAG is missing; refusing to proceed"
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
- echo "skip=$SKIP" >> "$GITHUB_OUTPUT"
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 = \"${EP_VERSION}\"/" pyproject.toml
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
- - name: Publish to latest branch and tag
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
- # Rebase latest on top of the triggering branch, then re-apply the stash
149
- if git ls-remote --exit-code --heads origin latest > /dev/null; then
150
- git fetch origin latest
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 "v${EP_VERSION}" -m "EnergyPlus ${EP_VERSION} models"
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 latest --force-with-lease
169
- git push origin "v${EP_VERSION}"
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 latest (HEAD) already has the sed-updated content
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 \`latest\` branch.
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
 
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ *.lock
8
+
9
+ .claude/
10
+
11
+ _dev/
12
+
13
+ # Documentation build
14
+ _site/
15
+ site/
16
+ docs/reference/
17
+ .zensical-build.yml
18
+ .cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: idfpy
3
- Version: 25.2.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
  [![PyPI](https://img.shields.io/pypi/v/idfpy)](https://pypi.org/project/idfpy/)
27
27
  [![Python 3.12+](https://img.shields.io/pypi/pyversions/idfpy)](https://pypi.org/project/idfpy/)
28
28
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
29
- [![EnergyPlus 26.1](https://img.shields.io/badge/EnergyPlus-26.1-orange)](https://energyplus.net/)
29
+ [![EnergyPlus 26.2](https://img.shields.io/badge/EnergyPlus-26.2-orange)](https://energyplus.net/)
30
30
  [![Autoupdate](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml/badge.svg)](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml)
31
+ [![Docs](https://github.com/ITOTI-Y/idfpy/actions/workflows/docs.yml/badge.svg)](https://itoti-y.github.io/idfpy/)
31
32
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](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.1.0**.
36
+ Auto-generated from `Energy+.schema.epJSON` version **26.2.0**.
36
37
 
37
38
  ## Features
38
39
 
39
- - **859 object types** as Pydantic v2 models with full validation
40
- - **275 reference types** with cross-object validation
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 | ✅ 275 ref groups | ❌ |
61
- | Forward/reverse navigation | ✅ 2849 properties | ❌ |
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
  [![PyPI](https://img.shields.io/pypi/v/idfpy)](https://pypi.org/project/idfpy/)
4
4
  [![Python 3.12+](https://img.shields.io/pypi/pyversions/idfpy)](https://pypi.org/project/idfpy/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
- [![EnergyPlus 26.1](https://img.shields.io/badge/EnergyPlus-26.1-orange)](https://energyplus.net/)
6
+ [![EnergyPlus 26.2](https://img.shields.io/badge/EnergyPlus-26.2-orange)](https://energyplus.net/)
7
7
  [![Autoupdate](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml/badge.svg)](https://github.com/ITOTI-Y/idfpy/actions/workflows/sync-energyplus.yml)
8
+ [![Docs](https://github.com/ITOTI-Y/idfpy/actions/workflows/docs.yml/badge.svg)](https://itoti-y.github.io/idfpy/)
8
9
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](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.1.0**.
13
+ Auto-generated from `Energy+.schema.epJSON` version **26.2.0**.
13
14
 
14
15
  ## Features
15
16
 
16
- - **859 object types** as Pydantic v2 models with full validation
17
- - **275 reference types** with cross-object validation
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 | ✅ 275 ref groups | ❌ |
38
- | Forward/reverse navigation | ✅ 2849 properties | ❌ |
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,6 @@
1
+ # IDF
2
+
3
+ The container for a whole EnergyPlus model. See
4
+ [Files and dictionaries](../guides/files.md) for a guided tour.
5
+
6
+ ::: idfpy.idf.IDF
@@ -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
+ ```