mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
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:
@@ -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"}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user