Added base for ifc sverchok modifier

It's located in "Modifiers" section of the properties (the same way as IFC Array or IFC Stair).
It requires Sverchok addon to be enabled (it's lazy importing it when it's needed, so there is no new constant dependency).

What you can now do with it:
1) Add sverchok graphs to this modifier and store simple data from sverchok in IFC ("Update data from sverchok). The "output" node of the sverchok graph has to be type of "Viewer Draw" and it's name should start with "ifcoutput" (no case-sensitive).
2) Import/export json of sverchok graphs.
This commit is contained in:
Andrej730
2022-12-02 19:41:34 +06:00
parent d11959d303
commit 0b6c8fe83e
5 changed files with 388 additions and 3 deletions
@@ -17,7 +17,22 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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)
@@ -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
@@ -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)
@@ -0,0 +1,300 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>, @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 <http://www.gnu.org/licenses/>.
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")
@@ -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"