Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion import_3dm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ class Import3dm(Operator, ImportHelper):
default=10,
min=1,
)

create_instance_files: BoolProperty(
name="Auto-Create Linked Block Files",
description="Atomatically convert each .3dm block file into a .blend file. (SAVE BEFORE ENABLING THIS)",
default=False,
)

overwrite: BoolProperty(
name="Overwrite",
description="Overwrite existing .blend files that contain block definitions.",
default=False,
)

update_materials: BoolProperty(
name="Update Materials",
Expand All @@ -121,6 +133,12 @@ class Import3dm(Operator, ImportHelper):
)

def execute(self, context):
# Check if the file is unsaved and create_instance_files is True
# Abort operation
if bpy.data.filepath == "" and self.create_instance_files:
self.report({'ERROR_INVALID_INPUT'}, "Save your file before trying to create any linked block files.")
return {'CANCELLED'}

options = {
"filepath":self.filepath,
"import_views":self.import_views,
Expand All @@ -133,8 +151,10 @@ def execute(self, context):
"import_instances":self.import_instances,
"import_instances_grid_layout":self.import_instances_grid_layout,
"import_instances_grid":self.import_instances_grid,
"create_instance_files":self.create_instance_files,
"overwrite":self.overwrite,
}
return read_3dm(context, options)
return read_3dm(context, options, block_toggle = True)

def draw(self, context):
layout = self.layout
Expand Down Expand Up @@ -163,6 +183,17 @@ def draw(self, context):
box.prop(self, "import_instances")
box.prop(self, "import_instances_grid_layout")
box.prop(self, "import_instances_grid")

nested_box = box.box()
nested_box.label(text="Linked Blocks (Save before using!)")
nested_box.prop(self, "create_instance_files")
nested_box.prop(self, "overwrite")

# # Check if the file is unsaved
# if bpy.data.is_dirty:
# box = layout.box()
# box.label(text="Unsaved File")
# box.operator("wm.save_mainfile", text="Save")

box = layout.box()
box.label(text="Materials")
Expand Down
42 changes: 39 additions & 3 deletions import_3dm/converters/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import bpy
import rhino3dm as r3d
import os
from mathutils import Matrix
from mathutils import Vector
from math import sqrt
Expand All @@ -34,7 +35,7 @@
#proper exception handling


def handle_instance_definitions(context, model, toplayer, layername):
def handle_instance_definitions(context, model, toplayer, layername, filepath3dm):
"""
import instance definitions from rhino model as empty collections
"""
Expand All @@ -44,15 +45,50 @@ def handle_instance_definitions(context, model, toplayer, layername):
instance_col.hide_render = True
instance_col.hide_viewport = True
toplayer.children.link(instance_col)


instance_properties = []

for idef in model.InstanceDefinitions:
idef_col=utils.get_iddata(context.blend_data.collections,idef.Id, idef.Name, None )
idef_col = utils.get_iddata(context.blend_data.collections, idef.Id, idef.Name, None)

try:
instance_col.children.link(idef_col)
except Exception:
pass

name = idef.Name

if os.path.isfile(idef.SourceArchive):
source_archive = idef.SourceArchive
# Look for file in sub-directories
elif str(idef.UpdateType) != "InstanceDefinitionUpdateType.Static":
name3dm = os.path.basename(idef.SourceArchive)
dirname = os.path.dirname(filepath3dm)
match = False
for root, dirs, files in os.walk(dirname):
for file in files:
if ".3dm" in file:
if name3dm.lower() == file.lower():
source_archive = os.path.join(root, file)
print("Changed source archive from " + idef.SourceArchive + " to " + source_archive)
match = True
break
if match:
break
if not match:
source_archive = idef.SourceArchive
print("File \"" + name3dm + "\" could not be found!")
else:
source_archive = ""

update_type = idef.UpdateType

# Save relevant block data for handling of linked block files
instance_dict = {"Name":name, "SourceArchive":source_archive, "UpdateType":update_type}
instance_properties.append(instance_dict)

return instance_properties

def import_instance_reference(context, ob, iref, name, scale, options):
#TODO: insert reduced mesh proxy and hide actual instance in viewport for better performance on large files
iref.empty_display_size=0.5
Expand Down
160 changes: 146 additions & 14 deletions import_3dm/read3dm.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,45 @@ def install_dependencies():
try:
import rhino3dm as r3d
except:
print("Failed to load rhino3dm, trying to install automatically...")
try:
install_dependencies()
# let user restart Blender, reloading of rhino3dm after automated
# install doesn't always work, better to just fail clearly before
# that
raise Exception("Please restart Blender.")
except:
raise
if not sys.platform in ('darwin', 'win32'):
print("Failed to load rhino3dm, trying to install automatically...")
try:
install_dependencies()
# let user restart Blender, reloading of rhino3dm after automated
# install doesn't always work, better to just fail clearly before
# that
raise Exception("Please restart Blender.")
except:
raise

from . import converters

def clear_memory():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

bpy.ops.collection.select_all(action='SELECT')
bpy.ops.collection.delete()

bpy.ops.material.select_all(action='SELECT')
bpy.ops.material.delete()

