Compare commits

...

10 Commits

Author SHA1 Message Date
Ryan Schultz fcb382f46c Bonsai: remove_representation handles occurrence reps mapped to a floating map
remove_representation took its type-level branch for any mapped representation,
which no-ops when the map is not one of the element type's RepresentationMaps --
a floating/shared map (typical of Revit imports where the type's RepresentationMaps
is empty). The occurrence's mapped rep was then unremovable: unassign_representation
on the type finds no matching map, and remove_representation can't delete the
geometry still referenced by the floating map.

Take the type-level branch only when the object is the type itself, or the mapped
rep is genuinely anchored to the type (new tool.Geometry.is_mapped_representation_of_type).
A floating mapped rep now falls through to occurrence-level removal, which
unassigns it from just that occurrence and lets remove_deep2 GC the map once its
last user is gone. No change for local reps or type-anchored mapped reps.

Fixes #9216.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 09:11:32 -05:00
dependabot[bot] a11ebdf8c4 build(deps): bump actions/setup-python from 6 to 7
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-31 13:57:34 +02:00
Bartok b997726564 docs: add Eigen to Linux install deps
Linux "basic dependencies" omitted libeigen3-dev even though
ifcgeom requires Eigen3 (find_package Eigen3 REQUIRED) and the
cmake snippet already passes -DEIGEN_DIR=/usr/include/eigen3.
macOS Homebrew line already installs eigen.

Closes #6903

