Commit Graph

325 Commits

Author SHA1 Message Date
Thomas Krijnen be3c2ee770 Expose geometry types in snake case
Generated with the assistance of an AI coding tool.
2026-08-09 04:44:18 +02:00
Dion Moult e678012c71 ifcpatch: run FixArchiCADToRevitSpaces headlessly
The recipe loaded the model into Blender purely to reach its geometry
engine, so it could only run inside Bonsai and needed a filepath instead
of a file. It is now a plain BasePatcher.

The three fixes Revit needs are unchanged. To lower each space onto its
storey we take the storey elevation from util.placement and the space
placement from util.shape, instead of from Blender object matrices. To
convert to an extruded area solid we triangulate the space with
ifcopenshell.geom, union its downwards facing triangles with shapely to
get the footprint and its voids, and rebuild the body with
util.shape_builder.

Unioning every downwards facing face, rather than only the vertices
sitting at z=0, means stepped and clipped spaces keep their full
footprint, columns poking through a room become profile voids, and
disjoint footprints extrude as one item each. Spaces that cannot be
patched are logged and skipped rather than aborting the run, and a
storey above the top of a space falls back to the space's own height
instead of asking for a negative extrusion depth.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 12:16:46 +10:00
Dion Moult 19a1d88970 Fix all ty diagnostics on ifcviewer-wgpu (ci-lint ty-ios + ty-bonsai)
This branch carried v0.8.0's strict `[tool.ty.rules] all = "error"` config but
not the source fixes that were made upstream to satisfy it, so both ci-lint ty
gates were failing: `poe ty-ios` reported 256 diagnostics and `poe ty-bonsai`
258. Both are now clean.

Most fixes are ported from v0.8.0 and follow two idioms: initialise a name
before a conditional that may not bind it (plus an `assert` where the invariant
is real but not provable), and close an exhaustive `if`/`elif` chain with
`else: assert False, <discriminant>`.

The branch's own newer accessors are preserved throughout - `.file`,
`.declaration`, `file.types()`, `get_max_id()` are kept rather than reverted to
`wrapped_data.*`, and non-ty upstream changes (notably the in-progress geometry
cache removal) are deliberately not pulled in.

Notable fixes that are not straight ports:

* ifcopenshell_wrapper.pyi: `entity_instance.file` was declared as
  `def file(self) -> file`, where the property name shadows the `class file`
  below it, so the annotation resolved to `Unknown`. Every `element.file` in
  the codebase was therefore unchecked. Qualifying it to `ifcopenshell.file`
  restores `.schema` to its Literal union and surfaces no new diagnostics.

* model/wall.py: a duplicated merge fragment in the void-straddle path ran an
  always-true `if void_straddles:` that read `new_opening` from the mutually
  exclusive branch (stale value, or NameError on the first iteration), followed
  by an unreachable duplicate `elif`. Removing it makes the file match v0.8.0.

* light/operator.py: upstream's own fix unpacks three targets from two values
  and raises ValueError unconditionally; corrected to `None, None, None`.

* assign_system.py, validate.py, geom/main.py: walrus-in-genexp is valid at
  runtime (PEP 572 binds in the containing scope) but ty does not model it;
  rewritten as explicit loops, matching upstream.

Verified: poe ty-ios, poe ty-bonsai, ruff check src/ nix/, black --check .,
and compileall -W error at py3.10 (ifcopenshell-python) and py3.11 (bonsai).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 19:14:39 +10:00
Dion Moult 291d7d8441 black . 2026-07-26 18:03:09 +10:00
Petru Conduraru 7a82c9b0a3 ifcpatch: correct the AGS2IFC docstring example
The example block was copy pasted verbatim from ExtractPropertiesToSQLite,
so it named the wrong recipe and wrote a .sqlite file. These docstrings are
what ifcpatch surfaces as CLI and UI help, so anyone following the example
for AGS2IFC got a recipe name that does not match the one they selected.

Also state that the input file is not read and that a new IFC4X3 model is
built, since that is not obvious from the signature and the recipe creates
its own project rather than patching the one passed in.

Generated with the assistance of an AI coding tool.