bpy.ops.text.select_all(action='SELECT')
bpy.ops.text.delete()

bpy.ops.image.select_all(action='SELECT')
bpy.ops.image.delete()

def read_3dm(context, options):
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete()

def read_3dm(context, options, block_toggle):

filepath = options.get("filepath", "")
if block_toggle != False:
initial_file_3dm = filepath
initial_file_blend = bpy.data.filepath
initial_import_instances = options.get("import_instances",False)
model = None

try:
Expand Down Expand Up @@ -158,9 +181,10 @@ def read_3dm(context, options):
import_groups = options.get("import_groups", False)
import_nested_groups = options.get("import_nested_groups", False)
import_instances = options.get("import_instances",False)
create_instance_files = options.get("create_instance_files", False)
overwrite = options.get("overwrite", False)
update_materials = options.get("update_materials", False)


# Import Views and NamedViews
if import_views:
converters.handle_views(context, model, toplayer, model.Views, "Views", scale)
Expand All @@ -175,8 +199,8 @@ def read_3dm(context, options):
materials[converters.DEFAULT_RHINO_MATERIAL] = None

#build skeletal hierarchy of instance definitions as collections (will be populated by object importer)
if import_instances:
converters.handle_instance_definitions(context, model, toplayer, "Instance Definitions")
if import_instances: #or create_instance_files:
instance_properties = converters.handle_instance_definitions(context, model, toplayer, "Instance Definitions", initial_file_3dm)

# Handle objects
for ob in model.Objects:
Expand Down Expand Up @@ -252,5 +276,113 @@ def read_3dm(context, options):
bpy.ops.object.shade_smooth({'selected_editable_objects': toplayer.all_objects})
except Exception:
pass


# Create Blender files for each linked block instance
if create_instance_files and block_toggle:
for instance in instance_properties:
if str(instance['UpdateType']) != "InstanceDefinitionUpdateType.Static":

# Skip file creation if overwrite toggle is disabled
if os.path.isfile(instance['SourceArchive'].replace("3dm", "blend")) and not overwrite:
pass
else:
filepath = instance['SourceArchive']
options["filepath"] = filepath
options["import_instances"] = False
try:
nuke_everything(50)
read_3dm(context, options, block_toggle=False)
except Exception:
print("Failed to create file: ", filepath)

if create_instance_files:

# Save .blend file and delete its contents before moving on to the next
if block_toggle == False:
filepath_blend = filepath.replace('.3dm', '.blend')
bpy.ops.wm.save_as_mainfile(filepath=filepath_blend)
nuke_everything(50)

# Import the original model
elif block_toggle == True:
# Open the original Blender file
bpy.ops.wm.open_mainfile(filepath=initial_file_blend)
options["filepath"] = initial_file_3dm
options["import_instances"] = initial_import_instances
read_3dm(context, options, block_toggle = None)

# Link all block files after importing the original model
elif block_toggle == None:
link_all(instance_properties)

# Only link block files
if import_instances and not create_instance_files:
link_all(instance_properties)
if block_toggle:
print("Import complete!")
return {'FINISHED'}


# Delete all objects and collections and purge orphan data as many times as needed
# amount value is currently a workaround because I couldn't find a way to purge everything elegantly
def nuke_everything(amount):

# Delete all world data
for world in bpy.data.worlds:
bpy.data.worlds.remove(world)

# Create a new world shader and set it as active
new_world = bpy.data.worlds.new(name="World")
bpy.context.scene.world = new_world

for data in range(amount):

# Select all collections
bpy.ops.outliner.orphans_purge()

# Get the root collection
root_collection = bpy.context.scene.collection

# Recursively delete all collections except the root collection
for collection in root_collection.children:
bpy.data.collections.remove(collection, do_unlink=True)


# Locates files corresponding to Instance Definitions and populates instances through linking
def link_all(instance_properties):
failed_link_paths = []
failed_link_collections = []
for instance in instance_properties:
if str(instance['UpdateType']) == "InstanceDefinitionUpdateType.Linked":
source_archive = instance['SourceArchive']

for ob in bpy.data.objects:
if ob.name.endswith("_Instance"):
existing_collection_name = instance['Name']

if ob.name == existing_collection_name + "_Instance":
file_path = instance['SourceArchive'].replace(".3dm",".blend")
existing_collection = bpy.data.collections.get(existing_collection_name)
linked_collection_name = os.path.basename(instance['SourceArchive']).split('/')[-1].replace(".3dm","")

try:
with bpy.data.libraries.load(file_path) as (data_from, data_to):
data_to.collections = [linked_collection_name]

for collection in data_to.collections:
if existing_collection:
existing_collection.children.link(collection)
else:
bpy.context.scene.collection.children.link(collection)

print("The Collection \"" + linked_collection_name + "\" was successfully linked!")
except Exception:
print("Failed to link collection \"" + linked_collection_name + "\"")
failed_link_paths.append(file_path)
failed_link_collections.append(linked_collection_name)

if failed_link_paths is not None:
for i in range(len(failed_link_paths)):
print("Couldn't link collection \"" + failed_link_collections[i] + "\" at location: " + failed_link_paths[i])
else:
print("All Collections have been successfully linked!")