Generated with the assistance of an AI coding tool.
2026-07-31 12:49:37 +02:00
Petru Conduraru 25713a486a Fix test_rules.py filtering by sys.argv, which empties the corpus under pytest
test_file's parametrize list was filtered with `sys.argv[1] in
os.path.basename(fn)`, reading the raw process argv instead of a
pytest-native option. Under a bare `pytest` invocation sys.argv[1] is
pytest's own first CLI token, never a match, so the 138-fixture EXPRESS
rule corpus in test/fixtures/rules collapses to an empty parametrize and
pytest reports it as a single skipped test rather than an error. Under
CI's actual invocation (pytest -p no:pytest-blender -n $NPROCS test ...)
sys.argv[1] is "-p", which happens to substring-match 47 of the 138
fixtures, so CI has been silently running a coincidental 34% slice of
the corpus with no signal anything was wrong.

Replaced the module-level list comprehension with a pytest_generate_tests
hook plus a --rule CLI option (added via a new test/conftest.py). This
runs the full corpus by default under any pytest invocation, still
allows filtering to one rule for local debugging via --rule, and no
longer collides with pytest's own argv.

Verified all 138 fixtures collect and pass under the fixed harness
(63 fail- fixtures each raise a violation, 75 pass- fixtures raise none).

Generated with the assistance of an AI coding tool.
2026-07-31 10:38:58 +02:00
Petru Conduraru d3b6b82151 ifcmcp: pin mcp below 2.0 to fix broken FastMCP import
mcp 2.0.0 (unpinned in CI and in the ifcmcp[mcp] extra) renamed
mcp.server.fastmcp.FastMCP to mcp.server.mcpserver.MCPServer, which
ifcmcp does not support yet. server.py caught the resulting
ModuleNotFoundError with a bare except Exception and silently
reported it as FastMCP not installed, masking the real breakage
until the ifcmcp test suite failed in CI.

Pinned mcp to >=1.0,<2 in both ci.yml and ifcmcp's pyproject.toml
mcp extra, confirmed the full ifcmcp test suite (70 tests) passes
against mcp 1.29.0, and confirmed the genuinely-not-installed path
still raises the expected ImportError. Also narrowed the except
clause to ImportError only so an unrelated future bug in that
import block surfaces instead of being swallowed as "not installed".

Generated with the assistance of an AI coding tool.
2026-07-31 10:36:39 +02:00
yekose 9e6797e172 ifcparse: check the result of fopen before using the FILE*
FullBufferImpl and PagedFileImpl both open the file and then use the handle
without ever testing it:

    auto stream = _wfopen(fn_wide, L"rb");   // null when the file is missing
    fseek(stream, 0, SEEK_END);              // null goes straight to the CRT
    buf_.resize((size_t)ftell(stream));

Opening a path that does not exist therefore hands a null FILE* to the CRT. On
MSVC that does not return an error: the runtime terminates the process
immediately (fastfail, exit code 0xC0000409). No exception is thrown, no stack
unwinding starts, so a caller cannot defend with try/catch — the host
application simply dies. On glibc it is undefined behaviour as well.

This is reachable through the ordinary entry point, because guess_file_type()
answers FT_IFCSPF for a path that does not exist (its own comment calls this
"just weird, but for consistency with earlier behaviour"), so a missing path
flows into the reader rather than being reported.

The fix is to leave the reader empty when the open fails. Both implementations
then behave like a zero-length file: size() is 0 and get() throws out_of_range
for any position, so the parse fails and IfcFile::good() reports it, which is
what a caller can actually handle. PagedFileImpl's destructor already tested
fp_ for null, so the possibility was known — only the constructor did not check.

Verified by reading a non-existent path through IfcParse::IfcFile: the
constructor returns and good() reports the failure, where before the process
died with 0xC0000409 and no output.
2026-07-31 10:33:04 +02:00
Petru Conduraru 1f9a0a53bb ifcopenshell.template: fix timestring ignoring an explicit timestamp of 0
create(timestamp=0) computed the FILE_NAME timestring with
`d.get("timestamp") or time.time()`, which treats 0 (a legitimate
epoch timestamp) as unset because 0 is falsy. The header ended up
with the current wall-clock time in FILE_NAME while IFCOWNERHISTORY
correctly stored CreationDate=0, an inconsistent pair of dates in
the same file. Switched to an explicit None check so an explicit
timestamp of 0 is honoured the same way any other explicit
timestamp is.

Generated with the assistance of an AI coding tool.
2026-07-31 10:19:33 +02:00
yekose b82c4c53fe ifcgeom: add a profile_point overload taking a plain double
The profile mapping builds its points as

    profile_helper(m4, {
        {{-x, -y}, {f2}},
        ...

where `f2` is a `double` and profile_point's second member is a
`boost::optional<double>`. In recent Boost (somewhere between 1.85 and 1.91)
optional's converting constructor became explicit, and an explicit constructor
cannot be used in copy-initialization — which is what a braced element is. So
every one of these call sites stops compiling:

  MSVC 19.4x:  error C2664: cannot convert argument 2 from
               'initializer list' to 'const std::vector<profile_point>&'
  clang-cl 22: error: chosen constructor is explicit in copy-initialization

Twelve translation units are affected (IfcCShapeProfileDef,
IfcIShapeProfileDef, IfcLShapeProfileDef, IfcTShapeProfileDef,
IfcUShapeProfileDef, IfcZShapeProfileDef, IfcAsymmetricIShapeProfileDef,
IfcCraneRailAShapeProfileDef, IfcRectangleProfileDef,
IfcRectangleHollowProfileDef, IfcRoundedRectangleProfileDef,
IfcTrapeziumProfileDef), roughly 100 call sites in total.

Adding one overload that takes the double directly fixes all of them without
touching a single call site, and changes nothing for existing code: the
optional overload still wins wherever an optional is passed.

Verified by building schemas 2x3;4;4x3_add2 with MSVC 2022 against Boost
1.91 and OCCT 7.9.3 — IfcParse, IfcGeom, the schema mappings and
geometry_kernel_opencascade all archive cleanly. Without this, the same build
against Boost 1.85 succeeds, which is what identified Boost as the variable.
2026-07-31 10:14:16 +02:00
CyrilWaechter 8deefe497c Fix space regen doubling Z location
Removing translate_obj_to_z_location from the existing-IfcSpace
regeneration branch. The ShapeBuilder rewrite (d8de62308) builds
geometry in local space preserving obj.matrix_world, making the
translate call redundant — it adds z on top of the already-correct
location.z, producing 2*z.

Add test_regenerate_space_preserves_z_location to cover the
regeneration path with a non-zero Z elevation.

Generated with the assistance of an AI coding tool.
2026-07-26 16:19:51 +02:00
Petru Conduraru 9621388953 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.
2026-07-25 19:46:32 +10:00
40 changed files with 138 additions and 44 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7 # https://github.com/actions/checkout
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
- uses: actions/setup-python@v7
with:
python-version: '3.11'
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
- uses: actions/setup-python@v7
with:
python-version: '3.11'
- name: Compile
@@ -50,7 +50,7 @@ jobs:
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
- uses: actions/setup-python@v7
with:
python-version: '3.11'
- name: Compile
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -85,7 +85,7 @@ jobs:
cmake --build build-ifcopenshell --target install -j "$(nproc)"
- name: Set up Python 3.11
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: 3.11
@@ -120,7 +120,7 @@ jobs:
PY
- name: Set up Python 3.12
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: 3.12
+2 -2
View File
@@ -15,12 +15,12 @@ jobs:
uses: actions/checkout@v7
- name: Action - install python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ env.MIN_IOS_PY_VERSION }}
- name: Action - install python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ env.MIN_BLENDER_PY_VERSION }}
+4 -2
View File
@@ -48,7 +48,7 @@ jobs:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: 3.11
@@ -263,7 +263,9 @@ jobs:
cd ../ifcquery && make test || ERROR=1
pip install -e ../ifcedit --no-deps
cd ../ifcedit && make test || ERROR=1
pip install mcp
# Pinned <2: mcp 2.0.0 renamed mcp.server.fastmcp.FastMCP to
# mcp.server.mcpserver.MCPServer, which ifcmcp doesn't support yet.
pip install "mcp>=1.0,<2"
pip install -e ../ifcmcp --no-deps
cd ../ifcmcp && make test || ERROR=1
pip install -e ../ifctester --no-deps
@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: '3.x'
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
run: |
rsync -av --delete --exclude='.git/' src/ifcchat/ output/
- name: Setup Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.x"
- name: Download wheels
+9 -1
View File
@@ -150,7 +150,15 @@ def remove_representation(
element_type = geometry.get_element_type(element)
data = None
data_removed_by_switch_representation = False
if element_type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
# Take the type-level branch only when the removal really is type-scoped: the
# object is the type itself, or the mapped rep is anchored to the type's
# RepresentationMaps. A mapped rep pointing at a *floating* map not owned by
# the type (typical of Revit imports) must be removed at the occurrence level
# instead -- the type branch would no-op and leave it unremovable.
if element_type and (
geometry.is_type_product(element)
or geometry.is_mapped_representation_of_type(representation, element_type)
):
representation = geometry.resolve_mapped_representation(representation)
data = geometry.get_representation_data(representation)
if data and geometry.has_data_users(data):
-1
View File
@@ -222,7 +222,6 @@ def generate_space(
if element and element.is_a("IfcSpace"):
spatial.set_space_representation_from_polygon(active_obj, element, space_polygon, h, polygon_is_si=True)
spatial.translate_obj_to_z_location(active_obj, z)
else:
if relating_type:
name = model.generate_occurrence_name(relating_type, "IfcSpace")
+1
View File
@@ -477,6 +477,7 @@ class Geometry:
def is_box_representation(cls, representation): pass
def is_data_supported_for_adding_representation(cls, data): pass
def is_mapped_representation(cls, representation): pass
def is_mapped_representation_of_type(cls, representation, element_type): pass
def is_type_product(cls, element): pass
def link(cls, element, obj): pass
def record_object_materials(cls, obj): pass
+19
View File
@@ -1317,6 +1317,25 @@ class Geometry(bonsai.core.tool.Geometry):
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.RepresentationType == "MappedRepresentation"
@classmethod
def is_mapped_representation_of_type(
cls,
representation: ifcopenshell.entity_instance,
element_type: Union[ifcopenshell.entity_instance, None],
) -> bool:
"""``True`` when ``representation`` is a mapped representation whose
``IfcRepresentationMap`` is one of ``element_type``'s ``RepresentationMaps``
-- i.e. a genuine type-anchored shared representation, as opposed to a
floating map not owned by the type (which Revit exports produce)."""
if element_type is None or representation.RepresentationType != "MappedRepresentation":
return False
if not representation.Items:
return False
item = representation.Items[0]
if not item.is_a("IfcMappedItem"):
return False
return item.MappingSource in (element_type.RepresentationMaps or [])
@classmethod
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
+24
View File
@@ -288,3 +288,27 @@ class TestGenerateSpace(NewFile):
)
)
assert np.allclose(TEST_VERTS, sorted([tuple(v.co) for v in mesh.vertices]))
def test_regenerate_space_preserves_z_location(self):
bpy.ops.bim.create_project()
ifc = tool.Ifc.get()
scene = bpy.context.scene
product = ifcopenshell.api.root.create_entity(ifc, ifc_class="IfcWall")
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
obj = bpy.data.objects["Cube"]
scene.collection.objects.link(obj)
tool.Ifc.link(product, obj)
scene.cursor.location = (0, 0, 0)
bpy.ops.bim.generate_space()
space = bpy.data.objects["IfcSpace/Space"]
space.location.z = 5
bpy.context.view_layer.update()
bpy.context.view_layer.objects.active = space
space.select_set(True)
obj.select_set(False)
bpy.ops.bim.generate_space()
assert np.isclose(space.location.z, 5), f"Expected z=5, got {space.location.z}"
+8
View File
@@ -13,6 +13,14 @@ namespace ifcopenshell {
profile_point(const std::array<double, 2>& p, const boost::optional<double>& r = boost::none)
: xy(p), radius(r) {
}
// Recent Boost makes optional's converting constructor explicit,
// and an explicit constructor cannot be used in copy-initialization
// - which is what `{{x, y}, {radius}}` in the profile mappings is.
// Taking the double directly keeps every call site working.
profile_point(const std::array<double, 2>& p, double r)
: xy(p), radius(r) {
}
};
struct profile_point_with_edges {
+1 -1
View File
@@ -9,7 +9,7 @@ from ifcmcp.core import IfcSession
try:
from mcp.server.fastmcp import FastMCP # type: ignore
from mcp.types import ImageContent # type: ignore
except Exception: # pragma: no cover
except ImportError: # pragma: no cover
FastMCP = None # type: ignore
ImageContent = None # type: ignore
+3 -1
View File
@@ -18,7 +18,9 @@ classifiers = [
dependencies = ["ifcopenshell", "ifcquery", "ifcedit"]
[project.optional-dependencies]
mcp = ["mcp"]
# Pinned <2: mcp 2.0.0 renamed mcp.server.fastmcp.FastMCP to
# mcp.server.mcpserver.MCPServer, which this package doesn't support yet.
mcp = ["mcp>=1.0,<2"]
[project.scripts]
ifcmcp = "ifcmcp.__main__:main"
@@ -60,7 +60,7 @@ operating systems. GCC (4.7 or newer) or Clang (any version) is required.
.. code-block:: bash
sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev libeigen3-dev
The CGAL version that ships with Ubuntu 20.04 is too old. Users on Ubuntu 20.04 are advised to manually install CGAL 5.3.
@@ -64,7 +64,10 @@ DEFAULTS = {
"project_globalid": lambda d: compress(uuid.uuid4().hex),
"schema_identifier": lambda d: "IFC4",
"timestamp": lambda d: int(time.time()),
"timestring": lambda d: time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(d.get("timestamp") or time.time())),
"timestring": lambda d: time.strftime(
"%Y-%m-%dT%H:%M:%S",
time.gmtime(d["timestamp"] if d.get("timestamp") is not None else time.time()),
),
"mvd": lambda d: (
"ReferenceView_V1.2"
if d.get("schema_identifier") == "IFC4"
+7
View File
@@ -0,0 +1,7 @@
def pytest_addoption(parser):
parser.addoption(
"--rule",
action="store",
default=None,
help="Only run test_rules.py fixtures whose filename contains this substring.",
)
+10 -7
View File
@@ -1,6 +1,5 @@
import glob
import os
import sys
import pytest
import tabulate
@@ -9,14 +8,18 @@ import ifcopenshell.express.rule_executor
import ifcopenshell.validate
@pytest.mark.parametrize(
"filename",
[
def pytest_generate_tests(metafunc):
if "filename" not in metafunc.fixturenames:
return
rule = metafunc.config.getoption("--rule")
filenames = [
fn
for fn in glob.glob(os.path.join(os.path.dirname(__file__), "fixtures/rules/*.ifc"))
if len(sys.argv) < 2 or sys.argv[1] in os.path.basename(fn)
],
)
if not rule or rule in os.path.basename(fn)
]
metafunc.parametrize("filename", filenames, ids=[os.path.basename(fn) for fn in filenames])
def test_file(filename):
base = os.path.basename(filename)
file = ifcopenshell.open(filename)
+14
View File
@@ -46,6 +46,13 @@ struct FullBufferImpl final : FileReader::Impl {
#else
auto stream = fopen(fn.c_str(), "rb");
#endif
if (stream == nullptr) {
// Missing or unreadable file. Leave the buffer empty so the
// caller sees a zero-length input and reports a read error;
// handing a null FILE* to the CRT below terminates the whole
// process instead of failing the parse.
return;
}
fseek(stream, 0, SEEK_END);
buf_.resize((size_t)ftell(stream));
rewind(stream);
@@ -84,6 +91,13 @@ struct PagedFileImpl final : FileReader::Impl {
#else
fp_ = fopen(fn.c_str(), "rb");
#endif
if (fp_ == nullptr) {
// As above: behave like an empty file rather than passing a
// null FILE* to fseek. get() then throws out_of_range for any
// position and fetchPage_() is never reached.
file_size_ = 0;
return;
}
fseek(fp_, 0, SEEK_END);
file_size_ = (size_t)ftell(fp_);
rewind(fp_);
+6 -2
View File
@@ -47,6 +47,10 @@ class Patcher:
This is for AGS version 3.
The input file is not read. A new IFC4X3 model is built from the AGS
data, containing one IfcBorehole per location with its strata nested
inside as IfcGeotechnicalStratum.
:param ags_file: The AGS file to convert.
:filter_glob ags_file: *.ags
:param docs_dir: The directory URI where documents are stored
@@ -55,8 +59,8 @@ class Patcher:
.. code:: python
result = ifcpatch.execute({"input": fn, "file": model, "recipe": "ExtractPropertiesToSQLite"})
ifcpatch.write(result, "output.sqlite")
result = ifcpatch.execute({"file": model, "recipe": "AGS2IFC", "arguments": ["data.ags", "docs"]})
ifcpatch.write(result, "boreholes.ifc")
"""
self.file = file
self.logger = logger