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/>.
import bpy
import bmesh
import ifcopenshell
import bonsai.tool as tool
@@ -32,9 +30,23 @@ class ApplyExternalParametricGeometry(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
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)
# 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
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 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]]:
if not AuthoringData.is_loaded:
@@ -1709,12 +1712,16 @@ def update_is_editing(self: "BIMExternalParametricGeometryProperties", context:
if self.is_editing:
return
assert isinstance(self.id_data, bpy.types.Object)
tool.Model.clean_up_parametric_geometry(self.id_data)
del self["is_editing"]
del self["geo_nodes"]
self.property_unset("is_editing")
self.property_unset("geo_nodes")
self.property_unset("sverchok_nodes")
def update_geo_nodes(self: "BIMExternalParametricGeometryProperties", context: bpy.types.Context) -> None:
assert isinstance(self.id_data, bpy.types.Object)
if self.geo_nodes:
tool.Model.setup_parametric_geometry(self.id_data)
return
@@ -1725,14 +1732,25 @@ def update_geo_nodes(self: "BIMExternalParametricGeometryProperties", context: b
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):
is_editing: bpy.props.BoolProperty(
is_editing: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
name="Is Editing Paramteric Geometry",
description="Toggle editing parametric geometry.",
default=False,
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",
description="Geometry nodes tree to use as a source for representation.",
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"),
)
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:
is_editing: bool
geometry_source: Literal["GEONODES", "IFCSVERCHOK"]
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
import bpy
import bonsai.bim
from bonsai.bim import module
import bonsai.tool as tool
from bpy.types import Panel, Menu
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.prop(props, "is_editing", icon="CANCEL", text="")
row = layout.row(align=True)
row.prop_search(props, "geo_nodes", bpy.data, "node_groups")
if props.geo_nodes:
assert (modifier := tool.Model.get_epg_modifier(obj))
inputs = tool.Model.get_parametric_geometry_inputs(modifier)
# NOTE: users won't be able to see inputs descriptions.
# If we add group node inputs as modifiers inputs, descriptions will be visible.
# But then we need to ensure inputs are up to date
# (e.g. probably just by adding a refresh button).
for input in inputs:
row = layout.row(align=True)
row.prop(input, "default_value", text=input.name)
layout.prop(props, "geometry_source")
if props.geometry_source == "GEONODES":
row = layout.row(align=True)
row.prop_search(props, "geo_nodes", bpy.data, "node_groups")
if props.geo_nodes:
assert (modifier := tool.Model.get_epg_modifier(obj))
inputs = tool.Model.get_parametric_geometry_inputs(modifier)
# NOTE: users won't be able to see inputs descriptions.
# If we add group node inputs as modifiers inputs, descriptions will be visible.
# But then we need to ensure inputs are up to date
# (e.g. probably just by adding a refresh button).
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:
+64 -5
View File
@@ -54,6 +54,8 @@ T = TypeVar("T")
V_ = tool.Blender.V_
if TYPE_CHECKING:
import sverchok.node_tree
import ifcsverchok.nodes.ifc.shape_builder.shape_output
from bonsai.bim.module.model.prop import (
BIMModelProperties,
BIMDoorProperties,
@@ -2540,12 +2542,24 @@ class Model(bonsai.core.tool.Model):
@classmethod
def clean_up_parametric_geometry(cls, obj: bpy.types.Object) -> None:
# Geo nodes are using modifier.
modifier = tool.Model.get_epg_modifier(obj)
assert modifier
node_tree = modifier.node_group
assert node_tree
bpy.data.node_groups.remove(node_tree)
obj.modifiers.clear()
if modifier is not None:
node_tree = modifier.node_group
assert node_tree
bpy.data.node_groups.remove(node_tree)
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
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"]
@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
def align_objects(
cls,