mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-17 10:59:17 +00:00
Merge branch 'v0.8.0' into ifcmax/initial-refresh
This commit is contained in:
@@ -1177,6 +1177,8 @@ if "cgal" in targets:
|
||||
# Disable assembly, otherwise `emcc -c conftest.s` will crash due to assembly mismatch.
|
||||
gmp_args.extend(("--disable-assembly", "--enable-cxx"))
|
||||
mpfr_args.extend(("--host", "none"))
|
||||
elif "x86" in arch:
|
||||
gmp_args.append("--enable-fat") # See issues #7458 #7556
|
||||
|
||||
OLD_CC = None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
name = "IfcOpenShell"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"black==25.12",
|
||||
"ruff==0.14.13",
|
||||
"black==26.1.0",
|
||||
"ruff==0.14.14",
|
||||
"poethepoet",
|
||||
"gersemi==0.25.1",
|
||||
]
|
||||
|
||||
@@ -368,21 +368,11 @@ class BIM_PT_new_project_wizard(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "mass_unit", text="Mass Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "time_unit", text="Time Unit")
|
||||
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
||||
|
||||
if tool.Blender.get_addon_preferences().mass_time_units_in_wizard:
|
||||
header, body = self.layout.panel("Mass and Time Units", default_closed=True)
|
||||
if header:
|
||||
header.label(text="Mass and Time Units")
|
||||
if body:
|
||||
label = "Add Mass and Time Units" if not props.add_mass_time_units else "Remove Mass and Time Units"
|
||||
body.prop(props, "add_mass_time_units", toggle=True, text=label)
|
||||
if props.add_mass_time_units:
|
||||
row = body.row()
|
||||
row.prop(props, "mass_unit", text="Mass Unit")
|
||||
row = body.row()
|
||||
row.prop(props, "time_unit", text="Time Unit")
|
||||
|
||||
self.layout.use_property_split = True
|
||||
row = self.layout.row()
|
||||
row.operator("bim.create_project")
|
||||
|
||||
@@ -569,7 +569,7 @@ class SavePsetAsTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOpera
|
||||
template_file = IfcStore.pset_template_file
|
||||
assert template_file
|
||||
|
||||
tool.PsetTemplate.add_pset_as_template(pset, template_file)
|
||||
tool.PsetTemplate.add_pset_as_template(pset.Name, template_file)
|
||||
|
||||
template_file.write(IfcStore.pset_template_path)
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -589,6 +589,7 @@ class BIMProperties(PropertyGroup):
|
||||
area_unit: EnumProperty(
|
||||
default="SQUARE_METRE",
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
("NANO/SQUARE_METRE", "Square Nanometre", ""),
|
||||
("MICRO/SQUARE_METRE", "Square Micrometre", ""),
|
||||
("MILLI/SQUARE_METRE", "Square Millimetre", ""),
|
||||
@@ -606,6 +607,7 @@ class BIMProperties(PropertyGroup):
|
||||
volume_unit: EnumProperty(
|
||||
default="CUBIC_METRE",
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
("NANO/CUBIC_METRE", "Cubic Nanometre", ""),
|
||||
("MICRO/CUBIC_METRE", "Cubic Micrometre", ""),
|
||||
("MILLI/CUBIC_METRE", "Cubic Millimetre", ""),
|
||||
@@ -619,31 +621,28 @@ class BIMProperties(PropertyGroup):
|
||||
],
|
||||
name="IFC Volume Unit",
|
||||
)
|
||||
add_mass_time_units: bpy.props.BoolProperty(
|
||||
name="Add Mass and Time Units",
|
||||
description="Enable to define mass and time units for the project",
|
||||
default=False,
|
||||
)
|
||||
mass_unit: EnumProperty(
|
||||
items=[
|
||||
("KILOGRAM", "Kilogram", "Kilograms"),
|
||||
("NONE", "None", ""),
|
||||
("GRAM", "Gram", "Grams"),
|
||||
("POUND", "Pound", "Pounds"),
|
||||
("OUNCE", "Ounce", "Ounces"),
|
||||
("TONNE", "Tonne", "Metric Tons"),
|
||||
("KILO/KILOGRAM", "Kilogram", "Kilograms"),
|
||||
("MEGA/TONNE", "Tonne", "Metric Tons"),
|
||||
("pound", "Pound", "Pounds"),
|
||||
("ounce", "Ounce", "Ounces"),
|
||||
],
|
||||
name="Mass Unit",
|
||||
default="KILOGRAM",
|
||||
default="NONE",
|
||||
)
|
||||
time_unit: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
("SECOND", "Second", "Seconds"),
|
||||
("MINUTE", "Minute", "Minutes"),
|
||||
("HOUR", "Hour", "Hours"),
|
||||
("DAY", "Day", "Days"),
|
||||
("minute", "Minute", "Minutes"),
|
||||
("hour", "Hour", "Hours"),
|
||||
("day", "Day", "Days"),
|
||||
],
|
||||
name="Time Unit",
|
||||
default="HOUR",
|
||||
default="NONE",
|
||||
)
|
||||
tab_visibilities: CollectionProperty(type=BIMTabVisibility, name="Tab Visibilities")
|
||||
active_tab_visibility_index: IntProperty(name="Active Tab Visibility Index")
|
||||
|
||||
@@ -717,12 +717,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
default=False,
|
||||
)
|
||||
|
||||
mass_time_units_in_wizard: BoolProperty(
|
||||
name="Mass and time units in project wizard",
|
||||
description="Show mass and time units section in the new project wizard panel",
|
||||
default=False,
|
||||
)
|
||||
|
||||
chain_filter_with_set_operations: BoolProperty(
|
||||
name="NEW Filter mode: Enable chained filters with set operations",
|
||||
description="Enable chaining search filters with set operations: ADD (union: combine sets), SUBTRACT (difference: remove from set), FILTER (intersection: only elements in both sets), with autocomplete suggestions for filter values",
|
||||
|
||||
@@ -1124,7 +1124,7 @@ class Unit:
|
||||
def enable_editing_units(cls): pass
|
||||
def export_unit_attributes(cls): pass
|
||||
def get_scene_unit_name(cls, unit_type): pass
|
||||
def get_scene_unit_si_prefix(cls, unit_type): pass
|
||||
def get_scene_unit_si_prefix(cls, name): pass
|
||||
def import_unit_attributes(cls, unit): pass
|
||||
def import_units(cls): pass
|
||||
def is_scene_unit_metric(cls): pass
|
||||
|
||||
@@ -27,46 +27,17 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None:
|
||||
if unit.is_scene_unit_metric():
|
||||
lengthunit = ifc.run(
|
||||
"unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT")
|
||||
)
|
||||
areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=unit.get_scene_unit_si_prefix("AREAUNIT"))
|
||||
volumeunit = ifc.run(
|
||||
"unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT")
|
||||
)
|
||||
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
|
||||
units = [lengthunit, areaunit, volumeunit, planeangleunit]
|
||||
|
||||
if unit.add_mass_and_time_units():
|
||||
prefix = unit.get_scene_unit_si_prefix("MASSUNIT")
|
||||
if prefix == "CONVERSION":
|
||||
massunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("MASSUNIT").lower())
|
||||
units = []
|
||||
for unit_type in ["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT", "MASSUNIT", "TIMEUNIT"]:
|
||||
if name := unit.get_scene_unit_name(unit_type):
|
||||
if unit.is_si_unit(name):
|
||||
units.append(
|
||||
ifc.run("unit.add_si_unit", unit_type=unit_type, prefix=unit.get_scene_unit_si_prefix(name))
|
||||
)
|
||||
else:
|
||||
massunit = ifc.run("unit.add_si_unit", unit_type="MASSUNIT", prefix=prefix)
|
||||
prefix = unit.get_scene_unit_si_prefix("TIMEUNIT")
|
||||
if prefix == "CONVERSION":
|
||||
timeunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("TIMEUNIT").lower())
|
||||
else:
|
||||
timeunit = ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=prefix)
|
||||
units += [massunit, timeunit]
|
||||
|
||||
else:
|
||||
lengthunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("LENGTHUNIT"))
|
||||
areaunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("AREAUNIT"))
|
||||
volumeunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("VOLUMEUNIT"))
|
||||
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
|
||||
units = [lengthunit, areaunit, volumeunit, planeangleunit]
|
||||
|
||||
if unit.add_mass_and_time_units():
|
||||
massunit = ifc.run("unit.add_conversion_based_unit", name=unit.get_scene_unit_name("MASSUNIT").lower())
|
||||
time_unit_name = unit.get_scene_unit_name("TIMEUNIT")
|
||||
if time_unit_name == "SECOND":
|
||||
timeunit = ifc.run("unit.add_si_unit", unit_type="TIMEUNIT", prefix=None)
|
||||
else:
|
||||
timeunit = ifc.run("unit.add_conversion_based_unit", name=time_unit_name.lower())
|
||||
units += [massunit, timeunit]
|
||||
ifc.run("unit.assign_unit", units=units)
|
||||
units.append(ifc.run("unit.add_conversion_based_unit", name=name))
|
||||
if units:
|
||||
ifc.run("unit.assign_unit", units=units)
|
||||
|
||||
|
||||
def assign_unit(ifc: type[tool.Ifc], unit_tool: type[tool.Unit], unit: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -61,19 +61,23 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
|
||||
tool.Ifc.Operator._execute(self, context)
|
||||
|
||||
@classmethod
|
||||
def add_pset_as_template(
|
||||
cls, pset: ifcopenshell.entity_instance, template_file: ifcopenshell.file
|
||||
) -> ifcopenshell.entity_instance:
|
||||
# TODO: add tests.
|
||||
pset_template = ifcopenshell.api.pset_template.add_pset_template(template_file, pset.Name)
|
||||
for property in pset.HasProperties:
|
||||
ifcopenshell.api.pset_template.add_prop_template(
|
||||
template_file,
|
||||
pset_template,
|
||||
name=property.Name,
|
||||
description=property.Description,
|
||||
primary_measure_type=property.NominalValue.is_a(),
|
||||
)
|
||||
def add_pset_as_template(cls, pset_name: str, template_file: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
added_prop_names = set()
|
||||
pset_template = ifcopenshell.api.pset_template.add_pset_template(template_file, pset_name)
|
||||
for pset in tool.Ifc.get().by_type("IfcPropertySet"):
|
||||
if pset.Name != pset_name:
|
||||
continue
|
||||
for prop in pset.HasProperties:
|
||||
if prop.Name in added_prop_names:
|
||||
continue
|
||||
added_prop_names.add(prop.Name)
|
||||
ifcopenshell.api.pset_template.add_prop_template(
|
||||
template_file,
|
||||
pset_template,
|
||||
name=prop.Name,
|
||||
description=prop.Description,
|
||||
primary_measure_type=prop.NominalValue.is_a(),
|
||||
)
|
||||
return pset_template
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -330,66 +330,26 @@ class Unit(bonsai.core.tool.Unit):
|
||||
return bonsai.bim.helper.export_attributes(props.unit_attributes, callback=callback)
|
||||
|
||||
@classmethod
|
||||
def get_scene_unit_name(cls, unit_type: UNIT_TYPE) -> str:
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
def get_scene_unit_name(cls, unit_type: UNIT_TYPE) -> str | None:
|
||||
if unit_type == "LENGTHUNIT":
|
||||
assert bpy.context.scene
|
||||
props = bpy.context.scene.unit_settings
|
||||
if props.length_unit == "MILES":
|
||||
return "mile"
|
||||
elif props.length_unit == "FEET" or props.length_unit == "ADAPTIVE":
|
||||
return "foot"
|
||||
elif props.length_unit == "INCHES":
|
||||
return "inch"
|
||||
elif props.length_unit == "THOU":
|
||||
return "thou"
|
||||
return "foot"
|
||||
elif unit_type == "AREAUNIT":
|
||||
return bim_props.area_unit
|
||||
elif unit_type == "VOLUMEUNIT":
|
||||
return bim_props.volume_unit
|
||||
elif unit_type == "MASSUNIT":
|
||||
return bim_props.mass_unit.lower()
|
||||
elif unit_type == "TIMEUNIT":
|
||||
return bim_props.time_unit.lower()
|
||||
else:
|
||||
assert_never(unit_type)
|
||||
name = bpy.context.scene.unit_settings.length_unit
|
||||
name = {"MILES": "mile", "FEET": "foot", "INCHES": "inch", "THOU": "thou", "ADAPTIVE": "METERS"}.get(
|
||||
name, name
|
||||
)
|
||||
if len(name) > len("METERS") and name.endswith("METERS"):
|
||||
return f"{name[:-6]}/METRE"
|
||||
return name
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
if (name := getattr(bim_props, f"{unit_type[:-4].lower()}_unit")) != "NONE":
|
||||
return name
|
||||
|
||||
@classmethod
|
||||
def get_scene_unit_si_prefix(cls, unit_type: UNIT_TYPE) -> Union[str, None]:
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
if unit_type == "LENGTHUNIT":
|
||||
assert bpy.context.scene
|
||||
props = bpy.context.scene.unit_settings
|
||||
if props.length_unit == "ADAPTIVE" or props.length_unit == "METERS":
|
||||
return
|
||||
return props.length_unit.replace("METERS", "")
|
||||
elif unit_type == "AREAUNIT":
|
||||
unit = bim_props.area_unit
|
||||
elif unit_type == "VOLUMEUNIT":
|
||||
unit = bim_props.volume_unit
|
||||
elif unit_type == "MASSUNIT":
|
||||
unit = bim_props.mass_unit
|
||||
if unit == "GRAM":
|
||||
return None
|
||||
elif unit == "KILOGRAM":
|
||||
return "KILO"
|
||||
elif unit == "TONNE":
|
||||
return "MEGA"
|
||||
elif unit in ["POUND", "OUNCE"]:
|
||||
return "CONVERSION"
|
||||
else:
|
||||
return None
|
||||
elif unit_type == "TIMEUNIT":
|
||||
unit = bim_props.time_unit
|
||||
if unit == "SECOND":
|
||||
return None
|
||||
else:
|
||||
return "CONVERSION"
|
||||
else:
|
||||
assert_never(unit_type)
|
||||
if "/" in unit:
|
||||
return unit.split("/")[0]
|
||||
def is_si_unit(cls, name: str) -> bool:
|
||||
return name[0].isupper()
|
||||
|
||||
@classmethod
|
||||
def get_scene_unit_si_prefix(cls, name: str) -> str | None:
|
||||
return name.split("/")[0] if "/" in name else None
|
||||
|
||||
@classmethod
|
||||
def import_unit_attributes(cls, unit: ifcopenshell.entity_instance) -> None:
|
||||
@@ -504,9 +464,3 @@ class Unit(bonsai.core.tool.Unit):
|
||||
elif ifc_class == "IfcMonetaryUnit":
|
||||
return "COPY_ID"
|
||||
return "MOD_MESHDEFORM"
|
||||
|
||||
@classmethod
|
||||
def add_mass_and_time_units(cls) -> bool:
|
||||
"""Return True if the user wants to add mass and time units, False otherwise."""
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
return getattr(bim_props, "add_mass_time_units", False)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.tool.pset_template import PsetTemplate as subject
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
|
||||
class TestImplementsTool(NewFile):
|
||||
def test_run(self):
|
||||
assert isinstance(subject(), bonsai.core.tool.PsetTemplate)
|
||||
|
||||
|
||||
class TestAddPsetAsTemplate(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
element1 = ifc.createIfcWall()
|
||||
element2 = ifc.createIfcWall()
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc, product=element1, name="Foo")
|
||||
prop = ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"Foo": "a"})
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc, product=element2, name="Foo")
|
||||
prop = ifcopenshell.api.pset.edit_pset(ifc, pset=pset, properties={"Bar": "b"})
|
||||
library = ifcopenshell.file()
|
||||
assert (pset_template := subject.add_pset_as_template("Foo", library))
|
||||
assert pset_template.is_a("IfcPropertySetTemplate")
|
||||
assert len(templates := pset_template.HasPropertyTemplates) == 2
|
||||
assert set(t.Name for t in templates) == {"Foo", "Bar"}
|
||||
@@ -31,12 +31,16 @@ find_package(Eigen3 REQUIRED)
|
||||
|
||||
target_link_libraries(
|
||||
IfcGeom
|
||||
${kernel_libraries}
|
||||
${mapping_libraries}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
"Eigen3::Eigen"
|
||||
${CGAL_LIBRARIES}
|
||||
)
|
||||
|
||||
if(WITH_OPENCASCADE)
|
||||
target_link_libraries(IfcGeom TKernel)
|
||||
endif()
|
||||
|
||||
if(NOT WASM_BUILD)
|
||||
target_link_libraries(IfcGeom IfcParse)
|
||||
endif()
|
||||
|
||||
@@ -91,7 +91,7 @@ bool IfcGeom::Representation::BRep::calculate_volume(double& volume) const {
|
||||
volume = 0.;
|
||||
return false;
|
||||
}
|
||||
volume = s->area()->to_double();
|
||||
volume = s->volume()->to_double();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ foreach(kernel ${GEOMETRY_KERNELS})
|
||||
|
||||
set(KERNEL_TARGET "geometry_kernel_${kernel}")
|
||||
|
||||
add_library(${KERNEL_TARGET} OBJECT ${IFCGEOM_FILES})
|
||||
add_library(${KERNEL_TARGET} ${IFCGEOM_FILES})
|
||||
set_target_properties(
|
||||
${KERNEL_TARGET}
|
||||
PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" PUBLIC_HEADER "${IFCGEOM_H_FILES}"
|
||||
)
|
||||
list(APPEND kernel_libraries ${KERNEL_TARGET})
|
||||
target_link_libraries(${KERNEL_TARGET} ${${KERNEL_UPPER}_LIBRARIES} Eigen3::Eigen)
|
||||
target_link_libraries(${KERNEL_TARGET} ${${KERNEL_UPPER}_LIBRARIES} IfcGeom Eigen3::Eigen)
|
||||
install(
|
||||
TARGETS ${KERNEL_TARGET}
|
||||
EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}
|
||||
@@ -27,7 +27,7 @@ foreach(kernel ${GEOMETRY_KERNELS})
|
||||
set_property(TARGET ${KERNEL_TARGET} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
|
||||
|
||||
set(KERNEL_TARGET_SIMPLE "${KERNEL_TARGET}_simple")
|
||||
add_library(${KERNEL_TARGET_SIMPLE} OBJECT ${IFCGEOM_FILES})
|
||||
add_library(${KERNEL_TARGET_SIMPLE} ${IFCGEOM_FILES})
|
||||
set_target_properties(
|
||||
${KERNEL_TARGET_SIMPLE}
|
||||
PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS"
|
||||
@@ -42,3 +42,8 @@ foreach(kernel ${GEOMETRY_KERNELS})
|
||||
endforeach()
|
||||
|
||||
set(kernel_libraries ${kernel_libraries} PARENT_SCOPE)
|
||||
|
||||
install(
|
||||
FILES ifc_geomlibrary_api.h
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ifcgeom/kernels/"
|
||||
)
|
||||
|
||||
@@ -1835,6 +1835,9 @@ bool CgalKernel::convert_impl(const taxonomy::boolean_result::ptr br, Conversion
|
||||
CGAL::Polygon_with_holes_2<Kernel_> pwh(p, ++it, loops.end());
|
||||
CGAL::Gps_segment_traits_2<Kernel_> traits;
|
||||
if (!CGAL::are_holes_and_boundary_pairwise_disjoint(pwh, traits)) {
|
||||
#ifdef IFOPSH_SIMPLE_KERNEL
|
||||
throw std::runtime_error("Holes are not disjoint - use a different geometry kernel");
|
||||
#else
|
||||
// this is very slow.
|
||||
// the check is also slow...
|
||||
|
||||
@@ -1851,6 +1854,7 @@ bool CgalKernel::convert_impl(const taxonomy::boolean_result::ptr br, Conversion
|
||||
result.difference(*it);
|
||||
}
|
||||
result.polygons_with_holes(std::back_inserter(pwhs));
|
||||
#endif
|
||||
} else {
|
||||
pwhs.push_back(pwh);
|
||||
}
|
||||
|
||||
@@ -1792,6 +1792,10 @@ namespace IfcGeom {
|
||||
auto& vs = elem->geometry().verts();
|
||||
auto& fs = elem->geometry().faces();
|
||||
|
||||
if (vs.empty() || fs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gp_Trsf tr;
|
||||
tr.SetValues(
|
||||
m(0, 0), m(0, 1), m(0, 2), m(0, 3),
|
||||
@@ -1859,23 +1863,36 @@ namespace IfcGeom {
|
||||
candidates.push_back({ std::abs(Z.Dot(ref)), ref });
|
||||
}
|
||||
|
||||
if (candidates.empty()) {
|
||||
{
|
||||
gp_XYZ ref(0, 0, 1);
|
||||
candidates.push_back({ std::abs(Z.Dot(ref)), ref });
|
||||
gp_Ax3 ax3;
|
||||
gp_Trsf trsf2;
|
||||
|
||||
for (size_t attempt = 0; attempt < 2; ++attempt) {
|
||||
|
||||
if (candidates.empty() || attempt == 1) {
|
||||
{
|
||||
gp_XYZ ref(0, 0, 1);
|
||||
candidates.push_back({std::abs(Z.Dot(ref)), ref});
|
||||
}
|
||||
{
|
||||
gp_XYZ ref(1, 0, 0);
|
||||
candidates.push_back({std::abs(Z.Dot(ref)), ref});
|
||||
}
|
||||
}
|
||||
|
||||
auto X = std::min_element(candidates.begin(), candidates.end(), [](auto& p1, auto& p2) { return p1.first < p2.first; })->second;
|
||||
|
||||
{
|
||||
gp_XYZ ref(1, 0, 0);
|
||||
candidates.push_back({ std::abs(Z.Dot(ref)), ref });
|
||||
try {
|
||||
ax3 = gp_Ax3(gp::Origin(), Z, X);
|
||||
trsf2.SetTransformation(gp::XOY(), ax3);
|
||||
} catch (Standard_ConstructionError&) {
|
||||
// Try again, likely we have all identical normals in candidates so
|
||||
// we cannot find a suitable candidate and need the two default axes
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto X = std::min_element(candidates.begin(), candidates.end(), [](auto& p1, auto& p2) { return p1.first < p2.first; })->second;
|
||||
|
||||
gp_Trsf trsf2;
|
||||
gp_Ax3 ax3(gp::Origin(), Z, X);
|
||||
trsf2.SetTransformation(gp::XOY(), ax3);
|
||||
|
||||
Bnd_Box tmp;
|
||||
|
||||
for (auto& p : vs_transformed) {
|
||||
|
||||
@@ -1134,6 +1134,8 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To
|
||||
#endif
|
||||
builder->SetFuzzyValue(fuzz);
|
||||
builder->SetArguments(s1s);
|
||||
// We use our own multi-threading in ifcopenshell on a per-product basis
|
||||
builder->SetRunParallel(false);
|
||||
copy_operand(b, b_tmp);
|
||||
std::swap(b, b_tmp);
|
||||
builder->SetTools(b);
|
||||
|
||||
@@ -12,7 +12,8 @@ table below.
|
||||
+=========================+=========================+======================+
|
||||
| .ifc | .obj, .dae, .glb, .stp, | IfcConvert |
|
||||
| | .igs, .xml, .svg, .h5, | |
|
||||
| | .ttl, .ifc | |
|
||||
| | .ttl, .ifc, .rdb, | |
|
||||
| | .json (xeokit) | |
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .ifc | .dae, .abc, .usd, .obj, | Bonsai_ |
|
||||
| | .ply, .stl, .fbx, .glb, | |
|
||||
|
||||
@@ -70,7 +70,7 @@ CLI Manual
|
||||
|
||||
$ IfcConvert -h
|
||||
|
||||
IfcOpenShell IfcConvert 0.8.1-c49ca69 (OCC 7.8.1)
|
||||
IfcOpenShell IfcConvert 0.8.4-158fe92 (OCC 7.8.1)
|
||||
Usage: IfcConvert [options] <input.ifc> [<output>]
|
||||
|
||||
Converts (the geometry in) an IFC file into one of the following formats:
|
||||
@@ -80,6 +80,8 @@ CLI Manual
|
||||
.stp STEP Standard for the Exchange of Product Data
|
||||
.igs IGES Initial Graphics Exchange Specification
|
||||
.xml XML Property definitions and decomposition tree
|
||||
.json JSON Property definitions and decomposition tree in xeokit json format
|
||||
.rdb RocksDB RocksDB Key-Value store serialization of IFC data
|
||||
.svg SVG Scalable Vector Graphics (2D floor plan)
|
||||
.h5 HDF Hierarchical Data Format storing positions, normals and indices
|
||||
.ttl TTL/WKT RDF Turtle with Well-Known-Text geometry
|
||||
@@ -108,7 +110,8 @@ CLI Manual
|
||||
|
||||
Geometry options:
|
||||
--kernel arg (=opencascade) Geometry kernel to use (opencascade,
|
||||
cgal, cgal-simple).
|
||||
cgal, cgal-simple, hybrid-cgal-simple-o
|
||||
pencascade).
|
||||
-j [ --threads ] arg (=1) Number of parallel processing threads
|
||||
for geometry interpretation.
|
||||
--center-model Centers the elements by applying the
|
||||
@@ -118,6 +121,11 @@ CLI Manual
|
||||
--center-model-geometry Centers the elements by applying the
|
||||
center point of all mesh vertices as an
|
||||
offset.
|
||||
--model-offset arg Applies an arbitrary offset of form
|
||||
'x;y;z' to all placements.
|
||||
--model-rotation arg Applies an arbitrary quaternion
|
||||
rotation of form 'x;y;z;w' to all
|
||||
placements.
|
||||
--include arg Specifies that the instances that match
|
||||
a specific filtering criteria are to be
|
||||
included in the geometrical output:
|
||||
@@ -207,13 +215,12 @@ CLI Manual
|
||||
of surface normals, even if the faces
|
||||
are not properly oriented in the IFC
|
||||
file.
|
||||
--length-unit arg (= 1)
|
||||
--angle-unit arg (= 1)
|
||||
--precision arg (= 1e-05)
|
||||
--dimensionality arg (= 1) Specifies whether to include curves
|
||||
and/or surfaces and solids in the
|
||||
output result. Defaults to only
|
||||
surfaces and solids.
|
||||
surfaces and solids (SURFACES_AND_SOLID
|
||||
S). Other possible values are CURVES,
|
||||
CURVES_SURFACES_AND_SOLIDS.
|
||||
--layerset-first Assigns the first layer material of the
|
||||
layerset to the complete product.
|
||||
--disable-boolean-result Specifies whether to disable the
|
||||
@@ -262,9 +269,11 @@ CLI Manual
|
||||
geometrical output back to the unit of
|
||||
measure in which it is defined in the
|
||||
IFC file. Default is to use meters.
|
||||
--context-ids arg
|
||||
--context-ids arg
|
||||
--context-ids arg
|
||||
--context-ids arg List of comma separated context ids to
|
||||
process - e.g. '15,29' (no quotes
|
||||
needed).
|
||||
--context-types arg Currently option has no effect.
|
||||
--context-identifiers arg Currently option has no effect.
|
||||
--iterator-output arg (= 0)
|
||||
--disable-opening-subtractions Specifies whether to disable the
|
||||
boolean subtraction of
|
||||
@@ -304,10 +313,18 @@ CLI Manual
|
||||
geometry output.
|
||||
--circle-segments arg (= 16) Number of segments to approximate full
|
||||
circles in CGAL kernel.
|
||||
--cgal-smooth-angle-degrees arg (= -1)
|
||||
Angle in degrees under which adjacent
|
||||
facets will have averaged vertex
|
||||
normals in CGAL output. NB irrespective
|
||||
of original IFC geometry types.
|
||||
Defaults to -1 to disable smoothing.
|
||||
--keep-bounding-boxes Default is to removes IfcBoundingBox
|
||||
from model prior to converting
|
||||
geometry.Setting this option disables
|
||||
that behaviour
|
||||
--compute-curvature Specifies whether function_item_evaluat
|
||||
or.evaluate() computes curvature.
|
||||
--function-step-type arg (= 0) Indicates the method used for defining
|
||||
step size when evaluating
|
||||
function-based curves. Provides
|
||||
@@ -320,12 +337,24 @@ CLI Manual
|
||||
parallel. May decrease performance, but
|
||||
also decrease output size (in the
|
||||
future)
|
||||
--model-offset arg Applies an arbitrary offset of form
|
||||
'x,y,z' to all placements.
|
||||
--model-rotation arg Applies an arbitrary quaternion
|
||||
rotation of form 'x,y,z,w' to all
|
||||
placements.
|
||||
--permissive-shape-reuse Traverse geometry-level transformations
|
||||
and apply to product-level placement in
|
||||
order to increase reuse of geometries
|
||||
--triangulation-type arg (= 0) Type of planar facet to be emitted
|
||||
--cgal-original-edges Try to emit original edge face boundary
|
||||
edges instead of recomputed ones based
|
||||
on face normal. Falls back to
|
||||
triangulated data in case of boolean
|
||||
operands and faces with holes.
|
||||
--cache-shapes Experimental as not all topology hash
|
||||
functions fully implemented
|
||||
--max-offset arg Maximum translation offset to be
|
||||
observed after which median offset in
|
||||
model gets removed and logged. Requires
|
||||
--no-parallel-mapping.
|
||||
--max-offset-deviation arg To retain field of view, completely
|
||||
remove elements outside of the median
|
||||
offset. Requires --no-parallel-mapping.
|
||||
|
||||
Serialization options:
|
||||
--bounds arg Specifies the bounding rectangle, for
|
||||
@@ -421,3 +450,8 @@ CLI Manual
|
||||
--wkt-use-section Use a geometrical section rather than
|
||||
full polyhedral output and footprint in
|
||||
TTL WKT
|
||||
--separate-z-up-node Introduce a separate Z-Up node into the
|
||||
GlTF hierarchy instead of multiplying
|
||||
the transform into the root node
|
||||
matrices
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ edges, and faces, or alternatively an OpenCASCADE BRep.
|
||||
applications. See the `Geometry iterator`_ section below after reading this
|
||||
to see how to process geometry with multiple threads.
|
||||
|
||||
|
||||
Here is a simple example of processing a single wall into a list of vertices and
|
||||
faces. In this example, a ``shape`` variable is returned, which holds geometry
|
||||
related information in ``shape.geometry``:
|
||||
@@ -33,8 +32,12 @@ related information in ``shape.geometry``:
|
||||
ifc_file = ifcopenshell.open('model.ifc')
|
||||
element = ifc_file.by_type('IfcWall')[0]
|
||||
|
||||
# Create a shape using a hybrid of the cgal-simple geometry kernel and opencascade as a fallback
|
||||
# Choosing a geometry kernel has a big impact on speed and capability.
|
||||
# It is recommended to use the "hybrid-cgal-simple-opencascade" kernel.
|
||||
settings = ifcopenshell.geom.settings()
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
shape = ifcopenshell.geom.create_shape(
|
||||
settings, element, geometry_library="hybrid-cgal-simple-opencascade")
|
||||
|
||||
# The GUID of the element we processed
|
||||
print(shape.guid)
|
||||
@@ -224,7 +227,8 @@ Here is a simple example in Python:
|
||||
ifc_file = ifcopenshell.open('model.ifc')
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count())
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, ifc_file, multiprocessing.cpu_count(), geometry_library="hybrid-cgal-simple-opencascade")
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
shape = iterator.get()
|
||||
@@ -252,7 +256,9 @@ only process wall elements.
|
||||
.. code-block:: python
|
||||
|
||||
walls = ifc.by_type('IfcWall')
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc, multiprocessing.cpu_count(), include=walls)
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
settings, ifc, multiprocessing.cpu_count(),
|
||||
include=walls, geometry_library="hybrid-cgal-simple-opencascade")
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -111,6 +111,32 @@ In Python, this is set when the iterator is constructed:
|
||||
import multiprocessing
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, num_threads=multiprocessing.cpu_count())
|
||||
|
||||
geometry_library
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
+--------+-------------------+-------------+
|
||||
| Type | IfcConvert Option | Default |
|
||||
+========+===================+=============+
|
||||
| STRING | ``--kernel`` | opencascade |
|
||||
+--------+-------------------+-------------+
|
||||
|
||||
IfcOpenShell supports multiple geometry kernels to process geometry. Choosing the geometry kernel has trade-offs on geometric support, speed, and maturity. It is possible and recommended to chose a hybrid geometry kernel by providing the name ``hybrid-kernelX-kernelY``, where ``kernelX`` is the name of the first kernel to try, and ``kernelY`` is the name of the fallback kernel, for example ``hybrid-cgal-simple-opencascade``.
|
||||
|
||||
.. csv-table::
|
||||
:header: "Comparison", "cgal-simple", "cgal", "opencascade"
|
||||
|
||||
"Speed", "Very fast", "Fast", "Slow"
|
||||
"Curves in extrusion footprints", "Only circle and ellipse arcs are converted to polylines", "Only circle and ellipse arcs are converted to polylines", "Full support including beziers and nurbs"
|
||||
"Advanced (curved) breps", "No", "No", "Full support"
|
||||
"Boolean operations", "No", "Full support", "Full support"
|
||||
"Boolean operations with tolerance / fuzziness handling", "No", "Only manifold inputs", "Full support, including non-manifold inputs"
|
||||
"Sweeps along alignment curves", "Partial", "Partial", "Full support"
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, geometry_library="hybrid-cgal-simple-opencascade")
|
||||
ifcopenshell.geom.create_shape(settings, element, geometry_library="opencascade")
|
||||
|
||||
Iterator settings
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -750,6 +750,14 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
if node.attr == "create_entity":
|
||||
return node
|
||||
|
||||
if node.attr.startswith("__"):
|
||||
return node
|
||||
|
||||
# Don't rewrite at module scope (top-level, no indent)
|
||||
enclosing_stmt = next((p for p in parents if isinstance(p, ast.stmt)), None)
|
||||
if enclosing_stmt is not None and isinstance(getattr(enclosing_stmt, "parent", None), ast.Module):
|
||||
return node
|
||||
|
||||
new_value = self.visit(node.value)
|
||||
|
||||
# Replace the Attribute node with a call to the built-in `getattr` function
|
||||
@@ -842,18 +850,21 @@ if __name__ == "__main__":
|
||||
|
||||
print(
|
||||
"""
|
||||
def is_indeterminate(v):
|
||||
return v is None or type(v).__name__ == 'indeterminate_type'
|
||||
|
||||
def exists(v):
|
||||
if callable(v):
|
||||
try: return v() is not None
|
||||
except IndexError as e: return False
|
||||
else: return v is not None
|
||||
else: return not is_indeterminate(v)
|
||||
""",
|
||||
"\n",
|
||||
file=output,
|
||||
sep="\n",
|
||||
)
|
||||
print(
|
||||
"def nvl(v, default): return v if v is not None else default",
|
||||
"def nvl(v, default): return v if not is_indeterminate(v) else default",
|
||||
"\n",
|
||||
file=output,
|
||||
sep="\n",
|
||||
@@ -871,14 +882,14 @@ def is_entity(inst):
|
||||
def express_len(v):
|
||||
if isinstance(v, ifcopenshell.entity_instance) and not is_entity(v):
|
||||
v = v[0]
|
||||
elif v is None or v is INDETERMINATE:
|
||||
elif is_indeterminate(v):
|
||||
return INDETERMINATE
|
||||
return len(v)
|
||||
|
||||
old_range = range
|
||||
|
||||
def range(*args):
|
||||
if INDETERMINATE in args:
|
||||
if any(map(is_indeterminate, args)):
|
||||
return
|
||||
yield from old_range(*args)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,7 @@
|
||||
|
||||
bool check_aggregate_of_type(PyObject* aggregate, void* type_obj) {
|
||||
if (!PySequence_Check(aggregate)) return false;
|
||||
if (PySequence_Size(aggregate) == -1) return false;
|
||||
for(Py_ssize_t i = 0; i < PySequence_Size(aggregate); ++i) {
|
||||
PyObject* element = PySequence_GetItem(aggregate, i);
|
||||
// This is equivalent to the PyFloat_CheckExact macro. This means
|
||||
@@ -46,6 +47,7 @@
|
||||
|
||||
bool check_aggregate_of_aggregate_of_type(PyObject* aggregate, void* type_obj) {
|
||||
if (!PySequence_Check(aggregate)) return false;
|
||||
if (PySequence_Size(aggregate) == -1) return false;
|
||||
for(Py_ssize_t i = 0; i < PySequence_Size(aggregate); ++i) {
|
||||
PyObject* element = PySequence_GetItem(aggregate, i);
|
||||
bool b = check_aggregate_of_type(element, type_obj);
|
||||
|
||||
Reference in New Issue
Block a user