Compare commits

...

23 Commits

Author SHA1 Message Date
Dion Moult 3bdc969efb Fix #4690. User can now configure the font used to render viewport text. 2024-05-19 23:36:38 +10:00
Bruno Postle 932817877c Fix CPack source tarball generation
This was broken, now a IfcOpenShell-0.7.0.tar.gz file can be created
like so:

  mkdir build
  cd build
  cmake ../cmake/ -DEXTRA_VERSION=
  make package_source
2024-05-19 13:29:26 +01:00
Andrej730 cb255a18b9 fix error editing pset templates when you just opened ifc file
It was failing with an error below if you'd try to edit any property since `primary_measure_type()` returned `[]` because it was executed before `pset_templates()` and `IfcStore.pset_template_file` wasn't yet set.

```python
Error: Python: Traceback (most recent call last):
  File "\blenderbim\bim\module\pset_template\operator.py", line 146, in execute
    props.active_prop_template.primary_measure_type = template.PrimaryMeasureType
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: bpy_struct: item.attr = val: enum "IfcLabel" not found in ()
```
2024-05-18 11:24:10 +05:00
Andrej730 183028570e bump xsdata in blenderbim after 87c9bbe2a #4640 2024-05-18 01:49:56 +05:00
Andrej730 341cdd4ef7 fix error updating curve representations after 3fa573e #4685 #4682 2024-05-17 17:40:13 +05:00
Andrej730 fe68e16ca3 bim.update_representation - error if add_representation didn't worked 2024-05-17 17:31:01 +05:00
Andrej730 a8f0c90d47 set_element_value - fix issues with classification, similar to 52ae759 2024-05-17 17:31:01 +05:00
Andrej730 39f550529b experimental - set_element_value to throw exception for invalid queries 2024-05-17 17:31:01 +05:00
Andrej730 25c1a14e64 set_element_value - pass attr value to the next part of the query #4495
E.g. previously set_element_value with query "material.item.Material.Name" would fail with error "Material property is expecting an IFC entity and not a string".

But now it will detect that "Material" property is not a last key in the query and will pass `.Material` value forward and try to set it's `.Name` attribute with value.

The main goal is to make sure get_element_value and set_element_value would have a same result for same queries.
2024-05-17 17:31:01 +05:00
Andrej730 9f7d710119 fix set_element_value bug #4495 2024-05-17 17:31:01 +05:00
Andrej730 ea592775e4 fix set_element_value setting bool attributes with "False" str value 2024-05-17 17:31:01 +05:00
Andrej730 42be4eb064 typing 2024-05-17 17:31:01 +05:00
Hilko 8662e20c58 remove unused include #4620 2024-05-17 10:26:04 +02:00
Andrej730 da4ce3773f bcf - fix xsdata warning about deprecated argument in SerializerConfig
`pretty_print` was deprecated in 24.2 (https://github.com/tefra/xsdata/commit/2b01dbf6cf586e7d64e93ec7045dc17d2585e0de) and replaced with `indent`
2024-05-17 11:57:46 +05:00
Andrej730 87c9bbe2a7 Fix bcf due xsdata update #4680
After https://github.com/tefra/xsdata/commit/93a8ca0548d1f7badb628ad9fd0327672abe140b

`parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")` started failing with `TypeError: PushParser.register_namespace() missing 1 required positional argument: 'uri'` since xsdata added a new argument `ns_map` (previously it was always using `parse.ns_map` under the hood, now it allows to provide some external dictionary). We just restore the original behaviour with internal `ns_map` by providing it explicitly.

Specified xsdata version after that change in pyproject.toml.

Also `serialize()` is now using `parser.ns_map` as a fallback value (otherwise why we do `parser.register_namespace` if we never used the `parser.ns_map`?)
2024-05-17 11:46:53 +05:00
Andrej730 a66ee06e3d dev environment - add bcf 2024-05-17 11:42:06 +05:00
Andrej730 9969de58cb add a console toggle to error message section 2024-05-16 18:14:07 +05:00
Andrej730 6995cafe63 bim.split_along_edge fixes #4675
1) description to emphasize that active object will be the one that's cutting
2) skip non-ifc elements, previously they failed to process with error `AttributeError: 'NoneType' object has no attribute 'RepresentationType'`. Also skip objects without representations.
3) add an info message at the end

Here's a short gif in case if anyone doesn't about that feature: https://imgur.com/a/338w7jx
2024-05-16 18:14:06 +05:00
Andrej730 eaa80ad08a fix issue getting styles if object had empty mat slots
Occurred only with only_assigned_to_faces = True
A bit related to #4675

