external parametric geometry - integrate with ifcsverchok

Still very experimental, but here's a short demo - https://files.catbox.moe/u6vnhj.mp4
Sverchok graph I've used - https://files.catbox.moe/1x7b7q.png
This commit is contained in:
Andrej730
2026-01-16 16:51:14 +05:00
parent ed38d2347c
commit 9d1dcf9034
4 changed files with 137 additions and 26 deletions
+17 -5
View File
@@ -17,9 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import bmesh
import ifcopenshell
import bonsai.tool as tool import bonsai.tool as tool
@@ -32,9 +30,23 @@ class ApplyExternalParametricGeometry(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
assert obj assert obj
assert (active_representation := tool.Geometry.get_active_representation(obj))
ifc_context = active_representation.ContextOfItems
tool.Model.add_representation(obj, ifc_context)
props = tool.Model.get_epg_props(obj) props = tool.Model.get_epg_props(obj)
# TODO: consider nodes being empty.
if props.geometry_source == "GEONODES":
assert (active_representation := tool.Geometry.get_active_representation(obj))
ifc_context = active_representation.ContextOfItems
tool.Model.add_representation(obj, ifc_context)
elif props.geometry_source == "IFCSVERCHOK":
import ifcsverchok.helper as helper
nodes = props.sverchok_nodes
assert nodes is not None
tool.Model.run_ifcsverchok_graph_on_bonsai_file(nodes)
output_node = tool.Model.get_ifcsverchok_shape_output(nodes)
representation = helper.get_socket_value(output_node.inputs, "Representation")
tool.Model.replace_object_ifc_representation(representation.ContextOfItems, obj, representation)
props.is_editing = False props.is_editing = False
return {"FINISHED"} return {"FINISHED"}
+31 -4
View File
@@ -33,6 +33,9 @@ from typing import TYPE_CHECKING, Literal, get_args, Union, Any, Optional
from collections.abc import Callable from collections.abc import Callable
from mathutils import Vector from mathutils import Vector
if TYPE_CHECKING:
import sverchok.node_tree
def get_ifc_class(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: def get_ifc_class(self: "BIMModelProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not AuthoringData.is_loaded: if not AuthoringData.is_loaded:
@@ -1709,12 +1712,16 @@ def update_is_editing(self: "BIMExternalParametricGeometryProperties", context:
if self.is_editing: if self.is_editing:
return return
assert isinstance(self.id_data, bpy.types.Object)
tool.Model.clean_up_parametric_geometry(self.id_data) tool.Model.clean_up_parametric_geometry(self.id_data)
del self["is_editing"] self.property_unset("is_editing")
del self["geo_nodes"] self.property_unset("geo_nodes")
self.property_unset("sverchok_nodes")
def update_geo_nodes(self: "BIMExternalParametricGeometryProperties", context: bpy.types.Context) -> None: def update_geo_nodes(self: "BIMExternalParametricGeometryProperties", context: bpy.types.Context) -> None:
assert isinstance(self.id_data, bpy.types.Object)
if self.geo_nodes: if self.geo_nodes:
tool.Model.setup_parametric_geometry(self.id_data) tool.Model.setup_parametric_geometry(self.id_data)
return return
@@ -1725,14 +1732,25 @@ def update_geo_nodes(self: "BIMExternalParametricGeometryProperties", context: b
del self["geo_nodes"] del self["geo_nodes"]
def poll_sverchok_nodes(self: "BIMExternalParametricGeometryProperties", node_tree: bpy.types.NodeTree) -> bool:
return node_tree.bl_idname == "SverchCustomTreeType"
class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup): class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
is_editing: bpy.props.BoolProperty( is_editing: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
name="Is Editing Paramteric Geometry", name="Is Editing Paramteric Geometry",
description="Toggle editing parametric geometry.", description="Toggle editing parametric geometry.",
default=False, default=False,
update=update_is_editing, update=update_is_editing,
) )
geo_nodes: bpy.props.PointerProperty( geometry_source: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
name="Geometry Source",
items=[
("GEONODES", "Geometry Nodes", ""),
("IFCSVERCHOK", "IFC Sverchok", ""),
],
)
geo_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
name="Geometry Nodes", name="Geometry Nodes",
description="Geometry nodes tree to use as a source for representation.", description="Geometry nodes tree to use as a source for representation.",
type=bpy.types.GeometryNodeTree, type=bpy.types.GeometryNodeTree,
@@ -1740,6 +1758,15 @@ class BIMExternalParametricGeometryProperties(bpy.types.PropertyGroup):
poll=lambda self, node_tree: not node_tree.name.startswith("BBIM_EPG"), poll=lambda self, node_tree: not node_tree.name.startswith("BBIM_EPG"),
) )
sverchok_nodes: bpy.props.PointerProperty( # pyright: ignore[reportRedeclaration]
name="Sverchok Nodes",
description="Sverchok node tree to use as a source for representation.",
type=bpy.types.NodeTree,
poll=poll_sverchok_nodes,
)
if TYPE_CHECKING: if TYPE_CHECKING:
is_editing: bool is_editing: bool
geometry_source: Literal["GEONODES", "IFCSVERCHOK"]
geo_nodes: Union[bpy.types.GeometryNodeTree, None] geo_nodes: Union[bpy.types.GeometryNodeTree, None]
sverchok_nodes: Union[sverchok.node_tree.SverchCustomTree, None]
+25 -12
View File
@@ -19,6 +19,7 @@
from __future__ import annotations from __future__ import annotations
import bpy import bpy
import bonsai.bim import bonsai.bim
from bonsai.bim import module
import bonsai.tool as tool import bonsai.tool as tool
from bpy.types import Panel, Menu from bpy.types import Panel, Menu
from bonsai.bim.helper import prop_with_search from bonsai.bim.helper import prop_with_search
@@ -668,18 +669,30 @@ class BIM_PT_external_parametric_geometry(bpy.types.Panel):
row.operator("bim.apply_external_parametric_geometry", icon="CHECKMARK", text="") row.operator("bim.apply_external_parametric_geometry", icon="CHECKMARK", text="")
row.prop(props, "is_editing", icon="CANCEL", text="") row.prop(props, "is_editing", icon="CANCEL", text="")
row = layout.row(align=True) layout.prop(props, "geometry_source")
row.prop_search(props, "geo_nodes", bpy.data, "node_groups") if props.geometry_source == "GEONODES":
if props.geo_nodes: row = layout.row(align=True)
assert (modifier := tool.Model.get_epg_modifier(obj)) row.prop_search(props, "geo_nodes", bpy.data, "node_groups")
inputs = tool.Model.get_parametric_geometry_inputs(modifier) if props.geo_nodes:
# NOTE: users won't be able to see inputs descriptions. assert (modifier := tool.Model.get_epg_modifier(obj))
# If we add group node inputs as modifiers inputs, descriptions will be visible. inputs = tool.Model.get_parametric_geometry_inputs(modifier)
# But then we need to ensure inputs are up to date # NOTE: users won't be able to see inputs descriptions.
# (e.g. probably just by adding a refresh button). # If we add group node inputs as modifiers inputs, descriptions will be visible.
for input in inputs: # But then we need to ensure inputs are up to date
row = layout.row(align=True) # (e.g. probably just by adding a refresh button).
row.prop(input, "default_value", text=input.name) for input in inputs:
row = layout.row(align=True)
row.prop(input, "default_value", text=input.name)
elif props.geometry_source == "IFCSVERCHOK":
row = layout.row(align=True)
row.prop(props, "sverchok_nodes")
if props.sverchok_nodes:
# TODO: Updating mesh in `draw` is not ideal,
# should find a way to update only on graph changes.
res = tool.Model.update_mesh_from_sverchok(obj, props.sverchok_nodes)
if res is not None:
print(res)
layout.label(text=f"Error Updating from Graph, See System Console", icon="ERROR")
def draw_door_properties(layout: bpy.types.UILayout, props: module_prop.BIMDoorProperties) -> None: def draw_door_properties(layout: bpy.types.UILayout, props: module_prop.BIMDoorProperties) -> None:
+64 -5
View File
@@ -54,6 +54,8 @@ T = TypeVar("T")
V_ = tool.Blender.V_ V_ = tool.Blender.V_
if TYPE_CHECKING: if TYPE_CHECKING:
import sverchok.node_tree
import ifcsverchok.nodes.ifc.shape_builder.shape_output
from bonsai.bim.module.model.prop import ( from bonsai.bim.module.model.prop import (
BIMModelProperties, BIMModelProperties,
BIMDoorProperties, BIMDoorProperties,
@@ -2540,12 +2542,24 @@ class Model(bonsai.core.tool.Model):
@classmethod @classmethod
def clean_up_parametric_geometry(cls, obj: bpy.types.Object) -> None: def clean_up_parametric_geometry(cls, obj: bpy.types.Object) -> None:
# Geo nodes are using modifier.
modifier = tool.Model.get_epg_modifier(obj) modifier = tool.Model.get_epg_modifier(obj)
assert modifier if modifier is not None:
node_tree = modifier.node_group node_tree = modifier.node_group
assert node_tree assert node_tree
bpy.data.node_groups.remove(node_tree) bpy.data.node_groups.remove(node_tree)
obj.modifiers.clear() obj.modifiers.clear()
# Sverchok are changing the mesh data to preview changes.
# TODO: probably should use modifiers with nodes and temp mesh instead.
active_representation = tool.Geometry.get_active_representation(obj)
assert active_representation is not None
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=active_representation,
)
@classmethod @classmethod
def get_parametric_geometry_inputs(cls, modifier: bpy.types.NodesModifier) -> list[bpy.types.NodeSocket]: def get_parametric_geometry_inputs(cls, modifier: bpy.types.NodesModifier) -> list[bpy.types.NodeSocket]:
@@ -2557,6 +2571,51 @@ class Model(bonsai.core.tool.Model):
return [s for s in group_node.inputs if s.type != "GEOMETRY"] return [s for s in group_node.inputs if s.type != "GEOMETRY"]
@classmethod
def get_ifcsverchok_shape_output(
cls, node_tree: sverchok.node_tree.SverchCustomTree
) -> ifcsverchok.nodes.ifc.shape_builder.shape_output.SvSbShapeOutput:
from ifcsverchok.nodes.ifc.shape_builder.shape_output import SvSbShapeOutput
return next(n for n in node_tree.nodes if isinstance(n, SvSbShapeOutput))
@classmethod
def update_mesh_from_sverchok(
cls, obj: bpy.types.Object, node_tree: sverchok.node_tree.SverchCustomTree
) -> str | None:
"""
:return: ``None`` if successful, otherwise error message.
"""
import ifcsverchok.helper as helper
output_node = cls.get_ifcsverchok_shape_output(node_tree)
verts = helper.get_socket_value(output_node.outputs, "Vers", value_type="CONTAINER")
edges = helper.get_socket_value(output_node.outputs, "Edgs", value_type="CONTAINER")
faces = helper.get_socket_value(output_node.outputs, "Pols", value_type="CONTAINER")
mesh = obj.data
assert isinstance(mesh, bpy.types.Mesh)
mesh.clear_geometry()
# `shade_flat=False`, because `from_pydata` is using method that doesn't support changing shading
# during `Panel.draw` execution. We shade flat later ourselves.
mesh.from_pydata(verts, edges, faces, shade_flat=False)
def _name_convention_attribute_ensure(attributes, name, domain, data_type):
try:
attribute = attributes[name]
except KeyError:
return attributes.new(name, data_type, domain)
if attribute.domain == domain and attribute.data_type == data_type:
return attribute
attributes.remove(attribute)
return attributes.new(name, data_type, domain)
sharp_faces = _name_convention_attribute_ensure(mesh.attributes, "sharp_face", "FACE", "BOOLEAN")
assert isinstance(sharp_faces, bpy.types.BoolAttribute)
data = sharp_faces.data
ones = np.ones(len(data), dtype=bool)
data.foreach_set("value", ones)
@classmethod @classmethod
def align_objects( def align_objects(
cls, cls,