mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
typing
This commit is contained in:
@@ -55,10 +55,10 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
|
|||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(obj, bpy.types.Material):
|
if isinstance(obj, bpy.types.Material):
|
||||||
props = obj.BIMStyleProperties
|
props = tool.Style.get_material_style_props(obj)
|
||||||
if ifc_definition_id := props.ifc_definition_id:
|
if ifc_definition_id := props.ifc_definition_id:
|
||||||
if props.is_renaming:
|
if props.is_renaming:
|
||||||
props.is_renmaing = False
|
props.is_renaming = False
|
||||||
return
|
return
|
||||||
tool.Ifc.get().by_id(ifc_definition_id).Name = obj.name
|
tool.Ifc.get().by_id(ifc_definition_id).Name = obj.name
|
||||||
refresh_ui_data()
|
refresh_ui_data()
|
||||||
|
|||||||
@@ -265,7 +265,8 @@ class IfcStore:
|
|||||||
IfcStore.guid_map[global_id] = obj
|
IfcStore.guid_map[global_id] = obj
|
||||||
|
|
||||||
if element.is_a("IfcSurfaceStyle"):
|
if element.is_a("IfcSurfaceStyle"):
|
||||||
obj.BIMStyleProperties.ifc_definition_id = element.id()
|
props = tool.Style.get_material_style_props(obj)
|
||||||
|
props.ifc_definition_id = element.id()
|
||||||
else:
|
else:
|
||||||
props = tool.Blender.get_object_bim_props(obj)
|
props = tool.Blender.get_object_bim_props(obj)
|
||||||
props.ifc_definition_id = element.id()
|
props.ifc_definition_id = element.id()
|
||||||
@@ -406,7 +407,8 @@ class IfcStore:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None:
|
def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None:
|
||||||
if isinstance(obj, bpy.types.Material):
|
if isinstance(obj, bpy.types.Material):
|
||||||
obj.BIMStyleProperties.ifc_definition_id = 0
|
props = tool.Style.get_material_style_props(obj)
|
||||||
|
props.ifc_definition_id = 0
|
||||||
else: # bpy.types.Object
|
else: # bpy.types.Object
|
||||||
props = tool.Blender.get_object_bim_props(obj)
|
props = tool.Blender.get_object_bim_props(obj)
|
||||||
props.ifc_definition_id = 0
|
props.ifc_definition_id = 0
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class MaterialCreator:
|
|||||||
|
|
||||||
def load_existing_materials(self) -> None:
|
def load_existing_materials(self) -> None:
|
||||||
for material in bpy.data.materials:
|
for material in bpy.data.materials:
|
||||||
if ifc_definition_id := material.BIMStyleProperties.ifc_definition_id:
|
if ifc_definition_id := tool.Blender.get_ifc_definition_id(material):
|
||||||
self.styles[ifc_definition_id] = material
|
self.styles[ifc_definition_id] = material
|
||||||
|
|
||||||
def parse_element_type_material_styles(self, element: ifcopenshell.entity_instance) -> None:
|
def parse_element_type_material_styles(self, element: ifcopenshell.entity_instance) -> None:
|
||||||
@@ -964,10 +964,11 @@ class IfcImporter:
|
|||||||
self.material_creator.styles[style.id()] = blender_material
|
self.material_creator.styles[style.id()] = blender_material
|
||||||
|
|
||||||
style_elements = tool.Style.get_style_elements(blender_material)
|
style_elements = tool.Style.get_style_elements(blender_material)
|
||||||
|
props = tool.Style.get_material_style_props(blender_material)
|
||||||
if tool.Style.has_blender_external_style(style_elements):
|
if tool.Style.has_blender_external_style(style_elements):
|
||||||
blender_material.BIMStyleProperties.active_style_type = "External"
|
props.active_style_type = "External"
|
||||||
else:
|
else:
|
||||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
props.active_style_type = "Shading"
|
||||||
|
|
||||||
def place_objects_in_collections(self) -> None:
|
def place_objects_in_collections(self) -> None:
|
||||||
for ifc_definition_id, obj in self.added_data.items():
|
for ifc_definition_id, obj in self.added_data.items():
|
||||||
|
|||||||
@@ -3353,7 +3353,8 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
obj.material_slots[0].material = material
|
obj.material_slots[0].material = material
|
||||||
bpy.ops.bim.add_style()
|
bpy.ops.bim.add_style()
|
||||||
|
|
||||||
style = ifc_file.by_id(material.BIMStyleProperties.ifc_definition_id)
|
style = tool.Ifc.get_entity(material)
|
||||||
|
assert style
|
||||||
tool.Style.assign_style_to_object(style, obj)
|
tool.Style.assign_style_to_object(style, obj)
|
||||||
|
|
||||||
# TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
|
# TODO: IfcSurfaceStyleRendering is unnecessary here, added it only because
|
||||||
|
|||||||
@@ -1019,7 +1019,7 @@ class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
position = Matrix()
|
position = Matrix()
|
||||||
|
|
||||||
direction = Vector(extrusion.ExtrudedDirection.DirectionRatios).normalized()
|
direction = Vector(extrusion.ExtrudedDirection.DirectionRatios).normalized()
|
||||||
tool.Model.import_axis([Vector((0, 0, 0)), direction * extrusion.Depth], obj=obj, position=position)
|
tool.Model.import_axis((Vector((0, 0, 0)), direction * extrusion.Depth), obj=obj, position=position)
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode="EDIT")
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_axis(context))
|
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_axis(context))
|
||||||
|
|||||||
@@ -97,9 +97,8 @@ class BlenderMaterialStyleData:
|
|||||||
material = obj.active_material
|
material = obj.active_material
|
||||||
if not material:
|
if not material:
|
||||||
return False
|
return False
|
||||||
props = material.BIMStyleProperties
|
|
||||||
style_id = props.ifc_definition_id
|
style = tool.Ifc.get_entity(material)
|
||||||
style = tool.Ifc.get_entity_by_id(style_id)
|
|
||||||
if not style:
|
if not style:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -118,8 +118,7 @@ class UnlinkStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
# Don't check blender_material and style_id as this operator is only called from UI.
|
# Don't check blender_material and style_id as this operator is only called from UI.
|
||||||
assert isinstance(self.blender_material, str) # Type checker.
|
assert isinstance(self.blender_material, str) # Type checker.
|
||||||
material = bpy.data.materials[self.blender_material]
|
material = bpy.data.materials[self.blender_material]
|
||||||
style_id = material.BIMStyleProperties.ifc_definition_id
|
style = tool.Ifc.get_entity(material)
|
||||||
style = tool.Ifc.get_entity_by_id(style_id)
|
|
||||||
|
|
||||||
# Material is linked to a style from a different project.
|
# Material is linked to a style from a different project.
|
||||||
if not style or tool.Ifc.get_object(style) != material:
|
if not style or tool.Ifc.get_object(style) != material:
|
||||||
@@ -221,18 +220,25 @@ class UpdateCurrentStyle(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
style = tool.Ifc.get().by_id(self.style_id)
|
style = tool.Ifc.get().by_id(self.style_id)
|
||||||
material = tool.Ifc.get_object(style)
|
material = tool.Ifc.get_object(style)
|
||||||
current_style_type = material.BIMStyleProperties.active_style_type
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
|
current_style_type = msprops.active_style_type
|
||||||
|
|
||||||
if self.update_all:
|
if self.update_all:
|
||||||
sprops = tool.Style.get_style_props()
|
sprops = tool.Style.get_style_props()
|
||||||
sprops.active_style_type = current_style_type
|
sprops.active_style_type = current_style_type
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
updated_materials = set()
|
updated_materials: set[bpy.types.Material] = set()
|
||||||
for obj in context.selected_objects:
|
for obj in context.selected_objects:
|
||||||
|
if not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve)):
|
||||||
|
continue
|
||||||
for mat in obj.data.materials:
|
for mat in obj.data.materials:
|
||||||
if mat and mat not in updated_materials and mat.BIMStyleProperties.ifc_definition_id != 0:
|
if (
|
||||||
mat.BIMStyleProperties.active_style_type = current_style_type
|
mat
|
||||||
|
and mat not in updated_materials
|
||||||
|
and (msprops_ := tool.Style.get_material_style_props(mat)).ifc_definition_id != 0
|
||||||
|
):
|
||||||
|
msprops_.active_style_type = current_style_type
|
||||||
updated_materials.add(mat)
|
updated_materials.add(mat)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
@@ -755,7 +761,8 @@ class EnableEditingSurfaceStyle(bpy.types.Operator):
|
|||||||
bonsai.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes, callback)
|
bonsai.bim.helper.import_attributes2(surface_style or self.ifc_class, attributes, callback)
|
||||||
|
|
||||||
material = tool.Ifc.get_object(style)
|
material = tool.Ifc.get_object(style)
|
||||||
active_style_type = material.BIMStyleProperties.active_style_type
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
|
active_style_type = msprops.active_style_type
|
||||||
if self.ifc_class == "IfcExternallyDefinedSurfaceStyle" and active_style_type != "External":
|
if self.ifc_class == "IfcExternallyDefinedSurfaceStyle" and active_style_type != "External":
|
||||||
if tool.Style.has_blender_external_style(style_elements):
|
if tool.Style.has_blender_external_style(style_elements):
|
||||||
tool.Style.switch_shading(material, "External")
|
tool.Style.switch_shading(material, "External")
|
||||||
@@ -803,9 +810,10 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
# restore selected style type
|
# restore selected style type
|
||||||
material = tool.Ifc.get_object(self.style)
|
material = tool.Ifc.get_object(self.style)
|
||||||
material.BIMStyleProperties.active_style_type = material.BIMStyleProperties.active_style_type
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
|
msprops.active_style_type = msprops.active_style_type
|
||||||
|
|
||||||
def edit_existing_style(self):
|
def edit_existing_style(self) -> None:
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
material = tool.Ifc.get_object(self.style)
|
material = tool.Ifc.get_object(self.style)
|
||||||
assert self.surface_style
|
assert self.surface_style
|
||||||
@@ -867,7 +875,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
attributes=bonsai.bim.helper.export_attributes(attributes),
|
attributes=bonsai.bim.helper.export_attributes(attributes),
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_new_style(self):
|
def add_new_style(self) -> None:
|
||||||
material = tool.Ifc.get_object(self.style)
|
material = tool.Ifc.get_object(self.style)
|
||||||
if self.props.is_editing_class == "IfcSurfaceStyleShading":
|
if self.props.is_editing_class == "IfcSurfaceStyleShading":
|
||||||
surface_style = ifcopenshell.api.run(
|
surface_style = ifcopenshell.api.run(
|
||||||
@@ -911,13 +919,13 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
attributes=bonsai.bim.helper.export_attributes(attributes),
|
attributes=bonsai.bim.helper.export_attributes(attributes),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_shading_attributes(self):
|
def get_shading_attributes(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"SurfaceColour": self.color_to_dict(self.props.surface_colour),
|
"SurfaceColour": self.color_to_dict(self.props.surface_colour),
|
||||||
"Transparency": self.props.transparency or None,
|
"Transparency": self.props.transparency or None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_rendering_attributes(self):
|
def get_rendering_attributes(self) -> dict[str, Any]:
|
||||||
if self.props.is_diffuse_colour_null:
|
if self.props.is_diffuse_colour_null:
|
||||||
diffuse_colour = None
|
diffuse_colour = None
|
||||||
elif self.props.diffuse_colour_class == "IfcColourRgb":
|
elif self.props.diffuse_colour_class == "IfcColourRgb":
|
||||||
@@ -961,7 +969,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
textures.append(texture_data)
|
textures.append(texture_data)
|
||||||
return textures
|
return textures
|
||||||
|
|
||||||
def color_to_dict(self, x):
|
def color_to_dict(self, x: tuple[float, float, float]) -> dict[str, Any]:
|
||||||
return {"Red": x[0], "Green": x[1], "Blue": x[2]}
|
return {"Red": x[0], "Green": x[1], "Blue": x[2]}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ from typing import Literal, Union, TYPE_CHECKING, get_args
|
|||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
|
||||||
def get_style_types(self, context):
|
def get_style_types(self: "BIMStylesProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||||
if not StylesData.is_loaded:
|
if not StylesData.is_loaded:
|
||||||
StylesData.load()
|
StylesData.load()
|
||||||
return StylesData.data["style_types"]
|
return StylesData.data["style_types"]
|
||||||
|
|
||||||
|
|
||||||
def get_reflectance_methods(self, context):
|
def get_reflectance_methods(self: "BIMStylesProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||||
if not StylesData.is_loaded:
|
if not StylesData.is_loaded:
|
||||||
StylesData.load()
|
StylesData.load()
|
||||||
return StylesData.data["reflectance_methods"]
|
return StylesData.data["reflectance_methods"]
|
||||||
@@ -77,6 +77,16 @@ class Style(PropertyGroup):
|
|||||||
type=bpy.types.Material,
|
type=bpy.types.Material,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
ifc_definition_id: int
|
||||||
|
total_elements: int
|
||||||
|
style_classes: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||||
|
has_surface_colour: bool
|
||||||
|
surface_colour: tuple[float, float, float]
|
||||||
|
has_diffuse_colour: bool
|
||||||
|
diffuse_colour: tuple[float, float, float]
|
||||||
|
blender_material: Union[bpy.types.Material, None]
|
||||||
|
|
||||||
|
|
||||||
STYLE_TYPES = [
|
STYLE_TYPES = [
|
||||||
("Shading", "Shading", ""),
|
("Shading", "Shading", ""),
|
||||||
@@ -86,7 +96,7 @@ STYLE_TYPES = [
|
|||||||
|
|
||||||
def update_shading_styles(self: "BIMStylesProperties", context: bpy.types.Context) -> None:
|
def update_shading_styles(self: "BIMStylesProperties", context: bpy.types.Context) -> None:
|
||||||
for mat in bpy.data.materials:
|
for mat in bpy.data.materials:
|
||||||
if mat.BIMStyleProperties.ifc_definition_id == 0:
|
if tool.Blender.get_ifc_definition_id(mat) == 0:
|
||||||
continue
|
continue
|
||||||
tool.Style.change_current_style_type(mat, self.active_style_type)
|
tool.Style.change_current_style_type(mat, self.active_style_type)
|
||||||
|
|
||||||
@@ -111,7 +121,9 @@ UV_MODES = [
|
|||||||
("Camera", "Camera", _("UV from position coordinate in camera space")),
|
("Camera", "Camera", _("UV from position coordinate in camera space")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TextureMapMode = Literal[
|
||||||
|
"DIFFUSE", "NORMAL", "METALLICROUGHNESS", "SPECULAR", "SHININESS", "EMISSIVE", "OCCLUSION", "AMBIENT"
|
||||||
|
]
|
||||||
TEXTURE_MAPS_MODS = (
|
TEXTURE_MAPS_MODS = (
|
||||||
("DIFFUSE", "DIFFUSE", ""),
|
("DIFFUSE", "DIFFUSE", ""),
|
||||||
("NORMAL", "NORMAL", ""),
|
("NORMAL", "NORMAL", ""),
|
||||||
@@ -129,6 +141,10 @@ class Texture(PropertyGroup):
|
|||||||
# NOTE: subtype `FILE_PATH` is not used to avoid .blend relative paths
|
# NOTE: subtype `FILE_PATH` is not used to avoid .blend relative paths
|
||||||
path: StringProperty(name="Texture Path", update=update_shader_graph)
|
path: StringProperty(name="Texture Path", update=update_shader_graph)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
mode: TextureMapMode
|
||||||
|
path: str
|
||||||
|
|
||||||
|
|
||||||
class ColourRgb(PropertyGroup):
|
class ColourRgb(PropertyGroup):
|
||||||
name: StringProperty()
|
name: StringProperty()
|
||||||
@@ -136,6 +152,10 @@ class ColourRgb(PropertyGroup):
|
|||||||
# not exposed in the UI, here just to preserve the data
|
# not exposed in the UI, here just to preserve the data
|
||||||
color_name: StringProperty(name="Color Name")
|
color_name: StringProperty(name="Color Name")
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
color_value: tuple[float, float, float]
|
||||||
|
color_name: str
|
||||||
|
|
||||||
# to fit blender.bim.helper.export_attributes
|
# to fit blender.bim.helper.export_attributes
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -16,11 +16,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
import bpy
|
import bpy
|
||||||
import bonsai.bim.helper
|
import bonsai.bim.helper
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bpy.types import Panel, UIList
|
from bpy.types import Panel, UIList
|
||||||
from bonsai.bim.module.style.data import StylesData, BlenderMaterialStyleData
|
from bonsai.bim.module.style.data import StylesData, BlenderMaterialStyleData
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from bonsai.bim.module.style.prop import BIMStylesProperties, Style
|
||||||
|
|
||||||
|
|
||||||
class BIM_PT_styles(Panel):
|
class BIM_PT_styles(Panel):
|
||||||
@@ -94,7 +99,8 @@ class BIM_PT_styles(Panel):
|
|||||||
if active_style:
|
if active_style:
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
if material := style.blender_material:
|
if material := style.blender_material:
|
||||||
row.prop(material.BIMStyleProperties, "active_style_type", icon="SHADING_RENDERED", text="")
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
|
row.prop(msprops, "active_style_type", icon="SHADING_RENDERED", text="")
|
||||||
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
op = row.operator("bim.update_current_style", icon="FILE_REFRESH", text="")
|
||||||
op.style_id = style.ifc_definition_id
|
op.style_id = style.ifc_definition_id
|
||||||
|
|
||||||
@@ -255,11 +261,19 @@ class BIM_PT_styles(Panel):
|
|||||||
|
|
||||||
|
|
||||||
class BIM_UL_styles(UIList):
|
class BIM_UL_styles(UIList):
|
||||||
def draw_item(self, context, layout: bpy.types.UILayout, data, item, icon, active_data, active_property):
|
def draw_item(
|
||||||
|
self,
|
||||||
|
context,
|
||||||
|
layout: bpy.types.UILayout,
|
||||||
|
data: BIMStylesProperties,
|
||||||
|
item: Style,
|
||||||
|
icon,
|
||||||
|
active_data,
|
||||||
|
active_property,
|
||||||
|
):
|
||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
props = tool.Style.get_style_props()
|
if item.ifc_definition_id == data.is_editing_style:
|
||||||
if item.ifc_definition_id == props.is_editing_style:
|
|
||||||
row.label(text="", icon="GREASEPENCIL")
|
row.label(text="", icon="GREASEPENCIL")
|
||||||
row.prop(item, "name", text="", emboss=False)
|
row.prop(item, "name", text="", emboss=False)
|
||||||
if item.has_surface_colour:
|
if item.has_surface_colour:
|
||||||
@@ -295,7 +309,7 @@ class BIM_PT_style(Panel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return bool(tool.Ifc.get() and (material := context.material) and material.BIMStyleProperties.ifc_definition_id)
|
return bool(tool.Ifc.get() and (material := context.material) and tool.Blender.get_ifc_definition_id(material))
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
# NOTE: this UI is needed only to indicate whether blender material is linked to IFC
|
# NOTE: this UI is needed only to indicate whether blender material is linked to IFC
|
||||||
@@ -305,10 +319,11 @@ class BIM_PT_style(Panel):
|
|||||||
BlenderMaterialStyleData.load()
|
BlenderMaterialStyleData.load()
|
||||||
|
|
||||||
material = context.material
|
material = context.material
|
||||||
style_id = material.BIMStyleProperties.ifc_definition_id
|
assert material
|
||||||
|
style_id = tool.Blender.get_ifc_definition_id(material)
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
|
|
||||||
if style_id and not BlenderMaterialStyleData.data["is_linked_to_style"]:
|
if not BlenderMaterialStyleData.data["is_linked_to_style"]:
|
||||||
row.label(text="Material has linked IFC from a different project.")
|
row.label(text="Material has linked IFC from a different project.")
|
||||||
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
op = row.operator("bim.unlink_style", icon="UNLINKED", text="")
|
||||||
op.blender_material = material.name
|
op.blender_material = material.name
|
||||||
|
|||||||
@@ -1601,5 +1601,7 @@ class Blender(bonsai.core.tool.Blender):
|
|||||||
return obj.BIMObjectProperties
|
return obj.BIMObjectProperties
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_ifc_definition_id(cls, obj: bpy.types.Object) -> int:
|
def get_ifc_definition_id(cls, obj: IFC_CONNECTED_TYPE) -> int:
|
||||||
return tool.Blender.get_object_bim_props(obj).ifc_definition_id
|
if isinstance(obj, bpy.types.Object):
|
||||||
|
return tool.Blender.get_object_bim_props(obj).ifc_definition_id
|
||||||
|
return tool.Style.get_material_style_props(obj).ifc_definition_id
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_object_materials_without_styles(cls, obj: bpy.types.Object) -> list[bpy.types.Material]:
|
def get_object_materials_without_styles(cls, obj: bpy.types.Object) -> list[bpy.types.Material]:
|
||||||
return [
|
return [
|
||||||
s.material for s in obj.material_slots if s.material and not s.material.BIMStyleProperties.ifc_definition_id
|
s.material for s in obj.material_slots if s.material and not tool.Blender.get_ifc_definition_id(s.material)
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Ifc(bonsai.core.tool.Ifc):
|
|||||||
if isinstance(obj, bpy.types.Object):
|
if isinstance(obj, bpy.types.Object):
|
||||||
props = tool.Blender.get_object_bim_props(obj)
|
props = tool.Blender.get_object_bim_props(obj)
|
||||||
elif isinstance(obj, bpy.types.Material):
|
elif isinstance(obj, bpy.types.Material):
|
||||||
props = obj.BIMStyleProperties
|
props = tool.Style.get_material_style_props(obj)
|
||||||
else:
|
else:
|
||||||
props = tool.Geometry.get_mesh_props(obj)
|
props = tool.Geometry.get_mesh_props(obj)
|
||||||
|
|
||||||
|
|||||||
@@ -293,8 +293,19 @@ class Model(bonsai.core.tool.Model):
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
unit_scale: float
|
||||||
|
vertices: list[Vector]
|
||||||
|
edges: list[Sequence[int]]
|
||||||
|
arcs: list[Sequence[int]]
|
||||||
|
circles: list[Sequence[int]]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def import_axis(cls, axis, obj=None, position=None):
|
def import_axis(
|
||||||
|
cls,
|
||||||
|
axis: Union[ifcopenshell.entity_instance, tuple[Vector, Vector]],
|
||||||
|
obj=None,
|
||||||
|
position: Optional[Matrix] = None,
|
||||||
|
) -> bpy.types.Object:
|
||||||
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
|
||||||
if position is None:
|
if position is None:
|
||||||
@@ -305,7 +316,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
cls.arcs = []
|
cls.arcs = []
|
||||||
cls.circles = []
|
cls.circles = []
|
||||||
|
|
||||||
if isinstance(axis, list):
|
if isinstance(axis, tuple):
|
||||||
cls.vertices.extend(
|
cls.vertices.extend(
|
||||||
[
|
[
|
||||||
position @ Vector(cls.convert_unit_to_si(axis[0])).to_3d(),
|
position @ Vector(cls.convert_unit_to_si(axis[0])).to_3d(),
|
||||||
@@ -478,7 +489,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def convert_curve_to_mesh(
|
def convert_curve_to_mesh(
|
||||||
cls,
|
cls,
|
||||||
obj: bpy.types.Object,
|
obj: Union[bpy.types.Object, None], # Unused argument.
|
||||||
position: Matrix,
|
position: Matrix,
|
||||||
curve: ifcopenshell.entity_instance,
|
curve: ifcopenshell.entity_instance,
|
||||||
x_angle: Optional[float] = None,
|
x_angle: Optional[float] = None,
|
||||||
@@ -1624,7 +1635,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
loop_edges = list(bm.edges)
|
loop_edges = list(bm.edges)
|
||||||
|
|
||||||
# Create loops from edges
|
# Create loops from edges
|
||||||
loops = []
|
loops: list[list[bmesh.types.BMEdge]] = []
|
||||||
while loop_edges:
|
while loop_edges:
|
||||||
edge = loop_edges.pop()
|
edge = loop_edges.pop()
|
||||||
loop = [edge]
|
loop = [edge]
|
||||||
@@ -1645,19 +1656,19 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
tmp = ifcopenshell.file(schema=tool.Ifc.get().schema)
|
tmp = ifcopenshell.file(schema=tool.Ifc.get().schema)
|
||||||
|
|
||||||
def is_in_group(v, group_name):
|
def is_in_group(v: bmesh.types.BMVert, group_name: str) -> bool:
|
||||||
for group_index in groups[group_name]:
|
for group_index in groups[group_name]:
|
||||||
if group_index in v[deform_layer]:
|
if group_index in v[deform_layer]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_group_index(v, group_name):
|
def get_group_index(v: bmesh.types.BMVert, group_name: str) -> Union[int, None]:
|
||||||
for group_index in groups[group_name]:
|
for group_index in groups[group_name]:
|
||||||
if group_index in v[deform_layer]:
|
if group_index in v[deform_layer]:
|
||||||
return group_index
|
return group_index
|
||||||
|
|
||||||
# Convert all loops into IFC curves
|
# Convert all loops into IFC curves
|
||||||
curves = []
|
curves: list[ifcopenshell.entity_instance] = []
|
||||||
for loop in loops:
|
for loop in loops:
|
||||||
|
|
||||||
if len(loop) == 1 and all([is_in_group(v, "IFCCIRCLE") for v in loop[0].verts]):
|
if len(loop) == 1 and all([is_in_group(v, "IFCCIRCLE") for v in loop[0].verts]):
|
||||||
@@ -1670,7 +1681,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
tmp.createIfcCircle(tmp.createIfcAxis2Placement2D(tmp.createIfcCartesianPoint(list(mid))), radius)
|
tmp.createIfcCircle(tmp.createIfcAxis2Placement2D(tmp.createIfcCartesianPoint(list(mid))), radius)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
loop_verts = []
|
loop_verts: list[bmesh.types.BMVert] = []
|
||||||
for i, edge in enumerate(loop):
|
for i, edge in enumerate(loop):
|
||||||
if i == 0 and len(loop) == 1:
|
if i == 0 and len(loop) == 1:
|
||||||
loop_verts.append(edge.verts[0])
|
loop_verts.append(edge.verts[0])
|
||||||
@@ -1746,7 +1757,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
curves.append(tmp.createIfcIndexedPolyCurve(points))
|
||||||
|
|
||||||
# Sort IFC curves into either closed, or closed with void profile defs
|
# Sort IFC curves into either closed, or closed with void profile defs
|
||||||
profile_defs = []
|
profile_defs: list[ifcopenshell.entity_instance] = []
|
||||||
settings = ifcopenshell.geom.settings()
|
settings = ifcopenshell.geom.settings()
|
||||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||||
|
|
||||||
|
|||||||
@@ -418,8 +418,8 @@ class Root(bonsai.core.tool.Root):
|
|||||||
"""Rename material without triggerring name callback and unnecessary writing to IFC."""
|
"""Rename material without triggerring name callback and unnecessary writing to IFC."""
|
||||||
if material.name == name:
|
if material.name == name:
|
||||||
return
|
return
|
||||||
props = material.BIMStyleProperties
|
msprops = tool.Style.get_material_style_props(material)
|
||||||
props.is_renaming = True
|
msprops.is_renaming = True
|
||||||
material.name = name # The handler will trigger, and reset is_renaming to False.
|
material.name = name # The handler will trigger, and reset is_renaming to False.
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -162,14 +162,14 @@ class Style(bonsai.core.tool.Style):
|
|||||||
cls, blender_material_or_style: Union[bpy.types.Material, ifcopenshell.entity_instance]
|
cls, blender_material_or_style: Union[bpy.types.Material, ifcopenshell.entity_instance]
|
||||||
) -> dict[str, ifcopenshell.entity_instance]:
|
) -> dict[str, ifcopenshell.entity_instance]:
|
||||||
if isinstance(blender_material_or_style, bpy.types.Material):
|
if isinstance(blender_material_or_style, bpy.types.Material):
|
||||||
if not (ifc_definition_id := blender_material_or_style.BIMStyleProperties.ifc_definition_id):
|
style = tool.Ifc.get_entity(blender_material_or_style)
|
||||||
|
if not style:
|
||||||
return {}
|
return {}
|
||||||
style = tool.Ifc.get().by_id(ifc_definition_id)
|
|
||||||
else:
|
else:
|
||||||
style = blender_material_or_style
|
style = blender_material_or_style
|
||||||
style_elements = {}
|
style_elements = {}
|
||||||
for style in style.Styles:
|
for style_ in style.Styles:
|
||||||
style_elements[style.is_a()] = style
|
style_elements[style_.is_a()] = style_
|
||||||
return style_elements
|
return style_elements
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -305,7 +305,7 @@ class Style(bonsai.core.tool.Style):
|
|||||||
return next((l.from_node for l in input_pin.links if l.from_node.type == of_type), None)
|
return next((l.from_node for l in input_pin.links if l.from_node.type == of_type), None)
|
||||||
return next((l.from_node for l in input_pin.links), None)
|
return next((l.from_node for l in input_pin.links), None)
|
||||||
|
|
||||||
props = obj.BIMStyleProperties
|
props = tool.Style.get_material_style_props(obj)
|
||||||
transparency = 1 - obj.diffuse_color[3]
|
transparency = 1 - obj.diffuse_color[3]
|
||||||
diffuse_color = obj.diffuse_color
|
diffuse_color = obj.diffuse_color
|
||||||
viewport_color = color_to_ifc_format(obj.diffuse_color)
|
viewport_color = color_to_ifc_format(obj.diffuse_color)
|
||||||
@@ -487,16 +487,14 @@ class Style(bonsai.core.tool.Style):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_surface_shading_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
|
def get_surface_shading_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
|
if style := tool.Ifc.get_entity(obj):
|
||||||
style = tool.Ifc.get().by_id(ifc_definition_id)
|
|
||||||
items = [s for s in style.Styles if s.is_a() == "IfcSurfaceStyleShading"]
|
items = [s for s in style.Styles if s.is_a() == "IfcSurfaceStyleShading"]
|
||||||
if items:
|
if items:
|
||||||
return items[0]
|
return items[0]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_surface_texture_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
|
def get_surface_texture_style(cls, obj: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
|
||||||
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
|
if style := tool.Ifc.get_entity(obj):
|
||||||
style = tool.Ifc.get().by_id(ifc_definition_id)
|
|
||||||
items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleWithTextures")]
|
items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleWithTextures")]
|
||||||
if items:
|
if items:
|
||||||
return items[0]
|
return items[0]
|
||||||
@@ -581,7 +579,8 @@ class Style(bonsai.core.tool.Style):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def change_current_style_type(cls, blender_material: bpy.types.Material, style_type: str) -> None:
|
def change_current_style_type(cls, blender_material: bpy.types.Material, style_type: str) -> None:
|
||||||
blender_material.BIMStyleProperties.active_style_type = style_type
|
props = cls.get_material_style_props(blender_material)
|
||||||
|
props.active_style_type = style_type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_styled_items(cls, style: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
def get_styled_items(cls, style: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||||
@@ -632,7 +631,8 @@ class Style(bonsai.core.tool.Style):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reload_material_from_ifc(cls, blender_material: bpy.types.Material) -> None:
|
def reload_material_from_ifc(cls, blender_material: bpy.types.Material) -> None:
|
||||||
blender_material.BIMStyleProperties.active_style_type = blender_material.BIMStyleProperties.active_style_type
|
props = cls.get_material_style_props(blender_material)
|
||||||
|
props.active_style_type = props.active_style_type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def switch_shading(cls, blender_material: bpy.types.Material, style_type: StyleType) -> None:
|
def switch_shading(cls, blender_material: bpy.types.Material, style_type: StyleType) -> None:
|
||||||
|
|||||||
@@ -911,14 +911,14 @@ def the_material_name_is_not_an_ifc_material(name):
|
|||||||
@then(parsers.parse('the material "{name}" is an IFC style'))
|
@then(parsers.parse('the material "{name}" is an IFC style'))
|
||||||
def the_material_name_is_an_ifc_style(name):
|
def the_material_name_is_an_ifc_style(name):
|
||||||
obj = the_material_name_exists(name)
|
obj = the_material_name_exists(name)
|
||||||
ifc_definition_id = obj.BIMStyleProperties.ifc_definition_id
|
ifc_definition_id = tool.Blender.get_ifc_definition_id(obj)
|
||||||
assert ifc_definition_id != 0, f"The material {obj} has a style ID of {ifc_definition_id}"
|
assert ifc_definition_id != 0, f"The material {obj} has a style ID of {ifc_definition_id}"
|
||||||
|
|
||||||
|
|
||||||
@then(parsers.parse('the material "{name}" is not an IFC style'))
|
@then(parsers.parse('the material "{name}" is not an IFC style'))
|
||||||
def the_material_name_is_not_an_ifc_style(name):
|
def the_material_name_is_not_an_ifc_style(name):
|
||||||
obj = the_material_name_exists(name)
|
obj = the_material_name_exists(name)
|
||||||
ifc_definition_id = obj.BIMStyleProperties.ifc_definition_id
|
ifc_definition_id = tool.Blender.get_ifc_definition_id(obj)
|
||||||
assert ifc_definition_id == 0, f"The material {obj} has a style ID of {ifc_definition_id}"
|
assert ifc_definition_id == 0, f"The material {obj} has a style ID of {ifc_definition_id}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -891,11 +891,12 @@ class TestAddReferenceImage(NewFile):
|
|||||||
assert tool.Cad.are_vectors_equal(obj.dimensions, Vector((3.53982, 2.0, 0.0)))
|
assert tool.Cad.are_vectors_equal(obj.dimensions, Vector((3.53982, 2.0, 0.0)))
|
||||||
|
|
||||||
material = obj.active_material
|
material = obj.active_material
|
||||||
|
assert material
|
||||||
assert material.name == "image"
|
assert material.name == "image"
|
||||||
assert material.BIMStyleProperties.ifc_definition_id != 0
|
assert tool.Blender.get_ifc_definition_id(material) != 0
|
||||||
|
|
||||||
ifc_file = tool.Ifc.get()
|
style = tool.Ifc.get_entity(material)
|
||||||
style = ifc_file.by_id(material.BIMStyleProperties.ifc_definition_id)
|
assert style
|
||||||
styled_items = set(tool.Style.get_styled_items(style))
|
styled_items = set(tool.Style.get_styled_items(style))
|
||||||
representation_items = set(tool.Geometry.get_active_representation(obj).Items)
|
representation_items = set(tool.Geometry.get_active_representation(obj).Items)
|
||||||
assert styled_items == representation_items
|
assert styled_items == representation_items
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ class TestGetObjectMaterialsWithoutStyles(NewFile):
|
|||||||
material1 = bpy.data.materials.new("Material")
|
material1 = bpy.data.materials.new("Material")
|
||||||
material2 = bpy.data.materials.new("Material")
|
material2 = bpy.data.materials.new("Material")
|
||||||
material3 = bpy.data.materials.new("Material")
|
material3 = bpy.data.materials.new("Material")
|
||||||
material3.BIMStyleProperties.ifc_definition_id = 1
|
props = tool.Style.get_material_style_props(material3)
|
||||||
|
props.ifc_definition_id = 1
|
||||||
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
||||||
obj.data.materials.append(material1)
|
obj.data.materials.append(material1)
|
||||||
obj.data.materials.append(material2)
|
obj.data.materials.append(material2)
|
||||||
@@ -307,7 +308,7 @@ class TestRecordObjectMaterials(NewFile):
|
|||||||
tool.Ifc.set(ifc)
|
tool.Ifc.set(ifc)
|
||||||
style = ifc.createIfcSurfaceStyle()
|
style = ifc.createIfcSurfaceStyle()
|
||||||
material = bpy.data.materials.new("Material")
|
material = bpy.data.materials.new("Material")
|
||||||
material.BIMStyleProperties.ifc_definition_id = style.id()
|
tool.Ifc.link(style, material)
|
||||||
obj.data.materials.append(material)
|
obj.data.materials.append(material)
|
||||||
subject.record_object_materials(obj)
|
subject.record_object_materials(obj)
|
||||||
assert tool.Geometry.get_mesh_props(obj.data).material_checksum == str([style.id()])
|
assert tool.Geometry.get_mesh_props(obj.data).material_checksum == str([style.id()])
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ class TestApplyIfcMaterialChanges(NewFile):
|
|||||||
def get_used_styles(self, obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
|
def get_used_styles(self, obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
return {
|
return {
|
||||||
ifc_file.by_id(s.material.BIMStyleProperties.ifc_definition_id) for s in obj.material_slots if s.material
|
ifc_file.by_id(tool.Blender.get_ifc_definition_id(s.material)) for s in obj.material_slots if s.material
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_mesh(self, obj: bpy.types.Object) -> bpy.types.Mesh:
|
def get_mesh(self, obj: bpy.types.Object) -> bpy.types.Mesh:
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.api.project
|
||||||
|
import ifcopenshell.api.root
|
||||||
|
import ifcopenshell.api.unit
|
||||||
import bonsai.core.tool
|
import bonsai.core.tool
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from test.bim.bootstrap import NewFile
|
from test.bim.bootstrap import NewFile
|
||||||
|
|||||||
@@ -308,7 +308,8 @@ class TestGetSurfaceRenderingStyle(NewFile):
|
|||||||
style_item = tool.Ifc.get().createIfcSurfaceStyleRendering()
|
style_item = tool.Ifc.get().createIfcSurfaceStyleRendering()
|
||||||
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
||||||
obj = bpy.data.materials.new("Material")
|
obj = bpy.data.materials.new("Material")
|
||||||
obj.BIMStyleProperties.ifc_definition_id = style.id()
|
props = tool.Style.get_material_style_props(obj)
|
||||||
|
props.ifc_definition_id = style.id()
|
||||||
assert subject.get_surface_rendering_style(obj) == style_item
|
assert subject.get_surface_rendering_style(obj) == style_item
|
||||||
|
|
||||||
|
|
||||||
@@ -347,7 +348,7 @@ class TestGetSurfaceShadingStyle(NewFile):
|
|||||||
style_item = tool.Ifc.get().createIfcSurfaceStyleShading()
|
style_item = tool.Ifc.get().createIfcSurfaceStyleShading()
|
||||||
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
||||||
obj = bpy.data.materials.new("Material")
|
obj = bpy.data.materials.new("Material")
|
||||||
obj.BIMStyleProperties.ifc_definition_id = style.id()
|
tool.Ifc.link(style, obj)
|
||||||
assert subject.get_surface_shading_style(obj) == style_item
|
assert subject.get_surface_shading_style(obj) == style_item
|
||||||
|
|
||||||
def test_do_not_get_rendering_styles(self):
|
def test_do_not_get_rendering_styles(self):
|
||||||
@@ -355,7 +356,7 @@ class TestGetSurfaceShadingStyle(NewFile):
|
|||||||
style_item = tool.Ifc.get().createIfcSurfaceStyleRendering()
|
style_item = tool.Ifc.get().createIfcSurfaceStyleRendering()
|
||||||
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
||||||
obj = bpy.data.materials.new("Material")
|
obj = bpy.data.materials.new("Material")
|
||||||
obj.BIMStyleProperties.ifc_definition_id = style.id()
|
tool.Ifc.link(style, obj)
|
||||||
assert subject.get_surface_shading_style(obj) is None
|
assert subject.get_surface_shading_style(obj) is None
|
||||||
|
|
||||||
|
|
||||||
@@ -365,7 +366,7 @@ class TestGetSurfaceTextureStyle(NewFile):
|
|||||||
style_item = tool.Ifc.get().createIfcSurfaceStyleWithTextures()
|
style_item = tool.Ifc.get().createIfcSurfaceStyleWithTextures()
|
||||||
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item])
|
||||||
obj = bpy.data.materials.new("Material")
|
obj = bpy.data.materials.new("Material")
|
||||||
obj.BIMStyleProperties.ifc_definition_id = style.id()
|
tool.Ifc.link(style, obj)
|
||||||
assert subject.get_surface_texture_style(obj) == style_item
|
assert subject.get_surface_texture_style(obj) == style_item
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,159 +21,203 @@ import ifcopenshell.util.element
|
|||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from ifcopenshell.util.data import Clipping
|
from ifcopenshell.util.data import Clipping
|
||||||
from typing import Any, Union, Optional, Literal
|
from typing import Any, Union, Optional, Literal, get_args
|
||||||
|
|
||||||
VECTOR_3D = tuple[float, float, float]
|
VECTOR_3D = tuple[float, float, float]
|
||||||
|
CardinalPointNumeric = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
|
||||||
|
CardinalPointString = Literal[
|
||||||
|
"bottom left",
|
||||||
|
"bottom centre",
|
||||||
|
"bottom right",
|
||||||
|
"mid-depth left",
|
||||||
|
"mid-depth centre",
|
||||||
|
"mid-depth right",
|
||||||
|
"top left",
|
||||||
|
"top centre",
|
||||||
|
"top right",
|
||||||
|
"geometric centroid",
|
||||||
|
"bottom in line with the geometric centroid",
|
||||||
|
"left in line with the geometric centroid",
|
||||||
|
"right in line with the geometric centroid",
|
||||||
|
"top in line with the geometric centroid",
|
||||||
|
"shear centre",
|
||||||
|
"bottom in line with the shear centre",
|
||||||
|
"left in line with the shear centre",
|
||||||
|
"right in line with the shear centre",
|
||||||
|
"top in line with the shear centre",
|
||||||
|
]
|
||||||
|
CARDINAL_POINT_VALUES: tuple[CardinalPointString, ...] = get_args(CardinalPointString)
|
||||||
|
CardinalPoint = Union[CardinalPointNumeric, CardinalPointString]
|
||||||
|
|
||||||
|
|
||||||
def add_profile_representation(
|
def add_profile_representation(
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
# IfcGeometricRepresentationContext
|
|
||||||
context: ifcopenshell.entity_instance,
|
context: ifcopenshell.entity_instance,
|
||||||
profile: ifcopenshell.entity_instance,
|
profile: ifcopenshell.entity_instance,
|
||||||
# in meters
|
|
||||||
depth: float = 1.0,
|
depth: float = 1.0,
|
||||||
cardinal_point: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 5,
|
# TODO: None makes more sense as default value?
|
||||||
# A list of planes that define clipping half space solids
|
cardinal_point: Union[CardinalPoint, None] = 5,
|
||||||
# Planes are defined either by Clipping objects
|
|
||||||
# or by dictionaries of arguments for `Clipping.parse`
|
|
||||||
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
|
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
|
||||||
placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]] = (None, None),
|
placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]] = (None, None),
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
|
"""Add profile representation.
|
||||||
|
|
||||||
|
:param context: The IfcGeometricRepresentationContext for the representation,
|
||||||
|
only Model/Body/MODEL_VIEW type of representations are currently supported.
|
||||||
|
:param profile: The IfcProfileDef to extrude.
|
||||||
|
:param depth: The depth of the extrusion in meters.
|
||||||
|
:param cardinal_point: The cardinal point of the profile.
|
||||||
|
:param clippings: A list of planes that define clipping half space solids.
|
||||||
|
Planes are defined either by Clipping objects
|
||||||
|
or by dictionaries of arguments for `Clipping.parse`.
|
||||||
|
:param placement_zx_axes: A tuple of two vectors that define the placement of the profile.
|
||||||
|
The first vector is the Z axis, the second vector is the X axis.
|
||||||
|
:return: IfcShapeRepresentation.
|
||||||
|
"""
|
||||||
usecase = Usecase()
|
usecase = Usecase()
|
||||||
usecase.file = file
|
usecase.file = file
|
||||||
usecase.settings = {
|
clippings = clippings if clippings is not None else []
|
||||||
"context": context,
|
return usecase.execute(context, profile, depth, cardinal_point, clippings, placement_zx_axes)
|
||||||
"profile": profile,
|
|
||||||
"depth": depth,
|
|
||||||
"cardinal_point": cardinal_point,
|
|
||||||
"clippings": clippings if clippings is not None else [],
|
|
||||||
"placement_zx_axes": placement_zx_axes,
|
|
||||||
}
|
|
||||||
return usecase.execute()
|
|
||||||
|
|
||||||
|
|
||||||
class Usecase:
|
class Usecase:
|
||||||
file: ifcopenshell.file
|
file: ifcopenshell.file
|
||||||
settings: dict[str, Any]
|
clippings: list[Clipping]
|
||||||
|
|
||||||
def execute(self):
|
def execute(
|
||||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
self,
|
||||||
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
|
context: ifcopenshell.entity_instance,
|
||||||
return self.file.createIfcShapeRepresentation(
|
profile: ifcopenshell.entity_instance,
|
||||||
self.settings["context"],
|
depth: float,
|
||||||
self.settings["context"].ContextIdentifier,
|
cardinal_point: Union[CardinalPoint, None],
|
||||||
"Clipping" if self.settings["clippings"] else "SweptSolid",
|
clippings: list[Union[Clipping, dict[str, Any]]],
|
||||||
|
placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]],
|
||||||
|
) -> ifcopenshell.entity_instance:
|
||||||
|
if isinstance(cardinal_point, int):
|
||||||
|
cardinal_point = CARDINAL_POINT_VALUES[cardinal_point - 1]
|
||||||
|
|
||||||
|
self.cardinal_point = cardinal_point
|
||||||
|
self.profile = profile
|
||||||
|
self.clippings = [Clipping.parse(c) for c in clippings]
|
||||||
|
self.depth = depth
|
||||||
|
self.placement_zx_axes = placement_zx_axes
|
||||||
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||||
|
return self.file.create_entity(
|
||||||
|
"IfcShapeRepresentation",
|
||||||
|
context,
|
||||||
|
context.ContextIdentifier,
|
||||||
|
"Clipping" if self.clippings else "SweptSolid",
|
||||||
[self.create_item()],
|
[self.create_item()],
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_item(self):
|
def create_item(self) -> ifcopenshell.entity_instance:
|
||||||
point = self.get_point()
|
point = self.get_point()
|
||||||
placement = self.file.createIfcAxis2Placement3D(
|
placement = self.file.createIfcAxis2Placement3D(
|
||||||
point,
|
point,
|
||||||
self.file.createIfcDirection(self.settings["placement_zx_axes"][0] or (0.0, 0.0, 1.0)),
|
self.file.create_entity("IfcDirection", self.placement_zx_axes[0] or (0.0, 0.0, 1.0)),
|
||||||
self.file.createIfcDirection(self.settings["placement_zx_axes"][1] or (1.0, 0.0, 0.0)),
|
self.file.create_entity("IfcDirection", self.placement_zx_axes[1] or (1.0, 0.0, 0.0)),
|
||||||
)
|
)
|
||||||
extrusion = self.file.createIfcExtrudedAreaSolid(
|
extrusion = self.file.create_entity(
|
||||||
self.settings["profile"],
|
"IfcExtrudedAreaSolid",
|
||||||
|
self.profile,
|
||||||
placement,
|
placement,
|
||||||
self.file.createIfcDirection((0.0, 0.0, 1.0)),
|
self.file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||||
self.convert_si_to_unit(self.settings["depth"]),
|
self.convert_si_to_unit(self.depth),
|
||||||
)
|
)
|
||||||
if self.settings["clippings"]:
|
if self.clippings:
|
||||||
return self.apply_clippings(extrusion)
|
return self.apply_clippings(extrusion)
|
||||||
return extrusion
|
return extrusion
|
||||||
|
|
||||||
def apply_clippings(self, first_operand):
|
def apply_clippings(self, first_operand: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||||
while self.settings["clippings"]:
|
while self.clippings:
|
||||||
clipping = self.settings["clippings"].pop()
|
clipping = self.clippings.pop()
|
||||||
if isinstance(clipping, ifcopenshell.entity_instance):
|
if isinstance(clipping, ifcopenshell.entity_instance):
|
||||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||||
new.FirstOperand = first_operand
|
new.FirstOperand = first_operand
|
||||||
first_operand = new
|
first_operand = new
|
||||||
else: # Clipping
|
else: # Clipping
|
||||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
first_operand = clipping.apply(self.file, first_operand, self.unit_scale)
|
||||||
return first_operand
|
return first_operand
|
||||||
|
|
||||||
def convert_si_to_unit(self, co):
|
def convert_si_to_unit(self, co: float) -> float:
|
||||||
return co / self.settings["unit_scale"]
|
return co / self.unit_scale
|
||||||
|
|
||||||
def get_point(self):
|
def get_point(self) -> ifcopenshell.entity_instance:
|
||||||
if not self.settings["cardinal_point"]:
|
if not self.cardinal_point:
|
||||||
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 1:
|
elif self.cardinal_point == "bottom left":
|
||||||
return self.file.createIfcCartesianPoint((-self.get_x() / 2, self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((-self.get_x() / 2, self.get_y() / 2, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 2:
|
elif self.cardinal_point == "bottom centre":
|
||||||
return self.file.createIfcCartesianPoint((0.0, self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((0.0, self.get_y() / 2, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 3:
|
elif self.cardinal_point == "bottom right":
|
||||||
return self.file.createIfcCartesianPoint((self.get_x() / 2, self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((self.get_x() / 2, self.get_y() / 2, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 4:
|
elif self.cardinal_point == "mid-depth left":
|
||||||
return self.file.createIfcCartesianPoint((-self.get_x() / 2, 0.0, 0.0))
|
return self.file.createIfcCartesianPoint((-self.get_x() / 2, 0.0, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 5:
|
elif self.cardinal_point == "mid-depth centre":
|
||||||
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 6:
|
elif self.cardinal_point == "mid-depth right":
|
||||||
return self.file.createIfcCartesianPoint((self.get_x() / 2, 0.0, 0.0))
|
return self.file.createIfcCartesianPoint((self.get_x() / 2, 0.0, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 7:
|
elif self.cardinal_point == "top left":
|
||||||
return self.file.createIfcCartesianPoint((-self.get_x() / 2, -self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((-self.get_x() / 2, -self.get_y() / 2, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 8:
|
elif self.cardinal_point == "top centre":
|
||||||
return self.file.createIfcCartesianPoint((0.0, -self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((0.0, -self.get_y() / 2, 0.0))
|
||||||
elif self.settings["cardinal_point"] == 9:
|
elif self.cardinal_point == "top right":
|
||||||
return self.file.createIfcCartesianPoint((self.get_x() / 2, -self.get_y() / 2, 0.0))
|
return self.file.createIfcCartesianPoint((self.get_x() / 2, -self.get_y() / 2, 0.0))
|
||||||
# TODO other cardinal points
|
# TODO other cardinal points
|
||||||
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
return self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||||
|
|
||||||
def get_x(self):
|
def get_x(self) -> float:
|
||||||
if self.settings["profile"].is_a("IfcAsymmetricIShapeProfileDef"):
|
if self.profile.is_a("IfcAsymmetricIShapeProfileDef"):
|
||||||
return self.settings["profile"].OverallWidth
|
return self.profile.OverallWidth
|
||||||
elif self.settings["profile"].is_a("IfcCShapeProfileDef"):
|
elif self.profile.is_a("IfcCShapeProfileDef"):
|
||||||
return self.settings["profile"].Width
|
return self.profile.Width
|
||||||
elif self.settings["profile"].is_a("IfcCircleProfileDef"):
|
elif self.profile.is_a("IfcCircleProfileDef"):
|
||||||
return self.settings["profile"].Radius * 2
|
return self.profile.Radius * 2
|
||||||
elif self.settings["profile"].is_a("IfcEllipseProfileDef"):
|
elif self.profile.is_a("IfcEllipseProfileDef"):
|
||||||
return self.settings["profile"].SemiAxis1 * 2
|
return self.profile.SemiAxis1 * 2
|
||||||
elif self.settings["profile"].is_a("IfcIShapeProfileDef"):
|
elif self.profile.is_a("IfcIShapeProfileDef"):
|
||||||
return self.settings["profile"].OverallWidth
|
return self.profile.OverallWidth
|
||||||
elif self.settings["profile"].is_a("IfcLShapeProfileDef"):
|
elif self.profile.is_a("IfcLShapeProfileDef"):
|
||||||
return self.settings["profile"].Width
|
return self.profile.Width
|
||||||
elif self.settings["profile"].is_a("IfcRectangleProfileDef"):
|
elif self.profile.is_a("IfcRectangleProfileDef"):
|
||||||
return self.settings["profile"].XDim
|
return self.profile.XDim
|
||||||
elif self.settings["profile"].is_a("IfcTShapeProfileDef"):
|
elif self.profile.is_a("IfcTShapeProfileDef"):
|
||||||
return self.settings["profile"].FlangeWidth
|
return self.profile.FlangeWidth
|
||||||
elif self.settings["profile"].is_a("IfcUShapeProfileDef"):
|
elif self.profile.is_a("IfcUShapeProfileDef"):
|
||||||
return self.settings["profile"].FlangeWidth
|
return self.profile.FlangeWidth
|
||||||
elif self.settings["profile"].is_a("IfcZShapeProfileDef"):
|
elif self.profile.is_a("IfcZShapeProfileDef"):
|
||||||
return (self.settings["profile"].FlangeWidth * 2) - self.settings["profile"].WebThickness
|
return (self.profile.FlangeWidth * 2) - self.profile.WebThickness
|
||||||
else:
|
else:
|
||||||
settings = ifcopenshell.geom.settings()
|
settings = ifcopenshell.geom.settings()
|
||||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||||
shape = ifcopenshell.geom.create_shape(settings, self.settings["profile"])
|
shape = ifcopenshell.geom.create_shape(settings, self.profile)
|
||||||
return self.convert_si_to_unit(ifcopenshell.util.shape.get_x(shape))
|
return self.convert_si_to_unit(ifcopenshell.util.shape.get_x(shape))
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
def get_y(self):
|
def get_y(self) -> float:
|
||||||
if self.settings["profile"].is_a("IfcAsymmetricIShapeProfileDef"):
|
if self.profile.is_a("IfcAsymmetricIShapeProfileDef"):
|
||||||
return self.settings["profile"].OverallDepth
|
return self.profile.OverallDepth
|
||||||
elif self.settings["profile"].is_a("IfcCShapeProfileDef"):
|
elif self.profile.is_a("IfcCShapeProfileDef"):
|
||||||
return self.settings["profile"].Depth
|
return self.profile.Depth
|
||||||
elif self.settings["profile"].is_a("IfcCircleProfileDef"):
|
elif self.profile.is_a("IfcCircleProfileDef"):
|
||||||
return self.settings["profile"].Radius * 2
|
return self.profile.Radius * 2
|
||||||
elif self.settings["profile"].is_a("IfcEllipseProfileDef"):
|
elif self.profile.is_a("IfcEllipseProfileDef"):
|
||||||
return self.settings["profile"].SemiAxis2 * 2
|
return self.profile.SemiAxis2 * 2
|
||||||
elif self.settings["profile"].is_a("IfcIShapeProfileDef"):
|
elif self.profile.is_a("IfcIShapeProfileDef"):
|
||||||
return self.settings["profile"].OverallDepth
|
return self.profile.OverallDepth
|
||||||
elif self.settings["profile"].is_a("IfcLShapeProfileDef"):
|
elif self.profile.is_a("IfcLShapeProfileDef"):
|
||||||
return self.settings["profile"].Depth
|
return self.profile.Depth
|
||||||
elif self.settings["profile"].is_a("IfcRectangleProfileDef"):
|
elif self.profile.is_a("IfcRectangleProfileDef"):
|
||||||
return self.settings["profile"].YDim
|
return self.profile.YDim
|
||||||
elif self.settings["profile"].is_a("IfcTShapeProfileDef"):
|
elif self.profile.is_a("IfcTShapeProfileDef"):
|
||||||
return self.settings["profile"].Depth
|
return self.profile.Depth
|
||||||
elif self.settings["profile"].is_a("IfcUShapeProfileDef"):
|
elif self.profile.is_a("IfcUShapeProfileDef"):
|
||||||
return self.settings["profile"].Depth
|
return self.profile.Depth
|
||||||
elif self.settings["profile"].is_a("IfcZShapeProfileDef"):
|
elif self.profile.is_a("IfcZShapeProfileDef"):
|
||||||
return self.settings["profile"].Depth
|
return self.profile.Depth
|
||||||
else:
|
else:
|
||||||
settings = ifcopenshell.geom.settings()
|
settings = ifcopenshell.geom.settings()
|
||||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||||
shape = ifcopenshell.geom.create_shape(settings, self.settings["profile"])
|
shape = ifcopenshell.geom.create_shape(settings, self.profile)
|
||||||
return self.convert_si_to_unit(ifcopenshell.util.shape.get_y(shape))
|
return self.convert_si_to_unit(ifcopenshell.util.shape.get_y(shape))
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ class Usecase:
|
|||||||
self.settings_2d.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
self.settings_2d.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid)
|
shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid)
|
||||||
|
|
||||||
|
# NOTE: points do not need unit conversion
|
||||||
|
# as dummy file is inherently using project units.
|
||||||
if self.cardinal_point == 1:
|
if self.cardinal_point == 1:
|
||||||
return self.get_bottom_left(shape)
|
return self.get_bottom_left(shape)
|
||||||
elif self.cardinal_point == 2:
|
elif self.cardinal_point == 2:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
@@ -30,7 +31,9 @@ class Clipping:
|
|||||||
operand_type: str = "IfcHalfSpaceSolid"
|
operand_type: str = "IfcHalfSpaceSolid"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, raw_data: Any) -> Union[ifcopenshell.entity_instance, "Clipping", None]:
|
def parse(
|
||||||
|
cls, raw_data: Union[ifcopenshell.entity_instance, Clipping, dict[str, Any]]
|
||||||
|
) -> Union[ifcopenshell.entity_instance, Clipping]:
|
||||||
"""Parse various formats into a clipping object
|
"""Parse various formats into a clipping object
|
||||||
|
|
||||||
`raw_data` can be either:
|
`raw_data` can be either:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import test.bootstrap
|
|||||||
import ifcopenshell.api.root
|
import ifcopenshell.api.root
|
||||||
import ifcopenshell.api.context
|
import ifcopenshell.api.context
|
||||||
import ifcopenshell.api.geometry
|
import ifcopenshell.api.geometry
|
||||||
|
import ifcopenshell.util.shape_builder
|
||||||
|
|
||||||
|
|
||||||
class TestAddShapeAspect(test.bootstrap.IFC4):
|
class TestAddShapeAspect(test.bootstrap.IFC4):
|
||||||
|
|||||||
Reference in New Issue
Block a user