idfkit 1.1.1__tar.gz → 1.2.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.
- {idfkit-1.1.1 → idfkit-1.2.0}/CHANGELOG.md +72 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/Makefile +1 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/PKG-INFO +13 -14
- {idfkit-1.1.1 → idfkit-1.2.0}/README.md +12 -13
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/document-and-objects.py +17 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/geometry-and-surfaces.py +34 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/geometry-builders-and-zoning.py +43 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/parsing-idf-epjson.py +16 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/simulation-execution.py +5 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/weather-data.py +17 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/SKILL.md +1 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/document-and-objects.md +11 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/geometry-and-surfaces.md +28 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/geometry-builders-and-zoning.md +32 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/parsing-idf-epjson.md +21 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/simulation-execution.md +5 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/weather-data.md +7 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/pyproject.toml +4 -4
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/check_naming_register.py +6 -5
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/SKILL.md +1 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/document-and-objects.md +23 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/geometry-and-surfaces.md +47 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/geometry-builders-and-zoning.md +60 -3
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/parsing-idf-epjson.md +27 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/simulation-execution.md +10 -3
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/weather-data.md +19 -1
- idfkit-1.2.0/src/idfkit/_accessors.py +279 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/_conformance.py +1 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/_generated_types.pyi +23853 -1113
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/generate_stubs.py +53 -23
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/document.py +67 -11
- idfkit-1.2.0/src/idfkit/document.pyi +3621 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/exceptions.py +30 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/geometry.py +572 -43
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/geometry_builders.py +114 -52
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/idf_parser.py +77 -17
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/objects.py +130 -8
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schema.py +30 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/__init__.py +2 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/_common.py +7 -2
- idfkit-1.2.0/src/idfkit/simulation/_external_files.py +183 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/async_batch.py +8 -17
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/async_runner.py +21 -7
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/batch.py +18 -15
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/cache.py +115 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/result.py +18 -7
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/runner.py +21 -14
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/surface_matching.py +3 -55
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/visualization/model.py +36 -13
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_cli.py +2 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_epw_sentinels.py +2 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/geocode.py +114 -20
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/index.py +90 -17
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/zoning.py +242 -33
- idfkit-1.2.0/tests/test_accessors.py +683 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_codegen.py +15 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_geometry.py +348 -9
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_geometry_builders.py +55 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_idf_roundtrip.py +126 -1
- idfkit-1.2.0/tests/test_object_update.py +275 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_parsers.py +156 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_async.py +61 -1
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_batch.py +52 -1
- idfkit-1.2.0/tests/test_simulation_external_files.py +289 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_runner.py +136 -2
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_surface_matching.py +40 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_visualization_model.py +133 -4
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_cli.py +19 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_geocode.py +92 -8
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_index.py +87 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_zoning.py +311 -7
- idfkit-1.2.0/tests/type_checks/update_fixture.py +49 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/uv.lock +1 -1
- idfkit-1.1.1/src/idfkit/document.pyi +0 -696
- {idfkit-1.1.1 → idfkit-1.2.0}/.devcontainer/devcontainer.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.devcontainer/postCreateCommand.sh +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/CODEOWNERS +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/actions/setup-python-env/action.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/ep-testfiles.sparse +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/adopt-energyplus.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/archive-retired-site.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/benchmark.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/conformance.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/deploy-py-redirects.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/main.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/notify-downstream.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/on-release-main.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/rehearse-all.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.github/workflows/validate-codecov-config.yml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.gitignore +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.pre-commit-config.yaml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.pre-commit-hooks.yaml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.python-version +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.vscode/extensions.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/.vscode/settings.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/AGENTS.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/CLAUDE.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/CONTRIBUTING.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/LICENSE +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/hvac-loops.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/hvac-templates.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/reference-tracking.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/result-parsing.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/schedule-evaluation.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/schema-and-validation.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/thermal-properties.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/version-migration.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/visualization.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/snippets/writing-output.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/hvac-loops.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/hvac-templates.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/reference-tracking.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/result-parsing.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/schedule-evaluation.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/schema-and-validation.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/thermal-properties.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/version-migration.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/visualization.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/agent_references/templates/writing-output.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/aggregate.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/bench.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/bench_eso.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/bench_progress.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/bench_typecheckers.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/eso_results.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/profile_field_access.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/profile_lookup.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/profile_overloads.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/profile_stubs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/benchmarks/progress_benchmark_results.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/codecov.yaml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/old-sitemaps/README.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/old-sitemaps/developers.idfkit.com.retired.txt +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/old-sitemaps/developers.idfkit.com.txt +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/old-sitemaps/js.idfkit.com.sitemap.xml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/old-sitemaps/py.idfkit.com.sitemap.xml +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/redirects/README.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/redirects/path-map.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/_governance_source.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/adopt_energyplus.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/build_redirect_site.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/build_weather_index.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/check_parity_ledger.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/check_redirects.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/check_release_conformance.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/check_release_version.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/scripts/sweep_example_files.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/hvac-loops.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/hvac-templates.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/reference-tracking.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/result-parsing.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/schedule-evaluation.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/schema-and-validation.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/thermal-properties.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/version-migration.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/visualization.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/.agents/skills/developing-with-idfkit/references/writing-output.md +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/_compat.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/_compat_object.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/bake_references.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/generate_conformance.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/generate_doc_locations.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/codegen/generate_epw_sentinels.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_checker.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_cli.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_diff.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_extract.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_models.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/compat/_sarif.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/cst.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/docs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/download.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/epjson_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/introspection.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/async_runner.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/async_subprocess_backend.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/chain.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/diff.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/progress.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/protocol.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/report.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/runner.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/migration/subprocess_backend.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/py.typed +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/references.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/scene.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/builder.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/compact.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/day.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/day_types.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/evaluate.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/file.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/holidays.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/series.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/time_utils.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/types.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/week.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schedules/year.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V22-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V22-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V23-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V23-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V24-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V24-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V25-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V25-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V26-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V26-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V8-9-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-0-1/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-1-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-2-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-3-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-4-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-5-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/V9-6-0/Energy+.schema.epJSON.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/schemas/doc_locations.json +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/config.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/expand.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/fs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/outputs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/_timeutil.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/csv.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/err.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/eso.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/html.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/rdd.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/parsers/sql.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/plotting/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/plotting/matplotlib.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/plotting/plotly.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/plotting/visualizations.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/progress.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/simulation/progress_bars.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/thermal/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/thermal/gas.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/thermal/properties.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/validation.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/versions.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/visualization/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/visualization/svg.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_browser/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_browser/assets/app.js +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_browser/assets/index.html +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_browser/assets/style.css +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/_browser/server.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/data/stations.json.gz +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/designday.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/download.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/epw.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/spatial.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/weather/station.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/src/idfkit/writers.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/conftest.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/generate_eso_fixtures.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.csv +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.err +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.eso +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.mdd +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.mtr +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample.rdd +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample_fatal.err +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/simulation/sample_truncated.eso +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/weather/empty.ddy +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/fixtures/weather/sample.ddy +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_compact.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_day.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_day_types.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_evaluate.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_file.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_holidays.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_series.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_time_utils.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_types.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_week.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/schedules/test_year.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_adopt_energyplus.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_agent_references.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_basic.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_check_redirects.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_check_release_version.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_cli_migrate.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_compat_cli.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_compat_diff.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_compat_extract.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_docs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_document.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_download.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_epjson_roundtrip.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_eppy_compat.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_exceptions.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_expand.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_extensible_canonical_api.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_extensible_eppy_compat.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_extensible_normalization.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_governance_source_matches.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_introspection.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_logging.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_async_runner.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_chain.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_config.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_diff.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_e2e.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_exceptions.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_report.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_runner.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_migration_subprocess_backend.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_objects.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_references.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_roundtrip.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_scene.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_schedule_builder.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_schema.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_cache.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_config.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_csv_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_e2e.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_err_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_eso_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_fs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_html_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_outputs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_plotting.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_prep_outputs.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_preprocessor_timeout.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_progress.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_progress_bars.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_rdd_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_result.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_sql_parser.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_simulation_version_mismatch.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_strict_typing.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_thermal.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_type_name_lookup.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_validation.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_versions.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_visualization.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_designday.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_download.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_offline.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_spatial.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_weather_station.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/test_writers.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/weather/__init__.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/weather/epw_fixtures.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/weather/test_epw.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/weather/test_epw_sentinels.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tests/weather/test_index_climate_zone.py +0 -0
- {idfkit-1.1.1 → idfkit-1.2.0}/tox.ini +0 -0
|
@@ -7,6 +7,75 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
This release moves to `conformance-2026.21`. Through `conformance-2026.19`, the level the TypeScript library also declares, the corpus adds five cases for the multi-field `update` shared with the TypeScript library, an `update` operation and a `post-operation-epjson` assertion (`conformance-2026.17`), and removes the divergence entries for those cases in both languages: the ten Python entries that `IDFObject.update` closes (`conformance-2026.18`) and the six TypeScript ones (`conformance-2026.19`). `conformance-2026.20` then removes the two Python round-trip entries that #183 closes. An idfkit release without `IDFObject.update` fails the five update cases at this level, and one that still stores a blank fixed field as `""` fails those two. `conformance-2026.21` removes the three Python entries that #187 closes, so a release that still drops an unterminated final object fails `malformed-truncated-object` and `preserve-unterminated-final`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Every object type in a document's schema became reachable as a typed attribute
|
|
17
|
+
on `IDFDocument`, not only the hand-written shorthands.** Each type has one
|
|
18
|
+
`snake_case` plural, declared in `document.pyi`: `doc.air_loop_hvacs` is
|
|
19
|
+
`IDFCollection[AirLoopHVAC]`, and `doc.coil_cooling_dx_single_speeds` and
|
|
20
|
+
`doc.zone_hvac_equipment_connections` resolve likewise. Names are derived from
|
|
21
|
+
the schema by rule, with a short override table for the types the rule splits
|
|
22
|
+
badly (`Output:SQLite` is `doc.output_sqlite`), and a test fails when a new
|
|
23
|
+
release adds an acronym that has not been reviewed. Existing shorthands kept
|
|
24
|
+
their meaning, and the spellings that resolved before, such as
|
|
25
|
+
`doc.Site_Location` and `doc.ZONE`, still resolve. A failed lookup names the
|
|
26
|
+
closest matches and their object types; the message is built only when read, so
|
|
27
|
+
`hasattr()` and `getattr(doc, name, default)` stay fast
|
|
28
|
+
([#202](https://github.com/idfkit/idfkit/pull/202)).
|
|
29
|
+
- `idfkit.geometry.polygon_difference_parts_2d(outer, *inners)` subtracts one or more polygons and returns hole-free, non-overlapping, counter-clockwise pieces, largest first, that together cover the difference. A result with a hole is cut by a vertical line through each hole. Every piece is a valid EnergyPlus surface outline or `create_block` footprint. ([#164](https://github.com/idfkit/idfkit/issues/164), [#244](https://github.com/idfkit/idfkit/pull/244))
|
|
30
|
+
- `idfkit.geometry.triangulate_2d(poly)` ear-clips a convex or concave 2D polygon into triangles, returned as index triples into the input in its own winding. It tolerates collinear and duplicate vertices, keyhole outlines with doubled bridge vertices and polygons pinched at a vertex, and it still uses every vertex of a self-intersecting polygon. The 3D viewers and `intersect_and_match` now share it. ([#246](https://github.com/idfkit/idfkit/pull/246))
|
|
31
|
+
- `idfkit.geometry_builders.split_horizontal_surface_by_regions(doc, surface, regions)` splits a horizontal surface under several regions in one step and returns `(region_surfaces, remaining_surfaces)`. `link_blocks` now uses it for each roof, so adjacent floors resting on one roof are subtracted together. ([#164](https://github.com/idfkit/idfkit/issues/164), [#244](https://github.com/idfkit/idfkit/pull/244))
|
|
32
|
+
- `idfkit.zoning.create_courtyard_block(doc, name, outer, courtyards, floor_to_floor, num_stories)` builds a block whose footprint has one or more courtyards cut out. The footprint is split into hole-free pieces with `polygon_difference_parts_2d`, each piece becomes one zone per story named `"<name> Part <k>"` (`"<name> Story <n> Part <k>"` for several stories), and the walls along the cuts between pieces are paired interior walls rather than exterior walls. ([#245](https://github.com/idfkit/idfkit/pull/245))
|
|
33
|
+
- `idfkit.zoning.footprint_courtyard_parts(outer_width, outer_depth, inner_width, inner_depth)` returns a centred rectangular courtyard footprint as two hole-free U-shaped halves. ([#245](https://github.com/idfkit/idfkit/pull/245))
|
|
34
|
+
- `IDFObject.update` sets several fields in one call, given as a mapping, keyword arguments, or both; where both name the same field, the keyword wins, as with `dict.update`. The call is all or nothing: every entry is examined before any is applied, and if one is refused, no field changes. An entry is refused when its name is not a field of the type, when it is `name` (use `IDFDocument.rename`), when it is a legacy flat extensible name such as `vertex_3_x_coordinate`, or when it gives the extensible wrapper key a value single assignment rejects: something other than a list, or a list holding an item that is not a dict or group, or a group with an unknown field. Unknown names are refused whenever the object has a schema field order, including in a document opened with `strict=False`, where single assignment stores them; an object constructed detached without one accepts any name, as single assignment does. Accepted entries are applied by ordinary attribute assignment, so reference and change tracking are those of consecutive assignments. The method returns `None`; the TypeScript `IdfObject.update` returns the object, a difference recorded in the naming register. The generated stubs type `update` per object type, so a type checker reports a misspelled field name or a wrongly typed value given as a keyword; the positional mapping is typed `Mapping[str, Any] | None`, as at run time, so that a mapping built at run time is accepted. ([#235](https://github.com/idfkit/idfkit/issues/235), [#236](https://github.com/idfkit/idfkit/pull/236))
|
|
35
|
+
- `InvalidFieldError` accepts an optional keyword-only `refused`, the entries of a refused `update` group, and then lists every one in its message and in its `refused` attribute, a tuple of `idfkit.exceptions.RefusedEntry`. Without it, every message is unchanged. ([#236](https://github.com/idfkit/idfkit/pull/236))
|
|
36
|
+
- **`GeocodingError` gained `kind` and `status`.** `kind` is `"not-found"`,
|
|
37
|
+
`"unreachable"`, `"http"` or `"invalid-response"`, and `status` holds the HTTP
|
|
38
|
+
code when `kind` is `"http"`, so a caller can tell a place name that matches
|
|
39
|
+
nothing from a service that could not be reached without matching message
|
|
40
|
+
text. `geocode` and `detect_location` set both on every error they raise. An
|
|
41
|
+
HTTP error from Nominatim or ipapi.co, previously reported only as "Failed to
|
|
42
|
+
geocode", now names its status. An ipapi.co payload that reports a rate limit
|
|
43
|
+
is `"http"` with `status` `429`, and a truncated response body is
|
|
44
|
+
`"unreachable"` rather than escaping as `http.client.IncompleteRead`.
|
|
45
|
+
Coordinates that are blank, boolean or not finite are `"invalid-response"`.
|
|
46
|
+
`@idfkit/weather` carries the same fields
|
|
47
|
+
([idfkit/idfkit-js#88](https://github.com/idfkit/idfkit-js/issues/88), [#249](https://github.com/idfkit/idfkit/pull/249)).
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- **Breaking:** `polygon_difference_2d` raises `ValueError` when the difference has a hole (the subtracted polygon lies strictly inside) or falls into several pieces, and names `polygon_difference_parts_2d` as the replacement. It used to return a single "bridge/slit" ring for these cases, which had the wrong area or coincident vertices. The subtracted polygon no longer has to lie inside the outer one, and either winding is accepted. ([#164](https://github.com/idfkit/idfkit/issues/164), [#244](https://github.com/idfkit/idfkit/pull/244))
|
|
52
|
+
- **Breaking:** A blank fixed field (any field outside a repeating group, in every object type) was stored in the model as `""`, numeric fields included, although `write_idf` and `write_epjson` both omitted it, so re-parsing idfkit's own IDF output did not equal the document it was written from: a `Zone` written with eight blank trailing commas parsed to 12 fields and came back with 4. Such a field is now left out of the model, as ConvertInputFormat and idfkit-js leave it out, and assigning `None` or `""` to a field now clears it, as it already cleared an extensible slot. A blank field now reads as `None`, as a field cut off by an earlier semicolon always has, in attribute, item and positional access and in the eppy-compatible `fieldvalues`; code that compared such a field with `""` should test for `None`. An explicit `0` is still a value, and `write_idf` still writes an interior blank in its position. ([#183](https://github.com/idfkit/idfkit/issues/183), [#242](https://github.com/idfkit/idfkit/pull/242))
|
|
53
|
+
- `idfkit tmy --near ADDRESS` now exits `1` (no matches) instead of `3`
|
|
54
|
+
(network failure) when the address resolves to no place. Exit `3` remains for
|
|
55
|
+
a geocoder that could not be reached, answered with an HTTP error, or returned
|
|
56
|
+
an unreadable body
|
|
57
|
+
([idfkit/idfkit-js#88](https://github.com/idfkit/idfkit-js/issues/88), [#249](https://github.com/idfkit/idfkit/pull/249)).
|
|
58
|
+
|
|
59
|
+
### Deprecated
|
|
60
|
+
|
|
61
|
+
- `footprint_courtyard` emits a `DeprecationWarning`. It returns a "slit" ring that repeats a vertex and runs out and back along a zero-width cut to the courtyard, which `create_block` turns into overlapping, opposite-facing exterior walls and a self-touching floor and roof that EnergyPlus rejects (the defect behind [#164](https://github.com/idfkit/idfkit/issues/164)). Its output is unchanged; use `create_courtyard_block` or `footprint_courtyard_parts` instead. ([#245](https://github.com/idfkit/idfkit/pull/245))
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- `StationIndex.search` missed queries in the form "City, state" and accented queries, and now finds them. Tokens are split on commas and semicolons as well as whitespace, so `"Golden, CO"` no longer keeps the comma in `"golden,"`, and on dots and hyphens as station names are, so `"St. Louis, MO"` and `"Winston-Salem, NC"` find their stations; a token equal to a station's state or country code qualifies the match rather than having to appear in its name, and counts as matched in a partial match, so `"Boulder, CO"` ranks Boulder, Colorado first; a token shorter than three characters must equal a whole word of the name, so `"co"` no longer prefix-matches Mbanza Congo and Cotonou; and query and station names are compared with diacritics folded, so `"Montréal"` finds Montreal and `"Tromsø"` finds Tromso. ([#248](https://github.com/idfkit/idfkit/issues/248), [#250](https://github.com/idfkit/idfkit/pull/250))
|
|
66
|
+
- `create_block`, `create_courtyard_block` and `ZonedBlock.build` with `air_boundary=True` now raise a `ValueError` naming the version requirement on a document older than EnergyPlus 9.2, before adding anything. `Construction:AirBoundary` first appeared in 9.2, and they previously added every zone and surface of the block and then failed with `UnknownObjectTypeError`, leaving the document holding a block with blank constructions on its interior walls. ([#247](https://github.com/idfkit/idfkit/pull/247))
|
|
67
|
+
- A batch job refused before EnergyPlus runs, such as one without `weather` that is not design-day-only, no longer leaves an empty temporary directory behind when it names no `output_dir`. `simulate_batch` and `async_simulate_batch` created one with `tempfile.mkdtemp()` for every such job and never removed it. The failed result's `run_dir` is now `idfkit.simulation.REFUSED_RUN_DIR`, a placeholder path under the null device that cannot be created, and its output accessors such as `errors` return empty results for it without reading the filesystem. ([#228](https://github.com/idfkit/idfkit/issues/228), [#239](https://github.com/idfkit/idfkit/pull/239))
|
|
68
|
+
- `SimulationCache` no longer returns a previous run's results after a file the model reads has changed. The key covered the model, the weather file and the run flags, so editing a `Schedule:File` CSV staged into `output_dir`, or one referenced by absolute path, returned the earlier run silently. The key now also hashes the contents of every file the model names for EnergyPlus to read: `Schedule:File`, `Schedule:File:Shading`, the `Table:Lookup`, `Table:IndependentVariable`, `Table:MultiVariableLookup` and `Table:TwoIndependentVariables` external files, `Construction:WindowDataFile` (including its default `Window5DataFile.dat`), the ASHRAE 205 representation files of `Chiller:Electric:ASHRAE205` and `Coil:DX:ASHRAE205:Performance`, and FMUs imported through `ExternalInterface:FunctionalMockupUnitImport`. The list is derived from the model's own schema, as the string fields marked `retaincase` whose names refer to a file or path, so file fields added by later EnergyPlus releases are covered too. A relative path is resolved as EnergyPlus resolves it, against `output_dir` and then the EnergyPlus executable's directory. Restoring a file's earlier content hits the earlier entry. A model with a `PythonPlugin:Instance`, or an `ExternalInterface` in `PtolemyServer` or `FunctionalMockupUnitExport` mode, can read inputs no key can cover, so `simulate`, `async_simulate` and the batch helpers now neither look it up nor store it, and log a warning. `SimulationCache.compute_key` takes a new `search_dirs` argument for resolving relative paths. A model that names no external file keeps the key it had, so existing cache entries stay valid. ([#241](https://github.com/idfkit/idfkit/pull/241))
|
|
69
|
+
- A simulation run into an `output_dir` that already held a weather file of the same name, left by an earlier run with a different weather file, now replaces that stale copy. EnergyPlus previously ran with the earlier weather file while `SimulationCache.compute_key` hashed the new one, so the cache stored results under the wrong key. A copy with identical content, or a weather file that already lives in `output_dir`, is left untouched. ([#227](https://github.com/idfkit/idfkit/issues/227), [#237](https://github.com/idfkit/idfkit/pull/237))
|
|
70
|
+
- `load_idf()` and `get_idf_version()` raised `VersionNotFoundError` on an IDF whose `Version` object carries more than three components, such as the `Version, 23.1.0.002;` that DesignBuilder writes. Components past the patch are now ignored, so that file resolves to 23.1.0, as it does in idfkit-js. The epJSON parser already read such strings this way. ([#238](https://github.com/idfkit/idfkit/pull/238))
|
|
71
|
+
- `polygon_difference_2d` returns a clean simple polygon. For an edge or corner cut-out the ring used to contain coincident consecutive vertices, a zero-width slit, so `create_block` built degenerate surfaces and EnergyPlus failed. Subtracting a polygon fully inside the outer one returned the wrong area: 4212.5 m² instead of 5300 m² for a 40 × 25 m island in a 105 × 60 m footprint. The difference is now computed from a planar overlay of both outlines with only the standard library. ([#164](https://github.com/idfkit/idfkit/issues/164), [#244](https://github.com/idfkit/idfkit/pull/244))
|
|
72
|
+
- The bundled `developing-with-idfkit` agent reference no longer suggests `footprint_courtyard`, or building each `polygon_difference_parts_2d` piece with its own `create_block` call, which left the walls along the cuts as exterior walls. It now points to `create_courtyard_block`. ([#245](https://github.com/idfkit/idfkit/pull/245))
|
|
73
|
+
- `polygon_difference_parts_2d` no longer leaves sliver pieces when two holes' middles nearly line up. Each hole got its own vertical cut, so courtyards whose middles were 1 cm apart came back as five pieces, three of them 1 cm wide, and `create_courtyard_block` made each one a zone. The largest hole is now cut through its middle and a hole an earlier cut already crosses gets no cut of its own, giving two pieces. ([#245](https://github.com/idfkit/idfkit/pull/245))
|
|
74
|
+
- `split_horizontal_surface` and `link_blocks` no longer leave a wrong-area, slit-shaped exposed roof when an upper block sits strictly inside the lower block's roof. The exposed frame is split into hole-free surfaces, and each extra piece is named `"<roof name> Remainder"` and copies the roof's construction, zone and boundary conditions. ([#164](https://github.com/idfkit/idfkit/issues/164), [#244](https://github.com/idfkit/idfkit/pull/244))
|
|
75
|
+
- `view_model()` and the other 3D viewers in `idfkit.visualization` drew concave surfaces, such as L- and U-shaped floors and roofs, with spurious triangles spanning their notches. Surfaces were fan-triangulated from their first vertex, which is only correct for convex polygons; concave ones are now ear-clipped on their own plane, keeping the surface's winding, and convex ones keep the fan. Collinear and duplicate vertices are skipped, keyhole polygons with doubled bridge vertices are clipped correctly, and a self-intersecting polygon is still drawn instead of raising. ([#240](https://github.com/idfkit/idfkit/pull/240), [#246](https://github.com/idfkit/idfkit/pull/246))
|
|
76
|
+
- `intersect_and_match` and `intersect_match` no longer give a concave surface drawn as a keyhole (a courtyard roof or ceiling with doubled bridge vertices), or one pinched at a vertex, the wrong area when they split it. Its triangulation clipped triangles that lay outside the surface. A 96 m² keyhole ceiling with one 4 m² floor above it was split into fragments totalling 168 m²; they now total 96 m². ([#246](https://github.com/idfkit/idfkit/pull/246))
|
|
77
|
+
- An IDF file whose last object has no closing `;`, such as `Version, 24.1;\nZone, Z1, 0`, no longer loads with that object silently dropped. A strict parse (the default) raises `IDFParseError` with an `Unterminated object near "..." (missing ";")` finding of code `ParseError`, positioned at the line the object starts on and naming its type. With `strict_parsing=False` or `load_idf_with_diagnostics` the rest of the file still loads, and the finding is logged as a warning and returned. Trailing whitespace and comments are not affected. The same change removes exponential backtracking in the object pattern and in the `preserve_formatting=True` syntax-tree pattern, which made an unterminated tail of thirty characters take more than one hundred seconds to read, and `iter_idf_objects` now skips such a tail with a warning instead of scanning it. ([#187](https://github.com/idfkit/idfkit/issues/187), [#243](https://github.com/idfkit/idfkit/pull/243))
|
|
78
|
+
|
|
10
79
|
## [1.1.1] - 2026-10-19
|
|
11
80
|
|
|
12
81
|
### Fixed
|
|
@@ -908,7 +977,9 @@ Initial public release.
|
|
|
908
977
|
- Performance benchmarks comparing idfkit against eppy and opyplus. ([#5](https://github.com/idfkit/idfkit/pull/5))
|
|
909
978
|
- MkDocs Material documentation site with a full API reference, an eppy migration guide, and a getting-started Jupyter notebook. ([#2](https://github.com/idfkit/idfkit/pull/2))
|
|
910
979
|
|
|
911
|
-
[Unreleased]: https://github.com/idfkit/idfkit/compare/v1.
|
|
980
|
+
[Unreleased]: https://github.com/idfkit/idfkit/compare/v1.2.0...HEAD
|
|
981
|
+
[1.2.0]: https://github.com/idfkit/idfkit/compare/v1.1.1...v1.2.0
|
|
982
|
+
[1.1.1]: https://github.com/idfkit/idfkit/compare/v1.1.0...v1.1.1
|
|
912
983
|
[1.1.0]: https://github.com/idfkit/idfkit/compare/v1.0.0...v1.1.0
|
|
913
984
|
[1.0.0]: https://github.com/idfkit/idfkit/compare/v1.0.0-rc.6...v1.0.0
|
|
914
985
|
[1.0.0-rc.6]: https://github.com/idfkit/idfkit/compare/v1.0.0-rc.5...v1.0.0-rc.6
|
|
@@ -89,7 +89,7 @@ check: check-stubs check-conformance-level check-epw-sentinels check-doc-locatio
|
|
|
89
89
|
@echo "🚀 Linting code: Running pre-commit"
|
|
90
90
|
@uv run pre-commit run -a
|
|
91
91
|
@echo "🚀 Static type checking: Running pyright"
|
|
92
|
-
@uv run pyright src/ agent_references/snippets
|
|
92
|
+
@uv run pyright src/ agent_references/snippets tests/type_checks
|
|
93
93
|
@echo "🚀 Checking for obsolete dependencies: Running deptry"
|
|
94
94
|
@uv run deptry src
|
|
95
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: idfkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A fast, modern EnergyPlus IDF/epJSON parser with O(1) lookups and reference tracking
|
|
5
5
|
Project-URL: Homepage, https://py.idfkit.com/
|
|
6
6
|
Project-URL: Repository, https://github.com/idfkit/idfkit
|
|
@@ -57,27 +57,26 @@ Description-Content-Type: text/markdown
|
|
|
57
57
|
|
|
58
58
|
**A fast, modern EnergyPlus IDF/epJSON toolkit for Python.**
|
|
59
59
|
|
|
60
|
-
> [!
|
|
61
|
-
> **
|
|
62
|
-
>
|
|
63
|
-
> `
|
|
60
|
+
> [!NOTE]
|
|
61
|
+
> **Upgrading from 0.x.** Since 1.0.0, writing a model to disk uses
|
|
62
|
+
> `save_idf(doc, path)` and `save_epjson(doc, path)`. `write_idf` and
|
|
63
|
+
> `write_epjson` only return a string.
|
|
64
64
|
>
|
|
65
|
-
>
|
|
65
|
+
> Code that still calls `write_*` with a path fails without naming the cause:
|
|
66
66
|
> `write_epjson(doc, path)` raises `TypeError: can't multiply sequence by
|
|
67
|
-
> non-int of type 'PosixPath'`, and `write_idf(doc, path)`
|
|
68
|
-
>
|
|
67
|
+
> non-int of type 'PosixPath'`, and `write_idf(doc, path)` writes nothing,
|
|
68
|
+
> leaving an empty file that surfaces later as
|
|
69
69
|
> `Could not detect EnergyPlus version in file`.
|
|
70
70
|
>
|
|
71
71
|
> Search for `write_idf(` and `write_epjson(` called with two positional
|
|
72
|
-
> arguments, or run a type checker, which rejects both. Full notes in the
|
|
72
|
+
> arguments, or run a type checker, which rejects both. Full notes are in the
|
|
73
73
|
> [changelog](CHANGELOG.md#migration).
|
|
74
74
|
|
|
75
75
|
> [!NOTE]
|
|
76
|
-
> idfkit
|
|
77
|
-
>
|
|
78
|
-
>
|
|
79
|
-
> [
|
|
80
|
-
> bug reports, or feature requests.
|
|
76
|
+
> idfkit follows [semantic versioning](https://semver.org/): breaking API
|
|
77
|
+
> changes occur only in major releases and are recorded in the
|
|
78
|
+
> [changelog](CHANGELOG.md). Feedback, bug reports, and feature requests are
|
|
79
|
+
> welcome as [GitHub issues](https://github.com/idfkit/idfkit/issues).
|
|
81
80
|
|
|
82
81
|
idfkit lets you load, create, query, and modify EnergyPlus models with an
|
|
83
82
|
intuitive Python API. It is designed as a drop-in replacement for
|
|
@@ -7,27 +7,26 @@
|
|
|
7
7
|
|
|
8
8
|
**A fast, modern EnergyPlus IDF/epJSON toolkit for Python.**
|
|
9
9
|
|
|
10
|
-
> [!
|
|
11
|
-
> **
|
|
12
|
-
>
|
|
13
|
-
> `
|
|
10
|
+
> [!NOTE]
|
|
11
|
+
> **Upgrading from 0.x.** Since 1.0.0, writing a model to disk uses
|
|
12
|
+
> `save_idf(doc, path)` and `save_epjson(doc, path)`. `write_idf` and
|
|
13
|
+
> `write_epjson` only return a string.
|
|
14
14
|
>
|
|
15
|
-
>
|
|
15
|
+
> Code that still calls `write_*` with a path fails without naming the cause:
|
|
16
16
|
> `write_epjson(doc, path)` raises `TypeError: can't multiply sequence by
|
|
17
|
-
> non-int of type 'PosixPath'`, and `write_idf(doc, path)`
|
|
18
|
-
>
|
|
17
|
+
> non-int of type 'PosixPath'`, and `write_idf(doc, path)` writes nothing,
|
|
18
|
+
> leaving an empty file that surfaces later as
|
|
19
19
|
> `Could not detect EnergyPlus version in file`.
|
|
20
20
|
>
|
|
21
21
|
> Search for `write_idf(` and `write_epjson(` called with two positional
|
|
22
|
-
> arguments, or run a type checker, which rejects both. Full notes in the
|
|
22
|
+
> arguments, or run a type checker, which rejects both. Full notes are in the
|
|
23
23
|
> [changelog](CHANGELOG.md#migration).
|
|
24
24
|
|
|
25
25
|
> [!NOTE]
|
|
26
|
-
> idfkit
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
> [
|
|
30
|
-
> bug reports, or feature requests.
|
|
26
|
+
> idfkit follows [semantic versioning](https://semver.org/): breaking API
|
|
27
|
+
> changes occur only in major releases and are recorded in the
|
|
28
|
+
> [changelog](CHANGELOG.md). Feedback, bug reports, and feature requests are
|
|
29
|
+
> welcome as [GitHub issues](https://github.com/idfkit/idfkit/issues).
|
|
31
30
|
|
|
32
31
|
idfkit lets you load, create, query, and modify EnergyPlus models with an
|
|
33
32
|
intuitive Python API. It is designed as a drop-in replacement for
|
|
@@ -63,6 +63,23 @@ for material in doc.materials:
|
|
|
63
63
|
# --8<-- [end:lookup]
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
# --8<-- [start:accessors]
|
|
67
|
+
# Any type in the schema is reachable as a typed attribute, not only common ones.
|
|
68
|
+
loops = doc.air_loop_hvacs # IDFCollection[AirLoopHVAC]
|
|
69
|
+
coils = doc.coil_cooling_dx_single_speeds # Coil:Cooling:DX:SingleSpeed
|
|
70
|
+
also_loops = doc["AirLoopHVAC"] # equivalent
|
|
71
|
+
shades = doc.shading_building_detaileds # Shading:Building:Detailed
|
|
72
|
+
|
|
73
|
+
# A typo names the closest matches and their object types:
|
|
74
|
+
# >>> doc.shading_bulding
|
|
75
|
+
# AttributeError: 'IDFDocument' object has no attribute 'shading_bulding'.
|
|
76
|
+
# Did you mean:
|
|
77
|
+
# shading_building (Shading:Building:Detailed)
|
|
78
|
+
# shading_buildings (Shading:Building)
|
|
79
|
+
# shading_fins (Shading:Fin)
|
|
80
|
+
# --8<-- [end:accessors]
|
|
81
|
+
|
|
82
|
+
|
|
66
83
|
# --8<-- [start:modify]
|
|
67
84
|
zone = doc["Zone"]["Office"]
|
|
68
85
|
zone.ceiling_height = 3.5 # plain field
|
|
@@ -148,10 +148,43 @@ inner = [(1, 1), (4, 1), (4, 4), (1, 4)]
|
|
|
148
148
|
polygon_area_2d(a) # 50.0
|
|
149
149
|
polygon_contains_2d(a, inner) # True (every vertex of inner is inside a)
|
|
150
150
|
polygon_intersection_2d(a, b) # overlapping rectangle
|
|
151
|
-
polygon_difference_2d(a, b) # a minus b
|
|
151
|
+
polygon_difference_2d(a, b) # a minus b: [(0, 0), (5, 0), (5, 5), (0, 5)]
|
|
152
152
|
# --8<-- [end:polygon-2d]
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
# --8<-- [start:polygon-difference-parts]
|
|
156
|
+
from idfkit import footprint_rectangle
|
|
157
|
+
from idfkit.geometry import polygon_difference_parts_2d
|
|
158
|
+
from idfkit.zoning import create_courtyard_block
|
|
159
|
+
|
|
160
|
+
site = footprint_rectangle(105, 60)
|
|
161
|
+
courtyard = footprint_rectangle(40, 25, origin=(35, 20)) # strictly inside: leaves a hole
|
|
162
|
+
office = footprint_rectangle(33.3, 9.0) # corner cut-out
|
|
163
|
+
|
|
164
|
+
# polygon_difference_2d(site, courtyard) raises ValueError: the result has a hole.
|
|
165
|
+
pieces = polygon_difference_parts_2d(site, courtyard, office)
|
|
166
|
+
|
|
167
|
+
# To build them, keep the pieces in one block so the cut walls become interior walls:
|
|
168
|
+
create_courtyard_block(doc, "Wing", site, [courtyard, office], floor_to_floor=3.5)
|
|
169
|
+
# --8<-- [end:polygon-difference-parts]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# --8<-- [start:triangulate-2d]
|
|
173
|
+
from idfkit.geometry import triangulate_2d
|
|
174
|
+
|
|
175
|
+
l_shape = [(10, 5), (5, 5), (5, 10), (0, 10), (0, 0), (10, 0)]
|
|
176
|
+
triangles = triangulate_2d(l_shape) # [(5, 0, 1), (1, 2, 3), (1, 3, 4), (1, 4, 5)]
|
|
177
|
+
total = sum(polygon_area_2d([l_shape[i] for i in tri]) for tri in triangles) # 75.0
|
|
178
|
+
# --8<-- [end:triangulate-2d]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# --8<-- [start:mistake-hole-good]
|
|
182
|
+
from idfkit.zoning import create_courtyard_block
|
|
183
|
+
|
|
184
|
+
create_courtyard_block(doc, "Block", site, [courtyard], floor_to_floor=3.5)
|
|
185
|
+
# --8<-- [end:mistake-hole-good]
|
|
186
|
+
|
|
187
|
+
|
|
155
188
|
# --8<-- [start:mistake-area-good]
|
|
156
189
|
calculate_surface_area(surface)
|
|
157
190
|
# --8<-- [end:mistake-area-good]
|
|
@@ -27,16 +27,34 @@ from idfkit import (
|
|
|
27
27
|
footprint_rectangle,
|
|
28
28
|
footprint_l_shape,
|
|
29
29
|
footprint_u_shape,
|
|
30
|
-
footprint_courtyard,
|
|
31
30
|
)
|
|
32
31
|
|
|
33
32
|
rect = footprint_rectangle(width=50, depth=30)
|
|
34
33
|
ell = footprint_l_shape(width=40, depth=20, wing_width=20, wing_depth=15)
|
|
35
34
|
u = footprint_u_shape(width=40, depth=30, courtyard_width=15, courtyard_depth=10)
|
|
36
|
-
courtyard = footprint_courtyard(outer_width=40, outer_depth=30, inner_width=15, inner_depth=10)
|
|
37
35
|
# --8<-- [end:footprints]
|
|
38
36
|
|
|
39
37
|
|
|
38
|
+
# --8<-- [start:courtyard]
|
|
39
|
+
from idfkit.zoning import create_courtyard_block, footprint_courtyard_parts
|
|
40
|
+
|
|
41
|
+
# One call: the footprint minus the courtyard(s), split into hole-free pieces,
|
|
42
|
+
# one zone per piece per story, cut walls between pieces paired as interior walls.
|
|
43
|
+
create_courtyard_block(
|
|
44
|
+
doc,
|
|
45
|
+
"Office",
|
|
46
|
+
footprint_rectangle(50, 40),
|
|
47
|
+
[footprint_rectangle(20, 10, origin=(15, 15))],
|
|
48
|
+
floor_to_floor=3.5,
|
|
49
|
+
num_stories=3,
|
|
50
|
+
)
|
|
51
|
+
# Zones: "Office Story 1 Part 1", "Office Story 1 Part 2", ...
|
|
52
|
+
|
|
53
|
+
# Just the pieces (two U-shaped halves), e.g. to inspect or zone them yourself
|
|
54
|
+
halves = footprint_courtyard_parts(outer_width=50, outer_depth=40, inner_width=20, inner_depth=10)
|
|
55
|
+
# --8<-- [end:courtyard]
|
|
56
|
+
|
|
57
|
+
|
|
40
58
|
# --8<-- [start:zoning-schemes]
|
|
41
59
|
from idfkit import ZoningScheme, ZoneFootprint
|
|
42
60
|
|
|
@@ -133,6 +151,22 @@ new_a, new_b = split_horizontal_surface(
|
|
|
133
151
|
# --8<-- [end:split-surface]
|
|
134
152
|
|
|
135
153
|
|
|
154
|
+
# --8<-- [start:split-surface-regions]
|
|
155
|
+
from idfkit.geometry_builders import split_horizontal_surface_by_regions
|
|
156
|
+
|
|
157
|
+
region_surfaces, remaining_surfaces = split_horizontal_surface_by_regions(
|
|
158
|
+
doc,
|
|
159
|
+
surface,
|
|
160
|
+
regions=[
|
|
161
|
+
[(10, 5), (15, 5), (15, 15), (10, 15)],
|
|
162
|
+
[(15, 5), (20, 5), (20, 15), (15, 15)],
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
# region_surfaces: one new surface per region, in order
|
|
166
|
+
# remaining_surfaces: the original surface first, then any extra remainder pieces
|
|
167
|
+
# --8<-- [end:split-surface-regions]
|
|
168
|
+
|
|
169
|
+
|
|
136
170
|
# --8<-- [start:mistake-ccw-good]
|
|
137
171
|
ccw_rect = footprint_rectangle(50, 30) # CCW by construction
|
|
138
172
|
# --8<-- [end:mistake-ccw-good]
|
|
@@ -145,6 +179,13 @@ for zone in doc["Zone"]:
|
|
|
145
179
|
# --8<-- [end:mistake-hvac-good]
|
|
146
180
|
|
|
147
181
|
|
|
182
|
+
# --8<-- [start:mistake-courtyard-good]
|
|
183
|
+
create_courtyard_block(
|
|
184
|
+
doc, "Office", footprint_rectangle(50, 40), [footprint_rectangle(20, 10, origin=(15, 15))], floor_to_floor=3.5
|
|
185
|
+
)
|
|
186
|
+
# --8<-- [end:mistake-courtyard-good]
|
|
187
|
+
|
|
188
|
+
|
|
148
189
|
# --8<-- [start:mistake-link-good]
|
|
149
190
|
link_blocks(doc)
|
|
150
191
|
# --8<-- [end:mistake-link-good]
|
|
@@ -43,6 +43,16 @@ except IDFParseError as e:
|
|
|
43
43
|
# --8<-- [end:strict-parsing]
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
# --8<-- [start:blank-fields]
|
|
47
|
+
# Zone, Office, 0, 0, 0, 0, , , ; -- type, multiplier and ceiling height left blank
|
|
48
|
+
zone = doc["Zone"]["Office"]
|
|
49
|
+
zone.ceiling_height # None, exactly as if the semicolon had come earlier
|
|
50
|
+
"ceiling_height" in zone.data # False: a blank field is not stored
|
|
51
|
+
if zone.ceiling_height is None:
|
|
52
|
+
zone.ceiling_height = "Autocalculate"
|
|
53
|
+
# --8<-- [end:blank-fields]
|
|
54
|
+
|
|
55
|
+
|
|
46
56
|
# --8<-- [start:version]
|
|
47
57
|
from idfkit import get_idf_version
|
|
48
58
|
|
|
@@ -106,6 +116,12 @@ if report.success and report.migrated_model is not None:
|
|
|
106
116
|
# --8<-- [end:mistake-version-good]
|
|
107
117
|
|
|
108
118
|
|
|
119
|
+
# --8<-- [start:mistake-blank-good]
|
|
120
|
+
if zone.multiplier is None: # blank or never written: both read as None
|
|
121
|
+
...
|
|
122
|
+
# --8<-- [end:mistake-blank-good]
|
|
123
|
+
|
|
124
|
+
|
|
109
125
|
# --8<-- [start:mistake-preserve-good]
|
|
110
126
|
doc = load_idf("building.idf", preserve_formatting=True)
|
|
111
127
|
save_idf(doc, "out.idf") # byte-identical
|
|
@@ -94,7 +94,7 @@ asyncio.run(main())
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
# --8<-- [start:batch]
|
|
97
|
-
from idfkit.simulation import simulate_batch, SimulationJob
|
|
97
|
+
from idfkit.simulation import REFUSED_RUN_DIR, simulate_batch, SimulationJob
|
|
98
98
|
|
|
99
99
|
jobs = []
|
|
100
100
|
for wwr in (0.2, 0.3, 0.4, 0.5):
|
|
@@ -106,7 +106,10 @@ for wwr in (0.2, 0.3, 0.4, 0.5):
|
|
|
106
106
|
|
|
107
107
|
batch = simulate_batch(jobs, max_workers=4)
|
|
108
108
|
for job, result in zip(jobs, batch):
|
|
109
|
-
|
|
109
|
+
if result.run_dir == REFUSED_RUN_DIR:
|
|
110
|
+
print(job.label, "refused:", result.stderr) # EnergyPlus never ran
|
|
111
|
+
else:
|
|
112
|
+
print(job.label, result.errors.summary())
|
|
110
113
|
# --8<-- [end:batch]
|
|
111
114
|
|
|
112
115
|
|
|
@@ -60,6 +60,23 @@ results = index.nearest(*geocode("350 Fifth Avenue, New York, NY"))
|
|
|
60
60
|
# --8<-- [end:search-address]
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
# --8<-- [start:geocode-errors]
|
|
64
|
+
from idfkit.weather import GeocodingError, geocode
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def locate(query: str) -> tuple[float, float] | str:
|
|
68
|
+
try:
|
|
69
|
+
return geocode(query)
|
|
70
|
+
except GeocodingError as exc:
|
|
71
|
+
if exc.kind == "not-found": # Nominatim answered: no such place
|
|
72
|
+
return f"No place called {query!r} was found."
|
|
73
|
+
# "unreachable", "http" (exc.status holds the code), or "invalid-response"
|
|
74
|
+
return "The place-name service could not be reached; try again shortly."
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# --8<-- [end:geocode-errors]
|
|
78
|
+
|
|
79
|
+
|
|
63
80
|
# --8<-- [start:search-coords]
|
|
64
81
|
hits = index.nearest(41.978, -87.904, limit=10)
|
|
65
82
|
# --8<-- [end:search-coords]
|
|
@@ -17,6 +17,7 @@ Read `references/<topic>.md` for focused, task-oriented guidance. Each reference
|
|
|
17
17
|
| Find or update cross-references between objects | [reference-tracking.md](references/reference-tracking.md) |
|
|
18
18
|
| Compute surface area, zone volume, azimuth, WWR | [geometry-and-surfaces.md](references/geometry-and-surfaces.md) |
|
|
19
19
|
| Build a building footprint and zone it | [geometry-builders-and-zoning.md](references/geometry-builders-and-zoning.md) |
|
|
20
|
+
| Build a footprint with a courtyard or hole (no slit polygons) | [geometry-builders-and-zoning.md](references/geometry-builders-and-zoning.md) |
|
|
20
21
|
| Stand up an HVAC system quickly with `HVACTemplate:*` | [hvac-templates.md](references/hvac-templates.md) |
|
|
21
22
|
| Hand-author `AirLoopHVAC` / `PlantLoop` / `CondenserLoop` | [hvac-loops.md](references/hvac-loops.md) |
|
|
22
23
|
| Run EnergyPlus simulations (sync, async, batch) | [simulation-execution.md](references/simulation-execution.md) |
|
|
@@ -24,7 +24,7 @@ The `IDFDocument` is the in-memory representation of an EnergyPlus model. Every
|
|
|
24
24
|
| `doc.schema` | `EpJSONSchema` for the document's version. |
|
|
25
25
|
| `doc[obj_type]` | The `IDFCollection` for the type. Type names are case-insensitive, as they are in EnergyPlus, so `doc["zone"]` and `doc["Zone"]` are the same collection. Never raises: an absent or misspelled type gives an empty collection that is detached from the document, so reading a type never inserts it. |
|
|
26
26
|
| `doc.get_collection(obj_type)` | The same operation, typed for dynamic `str` keys. It delegates to `doc[obj_type]`; there is no behavioural difference. |
|
|
27
|
-
| `doc.<attr>` |
|
|
27
|
+
| `doc.<attr>` | Typed attribute accessor for any schema type: the `snake_case` plural (`doc.air_loop_hvacs` is `IDFCollection[AirLoopHVAC]`). Hand-written shorthands also resolve and keep their meaning (`doc.building_surfaces` → `BuildingSurface:Detailed`, `doc.ideal_loads` → `ZoneHVAC:IdealLoadsAirSystem`). A typo raises `AttributeError` naming the closest matches. |
|
|
28
28
|
| `doc.add(obj_type, name=None, **fields)` | Create and insert an object. Returns the new `IDFObject`. |
|
|
29
29
|
| `doc.rename(obj_type, old, new)` | Rename + cascade updates through every reference. |
|
|
30
30
|
| `doc.removeidfobject(obj)` | Delete an object. |
|
|
@@ -52,6 +52,16 @@ Singletons (objects EnergyPlus requires exactly one of, like `Building` or `Simu
|
|
|
52
52
|
|
|
53
53
|
Collections support `.first()` (when you know there's a singleton), `.values()`, name-keyed `[name]` access, and `in` membership tests.
|
|
54
54
|
|
|
55
|
+
Any object type in the schema is also reachable as an attribute: its `snake_case` plural, typed like the subscript form. Use the attribute in application code, and `doc["Type:Name"]` when the type name comes from EnergyPlus documentation, an epJSON file or a variable. A misspelling raises `AttributeError` naming the closest matches:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
--8<-- "agent_references/snippets/document-and-objects.py:accessors"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Names are derived from the schema by rule, so object types added in a new EnergyPlus release are reachable with no change to idfkit. Three types the rule cannot split are named explicitly (`Output:SQLite` is `doc.output_sqlite`), and singular nouns ending in `s` get a real plural (`doc.window_material_gases`). The plural of every type, plus the shorthands, is declared in the type stub, so editors complete it and type checkers see `IDFCollection` of the generated class. The same names appear in `dir(doc)` for IPython and Jupyter.
|
|
62
|
+
|
|
63
|
+
Resolution is strict about separators: `doc.z_o_n_e`, `doc.zone_`, `doc.airloophvacs` and the singular `doc.air_loop_hvac` all raise rather than guess. The raw type name with `_` in place of `:` resolves in any case (`doc.Site_Location`, `doc.ZONE`), as it did before derived names existed, but is untyped. A name owned by a real member wins over any accessor: `doc.version` is the version tuple, and the `Version` objects are `doc.versions`.
|
|
64
|
+
|
|
55
65
|
## Modifying objects
|
|
56
66
|
|
|
57
67
|
Field access is via attribute. Setting a value re-validates against the schema and updates the reference graph automatically:
|
|
@@ -83,7 +83,7 @@ For higher-level ceiling↔floor helpers, see [geometry-builders-and-zoning.md](
|
|
|
83
83
|
|
|
84
84
|
## 2D polygon utilities
|
|
85
85
|
|
|
86
|
-
For computing footprint overlaps or differences (e.g.
|
|
86
|
+
For computing footprint overlaps or differences (e.g. corner cut-outs):
|
|
87
87
|
|
|
88
88
|
```python
|
|
89
89
|
--8<-- "agent_references/snippets/geometry-and-surfaces.py:polygon-2d"
|
|
@@ -91,8 +91,35 @@ For computing footprint overlaps or differences (e.g. courtyard cut-outs):
|
|
|
91
91
|
|
|
92
92
|
These take 2D `(x, y)` tuples (not `Vector3D`) because the typical use is on building footprints in plan view.
|
|
93
93
|
|
|
94
|
+
`polygon_difference_2d` returns one clean, counter-clockwise, simple polygon with no duplicate or coincident vertices, ready for `create_block`. The subtracted polygon may cross the outer boundary, sit flush with a corner, or extend outside it. A single ring cannot describe a result with a hole (the subtracted polygon strictly inside) or a result in several pieces, so `polygon_difference_2d` raises `ValueError` for those. Use `polygon_difference_parts_2d` from `idfkit.geometry` there. It subtracts any number of polygons and returns hole-free pieces, largest first, each one a valid EnergyPlus surface outline or `create_block` footprint. Building each piece with its own `create_block` call leaves the walls along the cuts as exterior walls; `create_courtyard_block` (in `idfkit.zoning`) builds the pieces as zones of one block so those walls are paired interior walls:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
--8<-- "agent_references/snippets/geometry-and-surfaces.py:polygon-difference-parts"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To cut a concave outline into triangles (for a mesh, or for area-weighted sampling), use `triangulate_2d` from `idfkit.geometry`. It returns index triples into the input, in the input's winding, and it handles keyhole outlines whose bridge vertices are doubled:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
--8<-- "agent_references/snippets/geometry-and-surfaces.py:triangulate-2d"
|
|
104
|
+
```
|
|
105
|
+
|
|
94
106
|
## Common mistakes
|
|
95
107
|
|
|
108
|
+
!!! failure "stitching a hole into one polygon with a slit"
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Joining the outer ring and the hole ring with a zero-width "bridge" edge
|
|
112
|
+
# gives coincident vertices, so create_block makes degenerate surfaces and EnergyPlus fatals.
|
|
113
|
+
slit = [*outer, outer[0], hole[0], *reversed(hole), hole[0]]
|
|
114
|
+
create_block(doc, "Block", slit, floor_to_floor=3.5)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
!!! success "split it into hole-free pieces in one block"
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
--8<-- "agent_references/snippets/geometry-and-surfaces.py:mistake-hole-good"
|
|
121
|
+
```
|
|
122
|
+
|
|
96
123
|
!!! failure "manually summing vertex coordinates"
|
|
97
124
|
|
|
98
125
|
```python
|
|
@@ -27,6 +27,16 @@ idfkit provides parametric footprint generators that return `list[tuple[float, f
|
|
|
27
27
|
|
|
28
28
|
Or pass any CCW `list[tuple[float, float]]` you generate yourself.
|
|
29
29
|
|
|
30
|
+
## Courtyards (footprints with holes)
|
|
31
|
+
|
|
32
|
+
EnergyPlus surfaces cannot have holes, so a footprint with a courtyard has to become several simple pieces. Use `create_courtyard_block` from `idfkit.zoning`: it subtracts the courtyard polygons from the outer footprint with `polygon_difference_parts_2d`, makes one zone per piece per story (`"<name> Part <k>"`, largest first), and builds the pieces as `CUSTOM` zones of one block, so the walls along the cuts are interior `Surface` walls paired between the pieces' zones, not exterior walls. Courtyards may also touch or cross the outer edge (a notch). `footprint_courtyard_parts` returns the two hole-free halves of a centred rectangular courtyard if you want the polygons themselves:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
--8<-- "agent_references/snippets/geometry-builders-and-zoning.py:courtyard"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each piece is concave, so there is no `zoning=` argument: every piece is one zone per story. For a finer split, compute the pieces yourself and pass them (or your own subdivision of them) as `custom_zones` to `create_block` with `ZoningScheme.CUSTOM`; walls whose edges match in reverse become interior walls.
|
|
39
|
+
|
|
30
40
|
## Zoning schemes
|
|
31
41
|
|
|
32
42
|
`ZoningScheme` controls how each story is partitioned:
|
|
@@ -41,7 +51,7 @@ Or pass any CCW `list[tuple[float, float]]` you generate yourself.
|
|
|
41
51
|
|
|
42
52
|
`create_block` also accepts:
|
|
43
53
|
|
|
44
|
-
- `air_boundary=True` — apply `Construction:AirBoundary` to all inter-zone walls. Use for open-plan spaces that share air but are still thermally distinct.
|
|
54
|
+
- `air_boundary=True` — apply `Construction:AirBoundary` to all inter-zone walls. Use for open-plan spaces that share air but are still thermally distinct. Needs EnergyPlus 9.2+; on an older document it raises `ValueError` before adding anything.
|
|
45
55
|
- `base_elevation` — ground floor Z (default 0). When > 0, the ground-floor boundary becomes `Outdoors` instead of `Ground`.
|
|
46
56
|
|
|
47
57
|
## Multi-block buildings (setbacks)
|
|
@@ -88,6 +98,12 @@ When a footprint changes and you want to keep one surface but redraw it:
|
|
|
88
98
|
--8<-- "agent_references/snippets/geometry-builders-and-zoning.py:split-surface"
|
|
89
99
|
```
|
|
90
100
|
|
|
101
|
+
The remainder is always EnergyPlus-safe: no holes and no zero-width slits. When the region sits strictly inside the surface, the remainder would be a frame with a hole, so it is cut into hole-free pieces. The original surface keeps the largest piece, and each other piece becomes a new `"<name> Remainder"` surface that copies its construction, zone and boundary conditions. To get those surfaces back, or to split under several adjacent regions at once (as `link_blocks` does for every floor resting on one roof), use `split_horizontal_surface_by_regions`:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
--8<-- "agent_references/snippets/geometry-builders-and-zoning.py:split-surface-regions"
|
|
105
|
+
```
|
|
106
|
+
|
|
91
107
|
## Common mistakes
|
|
92
108
|
|
|
93
109
|
!!! failure "passing a clockwise footprint"
|
|
@@ -120,6 +136,21 @@ When a footprint changes and you want to keep one surface but redraw it:
|
|
|
120
136
|
|
|
121
137
|
See [hvac-templates.md](hvac-templates.md).
|
|
122
138
|
|
|
139
|
+
!!! failure "the deprecated `footprint_courtyard` slit polygon"
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
fp = footprint_courtyard(50, 40, 20, 10) # DeprecationWarning
|
|
143
|
+
create_block(doc, "Office", fp, floor_to_floor=3.5)
|
|
144
|
+
# The ring repeats a vertex and runs out and back along a zero-width slit:
|
|
145
|
+
# two overlapping exterior walls and a self-touching floor. EnergyPlus fails.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
!!! success "`create_courtyard_block`"
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
--8<-- "agent_references/snippets/geometry-builders-and-zoning.py:mistake-courtyard-good"
|
|
152
|
+
```
|
|
153
|
+
|
|
123
154
|
!!! failure "forgetting `link_blocks` between setbacks"
|
|
124
155
|
|
|
125
156
|
```python
|
|
@@ -48,6 +48,14 @@ Drop to `strict=False` only as a tolerant fallback for legacy or noisy files:
|
|
|
48
48
|
--8<-- "agent_references/snippets/parsing-idf-epjson.py:strict-parsing"
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Blank fields are unset
|
|
52
|
+
|
|
53
|
+
A blank IDF field (`, ,`) is an unset field. It is not stored in `obj.data`, it is not written to epJSON, and it reads as `None`, the same as a field cut off by an early semicolon. An explicit `0` is a value and is kept. Assigning `None` or `""` to a field clears it the same way. Writing the document back to IDF and reading it again gives an equal model.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
--8<-- "agent_references/snippets/parsing-idf-epjson.py:blank-fields"
|
|
57
|
+
```
|
|
58
|
+
|
|
51
59
|
## Version handling
|
|
52
60
|
|
|
53
61
|
Every document is tied to a specific EnergyPlus version. The parser reads the `Version` object and picks the matching bundled schema (8.9 through 26.2 — see `idfkit.ENERGYPLUS_VERSIONS`).
|
|
@@ -124,6 +132,19 @@ For batch workflows, preload the schema once and pass it to every `IDFParser` so
|
|
|
124
132
|
--8<-- "agent_references/snippets/parsing-idf-epjson.py:mistake-version-good"
|
|
125
133
|
```
|
|
126
134
|
|
|
135
|
+
!!! failure "testing a blank field against the empty string"
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
if zone.multiplier == "": # never true: a blank field reads as None
|
|
139
|
+
zone.multiplier = 1
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
!!! success "test for None"
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
--8<-- "agent_references/snippets/parsing-idf-epjson.py:mistake-blank-good"
|
|
146
|
+
```
|
|
147
|
+
|
|
127
148
|
!!! failure "forgetting `preserve_formatting` for the writer side"
|
|
128
149
|
|
|
129
150
|
```python
|
|
@@ -82,6 +82,8 @@ For UI loops or asyncio-based batch tooling:
|
|
|
82
82
|
|
|
83
83
|
`async_simulate_batch` and `async_simulate_batch_stream` give you async/streaming variants. `simulate_batch` runs jobs concurrently on a thread pool (use `max_workers` to cap parallelism); each job is an EnergyPlus subprocess, so threads are all the parallelism it needs and documents never cross a process boundary.
|
|
84
84
|
|
|
85
|
+
A job that fails never raises out of the batch: its result has `success=False` and the reason in `result.stderr`. A job refused before EnergyPlus ran (for example `weather=None` on a job that is not design-day-only) and that named no `output_dir` gets `run_dir == REFUSED_RUN_DIR`, a placeholder that is never created; its `errors` report is empty, so read `stderr` for the reason.
|
|
86
|
+
|
|
85
87
|
## Caching
|
|
86
88
|
|
|
87
89
|
If you re-simulate the same model + weather + flags often (e.g. iterating on output processing), wrap in a `SimulationCache`:
|
|
@@ -90,7 +92,9 @@ If you re-simulate the same model + weather + flags often (e.g. iterating on out
|
|
|
90
92
|
--8<-- "agent_references/snippets/simulation-execution.py:caching"
|
|
91
93
|
```
|
|
92
94
|
|
|
93
|
-
The cache key is the SHA-256 of the (model
|
|
95
|
+
The cache key is the SHA-256 of the model, the weather file, the run flags, and the contents of every other file the model names: `Schedule:File` and `Schedule:File:Shading` CSVs, `Table:Lookup` / `Table:IndependentVariable` data files, `Construction:WindowDataFile`, ASHRAE 205 representation files and imported FMUs (the list is derived from the model's schema, so it follows the EnergyPlus version). Any change to any of them is a miss, and restoring a file's earlier content is a hit on the earlier entry. So staging a `Schedule:File` CSV into `output_dir` and varying it across runs caches correctly. A relative file path is resolved the way EnergyPlus resolves it: against `output_dir`, then the EnergyPlus executable's directory.
|
|
96
|
+
|
|
97
|
+
A model with a `PythonPlugin:Instance`, or with an `ExternalInterface` in `PtolemyServer` or `FunctionalMockupUnitExport` mode, has inputs no key can cover. `simulate` never caches it and logs a warning saying why.
|
|
94
98
|
|
|
95
99
|
## Progress events
|
|
96
100
|
|