Error was
    styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]

ValueError: zip() argument 2 is longer than argument 1
2024-05-16 18:14:06 +05:00
Andrej730 d001180082 remove unused code 2024-05-16 18:14:06 +05:00
Andrej730 3884403231 typing 2024-05-16 18:14:06 +05:00
Andrej730 8d4b5d83e1 same as e1250f217 2024-05-16 16:13:22 +05:00
Andrej730 e1250f2177 fix saving debug info to clipboard #4675
now we just rely on blender to make it crossplatform
2024-05-16 16:00:38 +05:00
36 changed files with 254 additions and 128 deletions
+6 -3
View File
@@ -36,7 +36,9 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
# use extra version to make pre-release using eg semver
set(EXTRA_VERSION "-alpha.3")
if(NOT DEFINED EXTRA_VERSION)
set(EXTRA_VERSION "-alpha.3")
endif()
option(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
option(WASM_BUILD "Build a WebAssembly binary." OFF)
@@ -1147,9 +1149,10 @@ endif()
# Packaging
list(APPEND CPACK_SOURCE_IGNORE_FILES
.git
.gitignore
"/\\\\.git"
"/build/"
)
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}${EXTRA_VERSION}")
SET(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}-${CMAKE_SYSTEM_NAME}")
+1 -1
View File
@@ -13,7 +13,7 @@ readme = "README.md"
requires-python = ">=3.8"
keywords = ["IFC", "BCF", "BIM"]
dependencies = [
"xsdata",
"xsdata>=24.4",
"numpy",
"ifcopenshell",
]
+7 -5
View File
@@ -10,14 +10,14 @@ from xsdata.formats.dataclass.serializers.config import SerializerConfig
def build_xml_parser(context: Optional[XmlContext] = None) -> XmlParser:
"""Return a parser for an XML file."""
parser = XmlParser(context=context or XmlContext())
parser.register_namespace("xs", "http://www.w3.org/2001/XMLSchema")
parser.register_namespace(ns_map=parser.ns_map, prefix="xs", uri="http://www.w3.org/2001/XMLSchema")
return parser
def build_serializer(context: Optional[XmlContext] = None) -> XmlSerializer:
"""Return a serializer for an XML file."""
return XmlSerializer(
config=SerializerConfig(pretty_print=True),
config=SerializerConfig(indent=" "),
context=context or XmlContext(),
)
@@ -36,8 +36,9 @@ class AbstractXmlParserSerializer(Protocol):
xml: The XML file as bytes.
clazz: The class to parse to.
"""
...
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
def serialize(self, obj: object, ns_map: Optional[dict[str, str]] = None) -> str:
"""
Serialize an object to XML.
@@ -48,6 +49,7 @@ class AbstractXmlParserSerializer(Protocol):
Returns:
The XML as string.
"""
...
class XmlParserSerializer:
@@ -68,7 +70,7 @@ class XmlParserSerializer:
"""
return self.parser.from_bytes(xml, clazz)
def serialize(self, obj: T, ns_map: Optional[dict[str, str]] = None) -> str:
def serialize(self, obj: object, ns_map: Optional[dict[Optional[str], str]] = None) -> str:
"""
Serialize an object to XML.
@@ -79,5 +81,5 @@ class XmlParserSerializer:
Returns:
The XML as string.
"""
ns_map = ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
ns_map = ns_map or self.parser.ns_map or {"xs": "http://www.w3.org/2001/XMLSchema"}
return self.serializer.render(obj, ns_map)
+2 -2
View File
@@ -286,9 +286,9 @@ endif
# Required by bcf
mkdir dist/working
cd dist/working && wget https://files.pythonhosted.org/packages/ad/c7/17c9d16320d8e2cfdb27d2fd298b5e8f7a3f211025b0c1bc7a39a85bd690/xsdata-22.11.tar.gz
cd dist/working && wget https://files.pythonhosted.org/packages/b4/ef/35d8118f903510f9e028f8a6a4edb615fa69e28a30d955593425a88e587a/xsdata-24.5.tar.gz
cd dist/working && tar -xzvf xsdata*
cp -r dist/working/xsdata-22.11/xsdata dist/blenderbim/libs/site/packages/
cp -r dist/working/xsdata-24.5/xsdata dist/blenderbim/libs/site/packages/
rm -rf dist/working
# Required by bcf
+2 -9
View File
@@ -122,7 +122,7 @@ if sys.modules.get("bpy", None):
def draw(self, context):
layout = self.layout
layout.label(text="BlenderBIM could not load.", icon="ERROR")
layout.label(text="View the console for full logs.", icon="CONSOLE")
layout.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
box = layout.box()
info = get_debug_info()
py = ".".join(info["python_version"].split(".")[0:2])
@@ -149,14 +149,7 @@ if sys.modules.get("bpy", None):
def execute(self, context):
info = format_debug_info(get_debug_info())
if platform.system() == "Windows":
command = "echo | set /p nul=" + info
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + info.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
context.window_manager.clipboard = info
return {"FINISHED"}
class HiddenPanel:
+14 -10
View File
@@ -26,6 +26,7 @@ import bmesh
import logging
import mathutils
import numpy as np
import numpy.typing as npt
import multiprocessing
import ifcopenshell
import ifcopenshell.geom
@@ -303,22 +304,22 @@ class IfcImporter:
self.update_progress(100)
bpy.context.window_manager.progress_end()
def is_element_far_away(self, element):
def is_element_far_away(self, element: ifcopenshell.entity_instance) -> bool:
try:
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
point = placement[:, 3][0:3]
return self.is_point_far_away(point, is_meters=False)
except:
pass
return False
def is_point_far_away(self, point, is_meters=True):
def is_point_far_away(
self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
) -> bool:
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
# Users can configure this if they have to handle larger sites but beware of surveying precision
limit = self.ifc_import_settings.distance_limit
limit = limit if is_meters else (limit / self.unit_scale)
coords = point
if hasattr(point, "Coordinates"):
coords = point.Coordinates
coords = getattr(point, "Coordinates", point)
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
def process_context_filter(self):
@@ -683,7 +684,7 @@ class IfcImporter:
props.blender_orthogonal_height = str(offset_point[2])
props.has_blender_offset = True
def get_offset_point(self):
def get_offset_point(self) -> Union[npt.NDArray[np.float64], None]:
elements_checked = 0
# If more than these elements aren't far away, the file probably isn't absolutely positioned
element_checking_threshold = 10
@@ -718,7 +719,7 @@ class IfcImporter:
if self.is_point_far_away(point, is_meters=False):
return point
def does_element_likely_have_geometry_far_away(self, element):
def does_element_likely_have_geometry_far_away(self, element: ifcopenshell.entity_instance) -> bool:
for representation in element.Representation.Representations:
items = []
for item in representation.Items:
@@ -735,13 +736,14 @@ class IfcImporter:
if subelement.is_a("IfcCartesianPoint"):
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
return True
return False
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset:
if obj.data and obj.data.get("has_cartesian_point_offset", None):
obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT"
elif self.is_point_far_away((matrix[0, 3], matrix[1, 3], matrix[2, 3])):
elif self.is_point_far_away((matrix[:3, 3])):
obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
matrix = ifcopenshell.util.geolocation.global2local(
matrix,
@@ -754,7 +756,9 @@ class IfcImporter:
return mathutils.Matrix(matrix.tolist())
def find_decomposed_ifc_class(self, element, ifc_class):
def find_decomposed_ifc_class(
self, element: ifcopenshell.entity_instance, ifc_class: str
) -> Union[ifcopenshell.entity_instance, None]:
if element.is_a(ifc_class):
return element
rel_aggregates = element.IsDecomposedBy
@@ -60,13 +60,7 @@ class CopyDebugInformation(bpy.types.Operator):
print(text)
print("-" * 80)
if platform.system() == "Windows":
command = "echo | set /p nul=" + text
elif platform.system() == "Darwin": # for MacOS
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | pbcopy'
else: # Linux
command = 'printf "' + text.replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
subprocess.run(command, shell=True, check=True)
context.window_manager.clipboard = text
return {"FINISHED"}
@@ -159,9 +159,7 @@ class BaseDecorator:
objecttype = "NOTDEFINED"
def __init__(self):
self.font_id = blf.load(
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
self.font_id = 0 # 0 is the default font
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
@@ -404,7 +402,6 @@ class BaseDecorator:
line_no += 1 if multiline_to_bottom else -1
return
# 0 is the default font, but we're fancier than that
font_id = self.font_id
color = context.preferences.addons["blenderbim"].preferences.decorations_colour
@@ -2014,6 +2011,13 @@ class DecorationsHandler:
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
if os.path.exists(drawing_font_path):
font_id = blf.load(drawing_font_path)
for decorator in self.decorators.values():
decorator.font_id = font_id
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
@@ -315,9 +315,7 @@ class RasterStyleProperty(enum.Enum):
SPACE_SHADING = "space.shading"
RASTER_STYLE_PROPERTIES_EXCLUDE = (
"scene.render.filepath",
)
RASTER_STYLE_PROPERTIES_EXCLUDE = ("scene.render.filepath",)
class DocProperties(PropertyGroup):
@@ -371,6 +369,7 @@ class DocProperties(PropertyGroup):
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
)
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
class BIMCameraProperties(PropertyGroup):
@@ -400,7 +400,11 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
# TODO: replace with core.add_representation?
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
if new_representation is None:
self.report({"ERROR"}, "Error creating representation for Blender object.")
return {"CANCELLED"}
if tool.Geometry.is_body_representation(new_representation):
[
@@ -23,6 +23,7 @@ import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.misc as core
import blenderbim.core.geometry as core_geometry
import blenderbim.core.root
from blenderbim.bim.ifc import IfcStore
from mathutils import Vector, Matrix, Euler
@@ -134,6 +135,10 @@ class ResizeToStorey(bpy.types.Operator, Operator):
class SplitAlongEdge(bpy.types.Operator, Operator):
bl_idname = "bim.split_along_edge"
bl_label = "Split Along Edge"
bl_description = (
"Active object is considered to be a cutting object."
"Will unassign element from a type if type has a representation."
)
bl_options = {"REGISTER", "UNDO"}
@classmethod
@@ -144,16 +149,25 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
cutter = context.active_object
objs = [o for o in context.selected_objects if o != cutter]
objs_to_cut = []
# Splitting only works on meshes
for obj in objs:
# You cannot split meshes if the representation is mapped.
element = tool.Ifc.get_entity(obj)
if element:
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
if not element:
continue
relating_type = tool.Root.get_element_type(element)
if relating_type and tool.Root.does_type_have_representations(relating_type):
bpy.ops.bim.unassign_type(related_object=obj.name)
# refresh representation
representation = tool.Geometry.get_active_representation(obj)
# skip empty objects that might get in the way
if not representation:
continue
core_geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -168,11 +182,13 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
if not tool.Geometry.is_meshlike(representation):
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="IfcTessellatedFaceSet")
new_objs = tool.Misc.split_objects_with_cutter(objs, cutter)
objs_to_cut.append(obj)
new_objs = tool.Misc.split_objects_with_cutter(objs_to_cut, cutter)
for obj in new_objs:
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
bpy.ops.bim.update_representation(obj=obj.name)
for obj in objs:
for obj in objs_to_cut:
bpy.ops.bim.update_representation(obj=obj.name)
representation = tool.Geometry.get_active_representation(obj)
@@ -187,6 +203,8 @@ class SplitAlongEdge(bpy.types.Operator, Operator):
apply_openings=True,
)
self.report({"INFO"}, f"Splitting finished, {len(new_objs)} new objects created.")
class GetConnectedSystemElements(bpy.types.Operator, Operator):
bl_idname = "bim.get_connected_system_elements"
@@ -931,13 +931,11 @@ class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
return context.active_object
def _execute(self, context):
styles = tool.Geometry.get_styles(context.active_object)
blenderbim.core.material.patch_non_parametric_mep_segment(
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
)
bpy.ops.bim.enable_editing_extrusion_axis()
bpy.ops.bim.edit_extrusion_axis()
styles = tool.Geometry.get_styles(context.active_object)
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
@@ -19,6 +19,7 @@
import bpy
import blenderbim.bim.schema
import ifcopenshell
import ifcopenshell.util.attribute
import ifcopenshell.util.element
import blenderbim.tool as tool
from blenderbim.bim.prop import Attribute, StrProperty
@@ -36,10 +36,14 @@ class PsetTemplatesData:
@classmethod
def load(cls):
cls.is_loaded = True
cls.data["pset_template_files"] = cls.pset_template_files()
# after pset_template_files
cls.data["pset_templates"] = cls.pset_templates()
# after pset_template_files because it loads IfcStore.pset_template_file
cls.data["primary_measure_type"] = cls.primary_measure_type()
cls.data["property_template_type"] = cls.property_template_type()
cls.data["pset_template_files"] = cls.pset_template_files()
cls.data["pset_templates"] = cls.pset_templates()
cls.data["pset_template"] = cls.pset_template()
cls.data["prop_templates"] = cls.prop_templates()
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.attribute
import blenderbim.bim.helper
import blenderbim.bim.handler
import blenderbim.core.structural as core
+14 -14
View File
@@ -290,8 +290,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row = layout.row()
row.prop(self, "spatial_elements_unselectable")
row = layout.row()
row.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save")
row = layout.row()
@@ -320,28 +318,30 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
row.prop(context.scene.BIMProperties, "data_dir")
row.operator("bim.select_data_dir", icon="FILE_FOLDER", text="")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.BIMProperties, "pset_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "sheets_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "layouts_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "titleblocks_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawings_dir")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "stylesheet_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "markers_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "symbols_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "patterns_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "shadingstyles_path")
row = self.layout.row(align=True)
row = self.layout.row()
row.prop(context.scene.DocProperties, "shadingstyle_default")
row = self.layout.row()
row.prop(context.scene.DocProperties, "drawing_font")
# Scene panel groups
@@ -410,7 +410,7 @@ class BIM_PT_tabs(Panel):
row = box.row(align=True)
row.label(text="BlenderBIM experienced an error :(", icon="ERROR")
row.operator("bim.close_error", text="", icon="CANCEL")
box.label(text="View the console for full logs.", icon="CONSOLE")
box.operator("wm.console_toggle", text="View the console for full logs.", icon="CONSOLE")
box.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
op = box.operator("bim.open_uri", text="How Can I Fix This?")
op.uri = "https://docs.blenderbim.org/users/troubleshooting.html"
+20 -12
View File
@@ -18,10 +18,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def edit_object_placement(
@@ -37,8 +38,15 @@ def edit_object_placement(
def add_representation(
ifc, geometry, style, surveyor, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
ifc: tool.Ifc,
geometry: tool.Geometry,
style: tool.Style,
surveyor: tool.Surveyor,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj)
if not element:
return
@@ -89,15 +97,15 @@ def add_representation(
def switch_representation(
ifc,
geometry,
obj=None,
representation=None,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
apply_openings=True,
):
ifc: tool.Ifc,
geometry: tool.Geometry,
obj: bpy.types.Object,
representation: ifcopenshell.entity_instance,
should_reload: bool = True,
is_global: bool = True,
should_sync_changes_first: bool = False,
apply_openings: bool = True,
) -> None:
"""Function can switch to representation that wasn't yet assigned to that object. See #2766.
`should_sync_changes_first` - sync ifc representation with current state of `obj.data`;
+21 -11
View File
@@ -16,8 +16,18 @@
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
def copy_class(ifc, collector, geometry, root, obj=None):
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def copy_class(
ifc: tool.Ifc, collector: tool.Collector, geometry: tool.Geometry, root: tool.Root, obj: bpy.types.Object
) -> ifcopenshell.entity_instance:
element = ifc.get_entity(obj)
if not element:
return
@@ -48,16 +58,16 @@ def copy_class(ifc, collector, geometry, root, obj=None):
def assign_class(
ifc,
collector,
root,
obj=None,
ifc_class=None,
predefined_type=None,
should_add_representation=True,
context=None,
ifc_representation_class=None,
):
ifc: tool.Ifc,
collector: tool.Collector,
root: tool.Root,
obj: bpy.types.Object,
ifc_class: str,
context: ifcopenshell.entity_instance,
predefined_type: Optional[str] = None,
should_add_representation: bool = True,
ifc_representation_class: Optional[str] = None,
) -> ifcopenshell.entity_instance:
if ifc.get_entity(obj):
return
+1 -1
View File
@@ -18,11 +18,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
import blenderbim.core.tool as tool
if TYPE_CHECKING:
import bpy
import ifcopenshell
import blenderbim.tool as tool
def reference_structure(
+20 -7
View File
@@ -24,8 +24,10 @@ import logging
import numpy as np
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.system
import blenderbim.core.tool
import blenderbim.core.drawing
@@ -319,7 +321,7 @@ class Geometry(blenderbim.core.tool.Geometry):
return new_mesh
@classmethod
def get_active_representation(cls, obj):
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
"""< IfcShapeRepresentation or None"""
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
@@ -459,7 +461,9 @@ class Geometry(blenderbim.core.tool.Geometry):
return f"{representation.ContextOfItems.id()}/{representation.id()}"
@classmethod
def get_styles(cls, obj, only_assigned_to_faces=False):
def get_styles(
cls, obj: bpy.types.Object, only_assigned_to_faces: bool = False
) -> list[Union[ifcopenshell.entity_instance, None]]:
styles = [tool.Style.get_style(s.material) for s in obj.material_slots if s.material]
if not only_assigned_to_faces:
return styles
@@ -467,8 +471,15 @@ class Geometry(blenderbim.core.tool.Geometry):
usage_count = [0] * len(obj.material_slots)
if not usage_count: # if there are no materials, polygons will still use index 0
return []
for poly in obj.data.polygons:
usage_count[poly.material_index] += 1
# remove usages for empty material slots
for i, slot in reversed(list(enumerate(obj.material_slots))):
if not slot.material:
del usage_count[i]
styles = [style for style, usage in zip(styles, usage_count, strict=True) if usage > 0]
return styles
@@ -560,11 +571,11 @@ class Geometry(blenderbim.core.tool.Geometry):
new.value = element[i]
@classmethod
def is_body_representation(cls, representation):
def is_body_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.ContextOfItems.ContextIdentifier == "Body"
@classmethod
def is_box_representation(cls, representation):
def is_box_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.ContextOfItems.ContextIdentifier == "Box"
@classmethod
@@ -572,11 +583,11 @@ class Geometry(blenderbim.core.tool.Geometry):
return not all([tool.Cad.is_x(o, 1.0) for o in obj.scale]) or obj in IfcStore.edited_objs
@classmethod
def is_mapped_representation(cls, representation):
def is_mapped_representation(cls, representation: ifcopenshell.entity_instance) -> bool:
return representation.RepresentationType == "MappedRepresentation"
@classmethod
def is_meshlike(cls, representation):
def is_meshlike(cls, representation: ifcopenshell.entity_instance) -> bool:
if ifcopenshell.util.representation.resolve_representation(representation).RepresentationType in (
"AdvancedBrep",
"Annotation2D",
@@ -656,7 +667,9 @@ class Geometry(blenderbim.core.tool.Geometry):
bpy.data.objects.remove(obj)
@classmethod
def resolve_mapped_representation(cls, representation):
def resolve_mapped_representation(
cls, representation: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
if representation.RepresentationType == "MappedRepresentation":
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
return representation
+3 -1
View File
@@ -97,7 +97,9 @@ class Misc(blenderbim.core.tool.Misc):
IfcStore.edited_objs.add(obj)
@classmethod
def split_objects_with_cutter(cls, objs, cutter):
def split_objects_with_cutter(
cls, objs: list[bpy.types.Object], cutter: bpy.types.Object
) -> list[bpy.types.Object]:
cutter_mesh = cutter.data
bm = bmesh.new()
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell
import ifcopenshell.util.unit
import blenderbim.core.tool
import blenderbim.core.root
import blenderbim.bim.schema
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
+8 -3
View File
@@ -29,6 +29,7 @@ import blenderbim.core.style
import blenderbim.tool as tool
from mathutils import Vector
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
from typing import Union, Optional
class Root(blenderbim.core.tool.Root):
@@ -129,7 +130,7 @@ class Root(blenderbim.core.tool.Root):
return ifcopenshell.util.representation.get_representation(element, context=context.ContextType)
@classmethod
def get_element_type(cls, element):
def get_element_type(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
return ifcopenshell.util.element.get_type(element)
@classmethod
@@ -282,8 +283,12 @@ class Root(blenderbim.core.tool.Root):
@classmethod
def run_geometry_add_representation(
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
):
cls,
obj: bpy.types.Object,
context: ifcopenshell.entity_instance,
ifc_representation_class: Optional[str] = None,
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
) -> ifcopenshell.entity_instance:
return blenderbim.core.geometry.add_representation(
tool.Ifc,
tool.Geometry,
@@ -105,6 +105,7 @@ For Linux or Mac:
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -117,6 +118,7 @@ For Linux or Mac:
$ ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
$ ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
$ ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
$ ln -s $PWD/src/bcf/src/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
$ ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
$ ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
$ ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
@@ -172,6 +174,7 @@ Before running it follow the instructions descibed after `rem` tags.
del "%blenderbim%\libs\site\packages\ifccsv.py"
del "%blenderbim%\libs\site\packages\ifcdiff.py"
del "%blenderbim%\libs\site\packages\bsdd.py"
rd /S /Q "%blenderbim%\libs\site\packages\bcf"
rd /S /Q "%blenderbim%\libs\site\packages\ifc4d"
rd /S /Q "%blenderbim%\libs\site\packages\ifc5d"
rd /S /Q "%blenderbim%\libs\site\packages\ifccityjson"
@@ -184,6 +187,7 @@ Before running it follow the instructions descibed after `rem` tags.
mklink "%blenderbim%\libs\site\packages\ifccsv.py" "%cd%\src\ifccsv\ifccsv.py"
mklink "%blenderbim%\libs\site\packages\ifcdiff.py" "%cd%\src\ifcdiff\ifcdiff.py"
mklink "%blenderbim%\libs\site\packages\bsdd.py" "%cd%\src\bsdd\bsdd.py"
mklink /D "%blenderbim%\libs\site\packages\bcf" "%cd%\src\bcf\src\bcf"
mklink /D "%blenderbim%\libs\site\packages\ifc4d" "%cd%\src\ifc4d\ifc4d"
mklink /D "%blenderbim%\libs\site\packages\ifc5d" "%cd%\src\ifc5d\ifc5d"
mklink /D "%blenderbim%\libs\site\packages\ifccityjson" "%cd%\src\ifccityjson\ifccityjson"
@@ -19,6 +19,7 @@
import bpy
import math
import ifcopenshell
import ifcopenshell.api
import blenderbim.core.tool
import blenderbim.tool as tool
from mathutils import Vector
@@ -59,7 +59,6 @@
#include <GeomAPI_ProjectPointOnSurf.hxx>
#include <Geom_Plane.hxx>
#include <IntTools_FaceFace.hxx>
#include <STEPConstruct_PointHasher.hxx>
#include "clash_utils.h"
#ifdef WITH_HDF5
@@ -17,12 +17,12 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Optional
from typing import Optional, Literal
def add_context(
file: ifcopenshell.file,
context_type: str,
context_type: Optional[Literal["Model", "Plan"]] = None,
context_identifier: Optional[str] = None,
target_view: Optional[str] = None,
parent: Optional[ifcopenshell.entity_instance] = None,
@@ -96,7 +96,7 @@ def add_context(
:param context_type: The type of the context, must be one of "Model" or
"Plan" only.
:type context_type: str
:type context_type: str, optional
:param context_identifier: The identifier of the context, chosen from
one of the common identifiers above or consult the IFC documentation
(under the IfcShapeRepresentation page) for more details. Optional
@@ -21,7 +21,7 @@ import math
import bmesh
import ifcopenshell.util.unit
from mathutils import Vector, Matrix
from typing import Union, Optional, Literal
from typing import Union, Optional, Literal, Any
Z_AXIS = Vector((0, 0, 1))
@@ -93,6 +93,9 @@ def add_representation(
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
self.is_manifold = None
if (
@@ -501,11 +504,10 @@ class Usecase:
geom_data = self.settings["geometry"]
if isinstance(geom_data, bpy.types.Mesh):
if not self.is_mesh_curve_consecutive(geom_data):
return
if self.file.schema == "IFC2X3":
return self.create_curves_from_mesh_ifc2x3(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
if self.is_mesh_curve_consecutive(geom_data):
if self.file.schema == "IFC2X3":
return self.create_curves_from_mesh_ifc2x3(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
import blenderbim.tool as tool
@@ -16,8 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
from typing import Union
def get_primitive_type(attribute_or_data_type):
def get_primitive_type(
attribute_or_data_type: Union[ifcopenshell_wrapper.attribute, ifcopenshell_wrapper.parameter_type]
) -> Union[str, tuple[str, list[str]]]:
if hasattr(attribute_or_data_type, "type_of_attribute"):
data_type = str(attribute_or_data_type.type_of_attribute())
else:
@@ -446,13 +446,13 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> str:
return predefined_type
def get_type(element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
def get_type(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""Retrieves the construction type element of an element occurrence
:param element: The element occurrence
:type: ifcopenshell.entity_instance
:return: The related type element
:rtype: ifcopenshell.entity_instance
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -21,6 +21,7 @@ import lark
import numpy as np
import ifcopenshell.api
import ifcopenshell.util
import ifcopenshell.util.attribute
import ifcopenshell.util.fm
import ifcopenshell.util.unit
import ifcopenshell.util.element
@@ -30,7 +31,7 @@ import ifcopenshell.util.classification
import ifcopenshell.util.schema
import ifcopenshell.util.shape
from decimal import Decimal
from typing import Optional, Any, Union
from typing import Optional, Any, Union, Iterable
filter_elements_grammar = lark.Lark(
@@ -324,12 +325,16 @@ def filter_elements(
return transformer.get_results()
class SetElementValueException(Exception): ...
def set_element_value(
ifc_file: ifcopenshell.file,
element: ifcopenshell.entity_instance,
element: Union[ifcopenshell.entity_instance, Iterable[ifcopenshell.entity_instance], None],
query: Union[str, list[str]],
value: Optional[str],
) -> Union[ifcopenshell.entity_instance, None]:
value: Any,
) -> None:
original_element = element
if isinstance(query, (list, tuple)):
keys = query
else:
@@ -392,12 +397,21 @@ def set_element_value(
ifcopenshell.api.run(
"geometry.edit_object_placement", ifc_file, product=element, matrix=matrix, is_si=False
)
return
elif isinstance(element, ifcopenshell.entity_instance):
if key == "Name" and element.is_a("IfcMaterialLayerSet"):
key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it.
if isinstance(key, str) and hasattr(element, key):
if getattr(element, key) != value:
if isinstance(key, str) and ((current_value := getattr(element, key, ...)) is not ...):
# check if key is not last
if len(keys) != i + 1:
element = current_value
continue
if current_value == value:
return
else:
# check if key is not last
try:
# Try our luck
return setattr(element, key, value)
@@ -418,9 +432,13 @@ def set_element_value(
if value in ("True", "true", "TRUE", "Yes", "1"):
value = True
elif value in ("False", "false", "FALSE", "No", "0"):
value = True
value = False
else:
value = bool(value)
elif data_type == "entity":
value = ifc_file.by_guid(value)
if current_value == value:
return
return setattr(element, key, value)
else:
# Try to extract pset
@@ -463,18 +481,20 @@ def set_element_value(
except:
pass
return
elif isinstance(element, (list, tuple)): # If we use regex
elif isinstance(element, (list, tuple, set)): # If we use regex
if key.isnumeric():
try:
element = element[int(key)]
except IndexError:
return
else:
results = []
for v in element:
cls.set_element_value(ifc_file, v, keys[i + 1 :], value)
set_element_value(ifc_file, v, keys[i:], value)
return
raise SetElementValueException(
f"Failed to set value for element '{original_element}' with query '{query}' (invalid or unsupported query)."
)
class FacetTransformer(lark.Transformer):
def __init__(self, ifc_file: ifcopenshell.file, elements: Optional[set[ifcopenshell.entity_instance]] = None):
@@ -311,6 +311,13 @@ class TestSetElementValue(test.bootstrap.IFC4):
subject.set_element_value(self.file, element, "Name", 123)
assert element.Name == "123"
def test_set_attributes_attribute(self):
material = self.file.create_entity("IfcMaterial")
layer = self.file.create_entity("IfcMaterialLayer")
layer.Material = material
subject.set_element_value(self.file, layer, "Material.Name", "Foo")
assert material.Name == "Foo"
class TestSelector(test.bootstrap.IFC4):
def test_selecting_from_specified_elements(self):
@@ -16,9 +16,29 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np
import numpy.typing as npt
import ifcopenshell
from typing import Literal, Optional, Union
class Patcher:
def __init__(self, src, file, logger, mode="geometry", a=None, b=None, c=None, d=None):
def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: logging.Logger,
mode: Literal[
"geometry",
"placement",
"both",
] = "geometry",
a: Optional[float] = None,
b: Optional[float] = None,
c: Optional[float] = None,
d: Optional[float] = None,
):
"""Reset any large coordinates to smaller coordinates based on a threshold
If you find large coordinates in your model, the large coordinates may
@@ -151,7 +171,7 @@ class Patcher:
point.Coordinates[2] + offset_point[2],
)
def is_point_far_away(self, point):
def is_point_far_away(self, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]]) -> bool:
if hasattr(point, "Coordinates"):
return (
abs(point.Coordinates[0]) > self.threshold
+2
View File
@@ -18,6 +18,8 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.representation
from ifcopenshell import template
@@ -18,6 +18,7 @@
import bpy
import ifcopenshell
import ifcopenshell.api
import ifcsverchok.helper
from sverchok.node_tree import SverchCustomTreeNode
@@ -21,6 +21,7 @@ import bpy
import ifcopenshell
import ifcsverchok.helper
import ifcopenshell.api
import ifcopenshell.util.representation
from ifcsverchok.ifcstore import SvIfcStore
import blenderbim.tool as tool
import blenderbim.core.geometry as core