(cherry picked from commit 9621388953)
2026-07-25 23:50:14 +10:00
Petru Conduraru ad6f9bbbf6 ifcpatch: use stdlib graphlib for Optimise topological sort (#4399)
The Optimise recipe imported `toposort`, a third-party PyPI package that
is not bundled with Bonsai, so running the recipe there raised
`ModuleNotFoundError: No module named 'toposort'`.

Replace it with the standard library `graphlib.TopologicalSorter`
(available since Python 3.9), which provides the same dependencies-first
ordering guarantee the recipe relies on: forward-referenced instances are
mapped before the instances that reference them. The dependency-graph
dict format ({node: {predecessors}}) is identical between the two, so the
graph construction is unchanged. Drop `toposort` from ifcpatch's
dependencies since it is no longer used.

Verified with toposort NOT installed: the Optimise recipe now runs and
deduplicates correctly (IfcParseExamples_test.ifc 88 -> 63 instances, all
6 products preserved, output reopens cleanly).

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 382f5e0c21)
2026-07-25 23:18:33 +10:00
Petru Conduraru 6dba261e1b Optimise IfcPatch recipe: make toposort backend configurable
aothms asked for the toposort dependency ordering used by the dedup
walk to try igraph's C-backed topological_sorting() first, since it
should shave off additional time on top of the non-recursive
get_info fix. Falls back to the pure python toposort package with a
warning if igraph is not installed.

Generated with the assistance of an AI coding tool.

(cherry picked from commit 7ebdd046b6)
2026-07-25 23:16:38 +10:00
Petru Conduraru 4299968b6e Fix #1043. Optimise IfcPatch recipe: avoid redundant recursive get_info
The 2020 profiling in issue #1043 found the Optimise recipe's dedup
loop spent almost all of its time in entity_instance.get_info(recursive=True):
because the topological sort already guarantees every referenced entity
is folded before the entity that references it, recomputing each
already-folded subtree's canonical value from scratch for every parent
that points to it is wasted work. Confirmed this is still exactly the
bottleneck in the current codebase, unchanged since 2020 (get_info's
recursive path still walks the whole subtree on every call).

Applied aothms's suggested fix from the issue thread: canonicalize each
entity with a non-recursive get_info, and for referenced entities substitute
the already-computed identity of their folded replacement (looked up in
instance_mapping) instead of re-expanding the subtree. Also limited the
toposort dependency graph to direct references (max_levels=1), since a
topological sort only needs direct edges, not the full transitive closure
traverse() was computing for every entity.

Benchmarked before and after on real IFC test fixtures and a larger
synthetic file with heavily shared geometry (thousands of walls sharing
a handful of profile/point subtrees, mirroring the sharing pattern
described in the issue):

- test/input/geometrygym_great_court_roof.ifc (56989 entities): 9.9s -> 1.7s
- test/input/acad2010_objects.ifc (16296 entities): 3.7s -> 0.4s
- synthetic 120083-entity fixture with heavy geometry sharing: 19.2s -> 3.4s

Verified correctness by comparing the full canonical (recursive get_info)
multiset of the optimized output between the old and new implementation on
all three fixtures: identical results, same fold counts.

Added test_Optimise.py covering the core scenario from the issue: entities
built from separate, value-identical non-rooted subtrees fold to a shared
instance, while entities with distinct values do not.

Generated with the assistance of an AI coding tool.

(cherry picked from commit 57cfd9d1fd)
2026-07-25 23:16:38 +10:00
Thomas Krijnen 7fc2d9a998 Merge remote-tracking branch 'origin/v0.8.0' into ifcviewer-wgpu 2026-07-09 13:21:39 +02:00
Petru Conduraru 49de7dbcb1 ExtractElements: handle IfcProject without RepresentationContexts #8199
The georeferencing fix (e6dc582) iterates IfcProject.RepresentationContexts
unconditionally, but the attribute is OPTIONAL and None on projects without
contexts, crashing every extraction on such files with
TypeError: 'NoneType' object is not iterable.

