mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user