diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py
index 5b7d60ffc7..1e24ba138e 100644
--- a/src/blenderbim/blenderbim/bim/module/model/__init__.py
+++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py
@@ -17,7 +17,22 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-from . import handler, prop, ui, grid, array, product, wall, slab, stair, opening, pie, workspace, profile
+from . import (
+ handler,
+ prop,
+ ui,
+ grid,
+ array,
+ product,
+ wall,
+ slab,
+ stair,
+ opening,
+ pie,
+ workspace,
+ profile,
+ sverchok_modifier,
+)
classes = (
array.AddArray,
@@ -74,9 +89,11 @@ classes = (
prop.BIMModelProperties,
prop.BIMArrayProperties,
prop.BIMStairProperties,
+ prop.BIMSverchokProperties,
ui.BIM_PT_authoring,
ui.BIM_PT_array,
ui.BIM_PT_stair,
+ ui.BIM_PT_sverchok,
ui.DisplayConstrTypesUI,
ui.LaunchTypeManager,
ui.HelpConstrTypes,
@@ -94,6 +111,11 @@ classes = (
pie.PieAddOpening,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
+ sverchok_modifier.CreateNewSverchokGraph,
+ sverchok_modifier.UpdateDataFromSverchok,
+ sverchok_modifier.DeleteSverchokGraph,
+ sverchok_modifier.ImportSverchokGraph,
+ sverchok_modifier.ExportSverchokGraph,
)
addon_keymaps = []
@@ -105,6 +127,7 @@ def register():
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
+ bpy.types.Object.BIMSverchokProperties = bpy.props.PointerProperty(type=prop.BIMSverchokProperties)
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
@@ -123,6 +146,7 @@ def unregister():
del bpy.types.Scene.BIMModelProperties
del bpy.types.Object.BIMArrayProperties
del bpy.types.Object.BIMStairProperties
+ del bpy.types.Object.BIMSverchokProperties
bpy.app.handlers.load_post.remove(handler.load_post)
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py
index 47b9b5039e..824716e032 100644
--- a/src/blenderbim/blenderbim/bim/module/model/data.py
+++ b/src/blenderbim/blenderbim/bim/module/model/data.py
@@ -32,6 +32,7 @@ def refresh():
AuthoringData.is_loaded = False
ArrayData.is_loaded = False
StairData.is_loaded = False
+ SverchokData.is_loaded = False
class AuthoringData:
@@ -374,3 +375,23 @@ class StairData:
if parameters:
parameters["data"] = json.loads(parameters.get("Data", "[]") or "[]")
return parameters
+
+
+class SverchokData:
+ data = {}
+ is_loaded = False
+
+ @classmethod
+ def load(cls):
+ cls.is_loaded = True
+ cls.data = {"parameters": cls.parameters()}
+
+ @classmethod
+ def parameters(cls):
+ element = tool.Ifc.get_entity(bpy.context.active_object)
+ if element:
+ psets = ifcopenshell.util.element.get_psets(element)
+ parameters = psets.get("BBIM_Sverchok", None)
+ if parameters:
+ parameters["data"] = json.loads(parameters.get("Data", "[]") or "[]")
+ return parameters
diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py
index bc40d98210..2636deccf7 100644
--- a/src/blenderbim/blenderbim/bim/module/model/prop.py
+++ b/src/blenderbim/blenderbim/bim/module/model/prop.py
@@ -20,7 +20,7 @@ import bpy
from blenderbim.bim.prop import ObjProperty
from blenderbim.bim.module.model.data import AuthoringData
from blenderbim.bim.module.model.root import ConstrTypeEntityNotFound
-from bpy.types import PropertyGroup
+from bpy.types import PropertyGroup, NodeTree
def get_ifc_class(self, context):
@@ -294,3 +294,7 @@ class BIMStairProperties(PropertyGroup):
"tread_depth": self.tread_depth,
"tread_run": self.tread_run,
}
+
+
+class BIMSverchokProperties(PropertyGroup):
+ node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
diff --git a/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py
new file mode 100644
index 0000000000..619c871c58
--- /dev/null
+++ b/src/blenderbim/blenderbim/bim/module/model/sverchok_modifier.py
@@ -0,0 +1,300 @@
+# BlenderBIM Add-on - OpenBIM Blender Add-on
+# Copyright (C) 2020, 2021, 2022 Dion Moult , @Andrej730
+#
+# This file is part of BlenderBIM Add-on.
+#
+# BlenderBIM Add-on is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BlenderBIM Add-on is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BlenderBIM Add-on. If not, see .
+
+import bpy
+from bpy.types import Operator
+from bpy.props import FloatProperty, IntProperty, BoolProperty
+from bpy_extras.object_utils import AddObjectHelper, object_data_add
+
+import bmesh
+from bmesh.types import BMVert
+
+import ifcopenshell
+import blenderbim
+import blenderbim.tool as tool
+from blenderbim.bim.module.model.prop import BIMStairProperties
+
+
+from mathutils import Vector
+from pprint import pprint
+import json
+import zipfile
+
+from os.path import basename, dirname
+import os.path
+import json
+
+
+def update_sverchok_modifier(context):
+ obj = context.active_object
+ props = obj.BIMSverchokProperties
+ element = tool.Ifc.get_entity(obj)
+ psets = ifcopenshell.util.element.get_psets(element)
+ pset = psets.get("BBIM_Sverchok", None)
+ sverchok_data = json.loads(pset["Data"]) if pset else dict()
+
+ vertices = sverchok_data.get("vertices", [])
+ edges = sverchok_data.get("edges", [])
+ faces = sverchok_data.get("polygons", [])
+
+ bm = bmesh.new()
+ bm.verts.index_update()
+ bm.edges.index_update()
+ bm.faces.index_update()
+
+ new_verts = [bm.verts.new(v) for v in vertices]
+ new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges]
+ new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces]
+ bm.verts.index_update()
+ bm.edges.index_update()
+ bm.faces.index_update()
+
+ if context.object.mode == "EDIT":
+ bmesh.update_edit_mesh(obj.data)
+ else:
+ bm.to_mesh(obj.data)
+ bm.free()
+ obj.data.update()
+
+
+# UI operators
+class CreateNewSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.create_new_sverchok_graph"
+ bl_label = "Create new sverchok graph"
+ bl_options = {"REGISTER"}
+
+ def _execute(self, context):
+ import sverchok
+
+ obj = context.active_object
+ props = obj.BIMSverchokProperties
+
+ node_group = bpy.data.node_groups.new("IfcNodeTree", type="SverchCustomTreeType")
+ plane = node_group.nodes.new(type="SvPlaneNodeMk3")
+ plane.sizex = 1
+ plane.sizey = 1
+ sverchok.ui.sv_temporal_viewers.add_temporal_viewer_draw(
+ node_group.nodes, node_group.links, plane, cut_links=True
+ )
+ viewer = [n for n in node_group.nodes if n.bl_idname == "SvViewerDrawMk4"][0]
+ viewer.label = f"IFCOutput {viewer.label}"
+
+ props.node_group = node_group
+ return {"FINISHED"}
+
+
+class DeleteSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.delete_sverchok_graph"
+ bl_label = "Delete selected sverchok graph"
+ bl_options = {"REGISTER"}
+
+ def _execute(self, context):
+ obj = context.active_object
+ element = tool.Ifc.get_entity(obj)
+ props = obj.BIMSverchokProperties
+ bpy.data.node_groups.remove(props.node_group)
+ return {"FINISHED"}
+
+ def draw(self, context):
+ self.layout.label(text="WARNING. The graph will be removed permamently.")
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self)
+
+
+class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.update_data_from_sverchok"
+ bl_label = "Update data from sverchok"
+ bl_options = {"REGISTER"}
+
+ def invoke(self, context, event):
+ if not context.active_object.BIMSverchokProperties.node_group:
+ return context.window_manager.invoke_props_dialog(self)
+ return self._execute(context)
+
+ def draw(self, context):
+ self.layout.label(text="WARNING. Sverchok data will be emptied")
+ self.layout.label(text="because no sverchok node group is selected.")
+
+ def _execute(self, context):
+ obj = context.active_object
+ element = tool.Ifc.get_entity(obj)
+ props = obj.BIMSverchokProperties
+ node_group = props.node_group
+
+ if node_group:
+ output_node = [
+ n
+ for n in node_group.nodes
+ if n.bl_idname == "SvViewerDrawMk4" and n.label.lower().startswith("ifcoutput")
+ ]
+
+ if not output_node:
+ error_msg = (
+ f"Couldn't find output node in node group {node_group.name}.\n"
+ 'Output node should be type of "Viewer Draw" and it\'s label should start with "ifcoutput".'
+ )
+ self.report({"ERROR"}, (error_msg))
+ return {"CANCELLED"}
+
+ output_node = output_node[0]
+
+ sverchok_data = {
+ "vertices": output_node.inputs["Vertices"].sv_get(deepcopy=False, default=[]),
+ "edges": output_node.inputs["Edges"].sv_get(deepcopy=False, default=[]),
+ "polygons": output_node.inputs["Polygons"].sv_get(deepcopy=False, default=[]),
+ }
+ # sv_get returns array of arrays of 1 element, for convenience we reduce it to just 1 level array
+ def try_first_element(x):
+ return x[0] if x else x
+
+ for key in sverchok_data:
+ sverchok_data[key] = try_first_element(sverchok_data[key])
+ else:
+ sverchok_data = {"vertices": [], "edges": [], "polygons": []}
+
+ print("sverchok data to upload: ", sverchok_data)
+ psets = ifcopenshell.util.element.get_psets(element)
+ pset = psets.get("BBIM_Sverchok", None)
+
+ if pset:
+ pset = tool.Ifc.get().by_id(pset["id"])
+ else:
+ pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Sverchok")
+
+ ifcopenshell.api.run(
+ "pset.edit_pset",
+ tool.Ifc.get(),
+ pset=pset,
+ properties={"Data": json.dumps(sverchok_data)},
+ )
+ update_sverchok_modifier(context)
+
+ return {"FINISHED"}
+
+
+# Basically copied Sverchok SvNodeTreeImporter
+# Wasn't able to use actual SvNodeTreeImporter because it expects node graph to be opened
+class ImportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.import_sverchok_graph"
+ bl_label = "Import sverchok graph"
+ bl_options = {"REGISTER"}
+
+ filepath: bpy.props.StringProperty(
+ name="File Path", description="Filepath used to import from", maxlen=1024, default="", subtype="FILE_PATH"
+ )
+
+ filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
+
+ def _execute(self, context):
+ import sverchok
+
+ importer = sverchok.utils.sv_json_import.JSONImporter.init_from_path(self.filepath)
+ obj = context.active_object
+ props = obj.BIMSverchokProperties
+
+ node_group = context.scene.io_panel_properties.import_tree
+ if not node_group:
+ node_group = props.node_group or bpy.data.node_groups.new("IfcNodeTree", type="SverchCustomTreeType")
+
+ importer.import_into_tree(node_group)
+ if importer.has_fails:
+ self.report({"ERROR"}, importer.fail_massage)
+ props.node_group = node_group
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+ def draw(self, context):
+ col = self.layout.column()
+ col.label(text="Destination tree to import JSON:")
+ col.template_ID(context.scene.io_panel_properties, "import_tree", new="node.new_import_tree")
+
+
+# Basically copied Sverchok SvNodeTreeExporter
+# Wasn't able to use actual SvNodeTreeExporter because it expects node graph to be opened
+class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator):
+ bl_idname = "bim.export_sverchok_graph"
+ bl_label = "Export sverchok graph"
+ bl_options = {"REGISTER"}
+
+ filepath: bpy.props.StringProperty(
+ name="File Path", description="Filepath used for exporting to", maxlen=1024, default="", subtype="FILE_PATH"
+ )
+
+ filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
+
+ compact: bpy.props.BoolProperty(default=True, description="Compact representation of the JSON file")
+ compress: bpy.props.BoolProperty()
+
+ def _execute(self, context):
+ import sverchok
+
+ obj = context.active_object
+ props = obj.BIMSverchokProperties
+ ng = props.node_group
+ destination_path = self.filepath
+ if not destination_path.lower().endswith(".json"):
+ destination_path += ".json"
+
+ # future: should check if filepath is a folder or ends in \
+ layout_dict = sverchok.utils.sv_json_export.JSONExporter.get_tree_structure(ng)
+
+ if not layout_dict:
+ msg = "no update list found - didn't export"
+ self.report({"WARNING"}, msg)
+ return {"CANCELLED"}
+
+ indent = None if self.compact else 2
+ with open(destination_path, "w") as fpo:
+ json.dump(layout_dict, fpo, indent=indent) # json_struct doesn't expect sort_keys = True
+ msg = "exported to: " + destination_path
+ self.report({"INFO"}, msg)
+
+ if self.compress:
+ comp_mode = zipfile.ZIP_DEFLATED
+
+ # destination path = /a../b../c../somename.json
+ base = basename(destination_path) # somename.json
+ basedir = dirname(destination_path) # /a../b../c../
+
+ # somename.zip
+ final_archivename = base.replace(".json", "") + ".zip"
+
+ # /a../b../c../somename.zip
+ fullpath = os.path.join(basedir, final_archivename)
+
+ with zipfile.ZipFile(fullpath, "w", compression=comp_mode) as myzip:
+ myzip.write(destination_path, arcname=base)
+
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+ def draw(self, context):
+ graph_name = context.active_object.BIMSverchokProperties.node_group.name
+ self.layout.label(text=f'Save node tree "{graph_name}" into json:')
+
+ col = self.layout.column(heading="Options") # new syntax in >= 2.90
+ col.use_property_split = True
+ col.prop(self, "compact")
+ col.prop(self, "compress", text="Create ZIP archive")
diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py
index 0341a869f9..846c6ac364 100644
--- a/src/blenderbim/blenderbim/bim/module/model/ui.py
+++ b/src/blenderbim/blenderbim/bim/module/model/ui.py
@@ -19,9 +19,10 @@
import bpy
import blenderbim.tool as tool
from bpy.types import Panel, Operator, Menu
-from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData
+from blenderbim.bim.module.model.data import AuthoringData, ArrayData, StairData, SverchokData
from blenderbim.bim.module.model.prop import store_cursor_position
from blenderbim.bim.module.model.stair import update_stair_modifier
+from blenderbim.bim.module.model.sverchok_modifier import update_sverchok_modifier
from blenderbim.bim.helper import prop_with_search
@@ -368,6 +369,41 @@ class BIM_PT_stair(bpy.types.Panel):
row.operator("bim.add_stair", icon="ADD", text="")
+class BIM_PT_sverchok(bpy.types.Panel):
+ bl_label = "IFC Sverchok"
+ bl_idname = "BIM_PT_sverchok"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "modifier"
+
+ @classmethod
+ def poll(cls, context):
+ # always display modifier if it's IFC object
+ return tool.Ifc.get() and tool.Ifc.get_entity(context.active_object)
+
+ def draw(self, context):
+ if not SverchokData.is_loaded:
+ SverchokData.load()
+
+ self.layout.label(text="Requires SVERCHOK addon to be enabled.", icon="SEQUENCE_COLOR_02")
+
+ props = context.active_object.BIMSverchokProperties
+ self.layout.prop_search(props, "node_group", bpy.data, "node_groups")
+ self.layout.operator("bim.create_new_sverchok_graph", icon="ADD")
+
+ self.layout.operator("bim.update_data_from_sverchok", icon="FILE_REFRESH")
+
+ row = self.layout.row()
+ row.operator("bim.delete_sverchok_graph", icon="X")
+ row.enabled = bool(props.node_group)
+
+ self.layout.operator("bim.import_sverchok_graph", text="Import JSON", icon="RNA")
+
+ row = self.layout.row()
+ row.operator("bim.export_sverchok_graph", text="Export to JSON", icon="FILE_BACKUP")
+ row.enabled = bool(props.node_group)
+
+
class BIM_MT_model(Menu):
bl_idname = "BIM_MT_model"
bl_label = "IFC Objects"