Also extend the #8199 regression test to assert element placements are
copied verbatim, so extraction can never bake map coordinates into local
placements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:24:34 +02:00
Thomas Krijnen f3e047d78e Schema compatibility #8230 2026-07-02 12:01:57 +02:00
Thomas Krijnen e6dc582d82 ExtractElements - copy over coordionate operation #8199 2026-06-30 10:12:45 +02:00
Thomas Krijnen 366fa67a84 Don't rely on cwd 2026-06-30 10:02:55 +02:00
Petru Conduraru 4a8b863b96 test(ifcpatch): ExtractElements regression test for georeferencing loss (#8199)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 09:59:07 +02:00
Petru Conduraru fce7cd3eb4 test(ifcpatch): add MergeProjects regression test for merging 3+ files (#7973)
Merging more than two IFC models with the MergeProjects recipe leaves
duplicated IfcGeometricRepresentationContext entities behind. All elements
are kept, but the accumulated contexts cause later disciplines to appear
"not merged" in viewers.

This test merges three projects and asserts the elements are kept, a single
IfcProject remains, and the geometric contexts are reused rather than
accumulated. It currently fails on the context assertion, reproducing #7973.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:43:07 +02:00
Gorgious56 2ab5ca9222 ifcpatch: small recipe polish
ExtractElements: expand the `query` docstring to cover the exclusion
syntax (`!` on entity classes, `!=` on attribute / pset / material /
classification / location / group facets) and the "seed with a broad
include before subtracting" gotcha — entity-class exclusion does not
auto-seed from "all elements", so a bare `! IfcSlab` query returns
nothing.

FixArchiCADToRevitDoorSwings: guard the `IfcIndexedPolyCurve.Segments`
loop against the IFC4 case where Segments is absent (a polyline
through all coords in declared order). Previously crashed on
`None.__iter__`.

Generated with the assistance of an AI coding tool.
2026-06-23 09:46:31 +02:00
Gorgious56 f710929e9e ifcpatch Migrate: defensive IFC4/IFC4X3 -> IFC2X3 downgrade
The Migrate recipe previously crashed mid-loop with the cryptic
`RuntimeError: Entity with name '' not found in schema 'IFC2X3'` when
asked to downgrade an IFC4 or IFC4X3 file to IFC2X3 — the
class_4_to_2x3 mapping marks IFC4-only geometry / element classes with
an empty-string sentinel and the old code blindly forwarded that to
create_entity. Real files routinely contain IfcPolygonalFaceSet,
IfcTriangulatedFaceSet, IfcIndexedPolyCurve, IfcLamp, IfcPipeSegment,
IfcGeographicElement, etc.

The recipe now runs a preprocessing pipeline when the target is IFC2X3
and the source is IFC4 or IFC4X3:

- DowngradeIndexedPolyCurve flattens IfcIndexedPolyCurve to IfcPolyline
  for the whole file (arcs included — see below).
- IfcPolygonalFaceSet / IfcTriangulatedFaceSet are converted directly
  to IfcFacetedBrep at the entity level via
  ifcopenshell.util.shape_builder.polygonal_face_set_to_faceted_brep,
  preserving topology including IfcIndexedPolygonalFaceWithVoids inner
  bounds. IfcShapeRepresentation carriers have their RepresentationType
  tag updated from "Tessellation" to "Brep".
- Orphan source-only geometry instances (left over after the rewires)
  are purged iteratively via
  geometry_classes_introduced_after(target, source).

The Migrator is invoked with fallback_element_to_proxy=True so
IFC4-only IfcElement subclasses (IfcLamp, IfcPipeSegment,
IfcGeographicElement, ...) become IfcBuildingElementProxy in the
output. A post-pass encodes "<OriginalClass>/<PredefinedType>" into
ObjectType (e.g. "IfcLamp/COMPACTFLUORESCENT") when ObjectType is
empty, so the lost subclass identity survives the downgrade as
searchable text.

The migration loop now collects per-entity failures into a list rather
than crashing on the first; a summary RuntimeError fires at end if any
failed, naming up to 20 with their inverse references. Successful
migrations log a single count line via self.logger.

DowngradeIndexedPolyCurve extended:
- Arc segments (IfcArcIndex) are flattened via
  ifcopenshell.util.shape_builder.arc_to_polyline_points with
  ARC_SUBDIVISION=16 chord points per arc.
- Multi-index IfcLineIndex segments handled correctly.
- Absent Segments list (IFC4 polyline-through-all-coords case) handled.

Test coverage: 11 tests across the two recipes covering all four
preprocessing branches, the IFC4X3 source gate, the ObjectType
encoding (incl. author-supplied ObjectType preservation), the summary
RuntimeError shape, and the arc subdivision.

Generated with the assistance of an AI coding tool.
2026-06-23 09:33:03 +02:00
Thomas Krijnen 1f6c467c88 Change default value of assume_asset_uniqueness_by_name #8045 2026-06-11 18:29:12 +02:00
Thomas Krijnen 4e406ab1ce Change default value of assume_asset_uniqueness_by_name #8045 2026-05-18 13:29:39 +02:00
Thomas Krijnen 91ae631c7d Merge remote-tracking branch 'origin/v0.8.0' into datamodel-v1.0 2026-04-18 20:15:28 +02:00
Andrej730 26280d24fe Add ty to check for missing symbols and other simple errors 2026-03-20 15:36:14 +05:00
Andrej730 c33509364c Fix error generating ifcpatch recipes docs for Bonsai tooltips
Mentioned in https://github.com/IfcOpenShell/IfcOpenShell/issues/7667#issuecomment-4076645173

Traceback:
```
Traceback (most recent call last):
  File "\bonsai\bim\module\patch\prop.py", line 55, in get_ifcpatch_recipes
    docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
  File "\ifcpatch\__init__.py", line 168, in extract_docs
    spec.loader.exec_module(submodule)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "<frozen importlib._bootstrap_external>", line 1027, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "\ifcpatch/recipes/FixRevit2025TINs.py", line 31, in <module>
    class Patcher:
    ...<509 lines>...
            return co / self.unit_scale
  File "\ifcpatch/recipes/FixRevit2025TINs.py", line 168, in Patcher
    def create_edges(self, obj: bpy.types.Object) -> None:
                                ^^^
NameError: name 'bpy' is not defined
File "\bonsai\bim\module\patch\prop.py", line 43, in get_ifcpatch_recipes
```
2026-03-18 10:04:36 +05:00
Andrej730 515fe8d2ef Remove use of deprecated tempfile.mktemp 2026-03-17 18:14:22 +05:00
Andrej730 fb16e91249 Remove use of deprecated datetime.utcnow()
To fix warnings below:
```
<python-input-1>:1: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
```
2026-03-16 15:10:03 +05:00
Andrej730 eba798c544 typing 2026-03-13 20:26:22 +05:00
Andrej730 ecc82a52f5 ExtractPropertiesToSQLite - add typing for created columns 2026-03-06 19:07:34 +05:00
Andrej730 9005333f53 ifcpatch MergeProjects - make logger arg optional 2026-03-06 19:07:33 +05:00
Andrej730 8743d5643e typing 2026-03-06 19:07:33 +05:00
Andrej730 619848823c Sort out imports 2026-03-06 19:07:32 +05:00
Andrej730 92c979fbbf black . 2026-02-27 14:52:55 +05:00
Andrej730 6d4eb06d89 black . 2026-01-26 17:10:03 +05:00
Andrej730 72f8218fcd ruff - check for more unused imports 2026-01-26 17:10:02 +05:00
Thomas Krijnen b66b04b001 Remove usage of .wrapped_item and some other fixes 2026-01-10 11:01:18 +01:00
Andrej730 13be6ccd45 Sort imports 2025-12-19 18:53:14 +05:00
Andrej730 64686dde5c Remove redundant :type from doc-strings
Args types already covered by annotations
2025-11-17 16:30:27 +05:00
Bruno Postle ad62c75f4f ifcclash ifccsv ifcdiff ifcpatch: README.md stubs
The pyproject.toml files were referencing these missing files
2025-11-09 20:52:12 +00:00
Dion Moult 65237cab65 Fix #7080. Potential bug with merging styles that didn't replace elements of the same class. 2025-10-13 08:44:47 +11:00
Dion Moult 25dacc8fed Fix #7208. Merge duplicates patch recipe can now control merging by empty attributes. 2025-10-06 23:18:32 +11:00
Andrej730 cc7732ecbf Replace .wrapped_data.header with .header (dfaa8d5) 2025-09-15 18:27:21 +05:00
Andrej730 be66d4884d typing 2025-09-15 18:27:19 +05:00
Andrej730 10e805b3f1 black . 2025-09-10 15:34:06 +05:00
Richard Brice d552f247fa Fixed a typo mapping horizontal curve segments to geometry 2025-09-09 13:08:31 -07:00
Andrej730 0cf3bb3da9 ifc2sql - support saving element types geometry 2025-09-05 19:10:36 +05:00
Andrej730 062bf8e60b typing 2025-09-05 19:10:36 +05:00
Andrej730 8c12bd0322 Reuse builder.create_axis2_placement_3d 2025-09-04 19:09:18 +05:00
Dion Moult 823eb7d199 SetFalseOrigin recipe now by default resets non-geometric spatial elements (XY values only). 2025-08-02 18:11:33 +10:00
Dion Moult 7038cbe871 ResetSpatialElementLocations patch now supports only_xy toggle.
Also rewrote the patch to use API and util functions to make it more
robust.
2025-08-02 17:54:55 +10:00
Andrej730 c700a32b09 Fix RUF015 2025-07-28 19:27:59 +05:00
Andrej730 0cb6d9de72 typing 2025-07-28 19:27:59 +05:00
Andrej730 cdeeab4b31 black . 2025-07-28 17:03:25 +05:00