From 0b51b31b7b0f735444d0119a6c9921492affe443 Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 16 Dec 2023 13:48:01 +0100 Subject: [PATCH] Allow MVR reimport --- __init__.py | 21 ++++++--- group.py | 1 + mvr.py | 110 ++++++++++++++++++++++++++++++++++----------- mvr_objects.py | 56 +++++++++++++++++++++++ panels/fixtures.py | 1 + pymvr/__init__.py | 22 +++++++-- pymvr/value.py | 10 +++++ 7 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 mvr_objects.py diff --git a/__init__.py b/__init__.py index b8b794d1..265a6878 100644 --- a/__init__.py +++ b/__init__.py @@ -45,6 +45,7 @@ from dmx.osc import DMX_OSC from dmx.util import rgb_to_cmy, xyY2rgbaa +from dmx.mvr_objects import DMX_MVR_Object from bpy.props import (BoolProperty, StringProperty, @@ -93,6 +94,7 @@ class DMX(PropertyGroup): DMX_Emitter_Material, DMX_Fixture_Channel, DMX_Fixture, + DMX_MVR_Object, DMX_Group, DMX_Universe, DMX_Value, @@ -216,6 +218,10 @@ def unregister(): name = "DMX Groups", type = DMX_Universe) + mvr_objects: CollectionProperty( + name = "MVR Objects", + type = DMX_MVR_Object) + def prepare_empty_buffer(self, context): # Clear the buffer on change of every protocol DMX_Data.prepare_empty_buffer() @@ -876,8 +882,8 @@ def syncProgrammer(self): ) # Kernel Methods # # Fixtures - def addFixture(self, name, profile, universe, address, mode, gel_color, display_beams, add_target, position=None, focus_point=None, uuid = None, fixture_id="", custom_id=0, fixture_id_numeric=0, unit_number=0): + # TODO: fix order of attributes to match fixture.build() bpy.app.handlers.depsgraph_update_post.clear() dmx = bpy.context.scene.dmx dmx.fixtures.add() @@ -930,8 +936,13 @@ def addMVR(self, file_name): extract_mvr_textures(mvr_scene, media_folder_path) for layer_index, layer in enumerate(mvr_scene.layers): - layer_collection = bpy.data.collections.new(layer.name or f"Layer {layer_index}") - bpy.context.scene.collection.children.link(layer_collection) + + layer_collection_name = layer.name or f"Layer {layer_index}" + if layer_collection_name in bpy.context.scene.collection.children: + layer_collection = bpy.context.scene.collection.children[layer_collection_name] + else: + layer_collection = bpy.data.collections.new(layer.name or f"Layer {layer_index}") + bpy.context.scene.collection.children.link(layer_collection) g_name = layer.name or "Layer" g_name = f"{g_name} {layer_index}" @@ -948,7 +959,7 @@ def addMVR(self, file_name): fixture_group ) self.clean_up_empty_mvr_collections(layer_collection) - if len(layer_collection.children) == 0: + if len(layer_collection.all_objects) == 0: bpy.context.scene.collection.children.unlink(layer_collection) bpy.context.window_manager.dmx.pause_render = False # re-enable render loop @@ -957,7 +968,7 @@ def addMVR(self, file_name): def clean_up_empty_mvr_collections(self,collections): for collection in collections.children: - if len(collection.children) == 0: + if len(collection.all_objects) == 0: collections.children.unlink(collection) def ensureUniverseExists(self, universe): diff --git a/group.py b/group.py index 251dae4b..90bf3312 100644 --- a/group.py +++ b/group.py @@ -56,6 +56,7 @@ def update(self): # and repopulate it if (len(sel_fixtures)): #self.runtime[self.name] = sel_fixtures; + # TODO: it would be better to use fixture UUID self.dump = json.dumps([fixture.name for fixture in sel_fixtures]) else: self.dump = '' diff --git a/mvr.py b/mvr.py index 36a453fe..91074a80 100644 --- a/mvr.py +++ b/mvr.py @@ -7,6 +7,7 @@ import hashlib import json from dmx.group import FixtureGroup +from dmx.mvr_objects import DMX_MVR_Object # importing from dmx didn't work, had to duplicate this function @@ -33,8 +34,8 @@ def onDepsgraph(scene): def process_mvr_child_list(dmx, child_list, layer_index, extract_to_folder_path, mvr_scene, already_extracted_files, layer_collection, fixture_group=None): - if "MVR Trusses" in layer_collection: - truss_collection = layer_collection["MVR Trusses"] + if "MVR Trusses" in layer_collection.children: + truss_collection = layer_collection.children["MVR Trusses"] else: truss_collection = bpy.data.collections.new("MVR Trusses") layer_collection.children.link(truss_collection) @@ -65,8 +66,8 @@ def process_mvr_child_list(dmx, child_list, layer_index, extract_to_folder_path, fixture_group, ) - if "MVR Scene objects" in layer_collection: - scene_collection_top = layer_collection["MVR Scene objects"] + if "MVR Scene objects" in layer_collection.children: + scene_collection_top = layer_collection.children["MVR Scene objects"] else: scene_collection_top = bpy.data.collections.new("MVR Scene objects") layer_collection.children.link(scene_collection_top) @@ -90,6 +91,7 @@ def process_mvr_child_list(dmx, child_list, layer_index, extract_to_folder_path, scene_collection = bpy.data.collections.new(scene_name) scene_collection_top.children.link(scene_collection) collection = scene_collection + print("creating extra collection", scene_name) process_mvr_object( mvr_scene, @@ -163,12 +165,31 @@ def process_mvr_object( folder = os.path.join(current_path, "assets", "models", "mvr") name = mvr_object.name + dmx = bpy.context.scene.dmx + previous_mvr_object = None + for existing_mvr_object in dmx.mvr_objects: + if existing_mvr_object.uuid == mvr_object.uuid: + previous_mvr_object = existing_mvr_object + print("Updating existing mvr object") + for child in existing_mvr_object.collection.children: + for obj in child.objects: + bpy.data.objects.remove(obj) + break + if previous_mvr_object: + dmx_mvr_object = previous_mvr_object + else: + dmx_mvr_object = dmx.mvr_objects.add() + dmx_mvr_object.name = name + dmx_mvr_object.object_type = mvr_object.__class__.__name__ + dmx_mvr_object.uuid = mvr_object.uuid + dmx_mvr_object.collection = bpy.data.collections.new(mvr_object.uuid) + for geometry in geometry3ds: if geometry.file_name: file = geometry.file_name local_transform = geometry.matrix.matrix extract_mvr_object(file, mvr_scene, folder, already_extracted_files) - add_mvr_object( + coll = add_mvr_object( name, file, folder, @@ -179,6 +200,8 @@ def process_mvr_object( group_collection, mvr_object, ) + if coll: + dmx_mvr_object.collection.children.link(coll) for symbol in symbols: symdefs = [sd for sd in mvr_scene.aux_data.symdefs if sd.uuid == symbol.symdef] @@ -189,7 +212,7 @@ def process_mvr_object( local_transform = geometry.matrix.matrix extract_mvr_object(file, mvr_scene, folder, already_extracted_files) - add_mvr_object( + coll = add_mvr_object( name, file, folder, @@ -200,6 +223,8 @@ def process_mvr_object( group_collection, symbol, ) + if coll: + dmx_mvr_object.collection.children.link(coll) def extract_mvr_object(file, mvr_scene, folder, already_extracted_files): @@ -280,7 +305,6 @@ def add_mvr_object( ob.rotation_mode = "XYZ" ob.rotation_euler = Matrix(local_transform).to_euler("XYZ") ob["file name"] = file - ob["uuid"] = mvr_object.uuid ob.matrix_world = global_transform # ob.location = Matrix(global_transform).to_translation() @@ -298,9 +322,13 @@ def add_mvr_object( ob.scale[2] *= 0.001 object_collection.objects.link(ob) - group_collection.children.link(object_collection) - # bpy.app.handlers.depsgraph_update_post.append(onDepsgraph) - print("MVR object loaded in %.4f sec." % (time.time() - start_time)) + + if len(object_collection.children) + len(object_collection.objects): + group_collection.children.link(object_collection) + print("MVR object loaded in %.4f sec." % (time.time() - start_time)) + return object_collection + + return None def add_mvr_fixture( @@ -315,6 +343,14 @@ def add_mvr_fixture( fixture_group=None, ): """Add fixture to the scene""" + + existing_fixture = None + for _fixture in dmx.fixtures: + if _fixture.uuid == fixture.uuid: + existing_fixture = _fixture + print(f"Update existing fixture {fixture.uuid}") + break + if f"{fixture.gdtf_spec}" in mvr_scene._package.namelist(): if fixture.gdtf_spec not in already_extracted_files.keys(): mvr_scene._package.extract(fixture.gdtf_spec, extract_to_folder_path) @@ -326,23 +362,43 @@ def add_mvr_fixture( fixture.gdtf_spec = "BlenderDMX@LED_PAR_64_RGBW@v0.3.gdtf" dmx.ensureUniverseExists(fixture.addresses[0].universe) - dmx.addFixture( - f"{fixture.name} {layer_index}-{fixture_index}", - fixture.gdtf_spec, - fixture.addresses[0].universe, - fixture.addresses[0].address, - fixture.gdtf_mode, - xyY2rgbaa(fixture.color), - True, - True, - position=fixture.matrix.matrix, - focus_point=focus_point, - uuid=fixture.uuid, - fixture_id=fixture.fixture_id, - custom_id=fixture.custom_id, - fixture_id_numeric=fixture.fixture_id_numeric, - unit_number=fixture.unit_number, - ) + + if existing_fixture is not None: + existing_fixture.build( + f"{fixture.name} {layer_index}-{fixture_index}", + fixture.gdtf_spec, + fixture.gdtf_mode, + fixture.addresses[0].universe, + fixture.addresses[0].address, + xyY2rgbaa(fixture.color), + True, + True, + mvr_position=fixture.matrix.matrix, + focus_point=focus_point, + uuid=fixture.uuid, + fixture_id=fixture.fixture_id, + custom_id=fixture.custom_id, + fixture_id_numeric=fixture.fixture_id_numeric, + unit_number=fixture.unit_number, + ) + else: + dmx.addFixture( + f"{fixture.name} {layer_index}-{fixture_index}", + fixture.gdtf_spec, + fixture.addresses[0].universe, + fixture.addresses[0].address, + fixture.gdtf_mode, + xyY2rgbaa(fixture.color), + True, + True, + position=fixture.matrix.matrix, + focus_point=focus_point, + uuid=fixture.uuid, + fixture_id=fixture.fixture_id, + custom_id=fixture.custom_id, + fixture_id_numeric=fixture.fixture_id_numeric, + unit_number=fixture.unit_number, + ) if fixture_group is not None: fixture_name = f"{fixture.name} {layer_index}-{fixture_index}" diff --git a/mvr_objects.py b/mvr_objects.py new file mode 100644 index 00000000..a055043a --- /dev/null +++ b/mvr_objects.py @@ -0,0 +1,56 @@ +# +# BlendexDMX > MVR Objects +# +# http://www.github.com/open-stage/BlenderDMX +# + +import bpy +import math +import mathutils +import uuid + +import json +from bpy.props import (IntProperty, + FloatProperty, + BoolProperty, + FloatVectorProperty, + PointerProperty, + StringProperty, + CollectionProperty) + +from bpy.types import (PropertyGroup, + Collection, + Object, + Material) + +class DMX_MVR_Object(PropertyGroup): + """Universal MVR object... in the future, make this specific + SceneObject, Truss, Layer...""" + + name: StringProperty( + name = "Name", + description = "Name", + default = "" + ) + + collection: PointerProperty( + name = "Collection of objects", + type = Collection) + + uuid: StringProperty( + name = "UUID", + description = "UUID", + default = str(uuid.uuid4()) + ) + + object_type: StringProperty( + name = "Object type", + description = "Simple object classification", + default = "SceneObject" #Layer, Truss, + ) + classing: StringProperty( + name = "Classing", + description = "Grouping/Layering", + default = "" + ) + diff --git a/panels/fixtures.py b/panels/fixtures.py index 202ef196..0c358e2e 100644 --- a/panels/fixtures.py +++ b/panels/fixtures.py @@ -298,6 +298,7 @@ def execute(self, context): dmx = scene.dmx selected = scene.dmx.selectedFixtures() context.window_manager.dmx.pause_render = True # pause renderer as partially imported fixture can cause issues during updates + # TODO: handle re-grouping if fixture name changed # Single fixture if (len(selected) == 1): fixture = selected[0] diff --git a/pymvr/__init__.py b/pymvr/__init__.py index 7dba9e18..2f0b74ac 100644 --- a/pymvr/__init__.py +++ b/pymvr/__init__.py @@ -560,15 +560,17 @@ def _read_xml(self, xml_node: "Element"): self.uuid = xml_node.attrib.get("uuid") self.symbol = [Symbol(xml_node=i) for i in xml_node.findall("Symbol")] - self.geometry3d = [Geometry3D(xml_node=i) for i in xml_node.findall("Geometry3D")] + _geometry3d = [Geometry3D(xml_node=i) for i in xml_node.findall("Geometry3D")] if xml_node.find("ChildList"): child_list = xml_node.find("ChildList") symbols = [Symbol(xml_node=i) for i in child_list.findall("Symbol")] geometry3ds = [Geometry3D(xml_node=i) for i in child_list.findall("Geometry3D")] - self.symbol += symbols # TODO remove this over time, children should only be in the child_list - self.geometry3d += geometry3ds + self.symbol += symbols + _geometry3d += geometry3ds + # sometimes the list of geometry3d is full of duplicates, eliminate them here + self.geometry3d = list(set(_geometry3d)) class Geometry3D(BaseNode): def __init__( @@ -588,7 +590,19 @@ def _read_xml(self, xml_node: "Element"): self.matrix = Matrix(str_repr=xml_node.find("Matrix").text) def __str__(self): - return f"{self.file_name}" + return f"{self.file_name} {self.matrix}" + + def __repr__(self): + return f"{self.file_name} {self.matrix}" + + def __eq__(self, other): + return self.file_name == other.file_name and self.matrix == other.matrix + + def __ne__(self, other): + return self.file_name != other.file_name or self.matrix != other.matrix + + def __hash__(self): + return hash((self.file_name, str(self.matrix))) class Symbol(BaseNode): diff --git a/pymvr/value.py b/pymvr/value.py index 04bbfda6..a510175d 100644 --- a/pymvr/value.py +++ b/pymvr/value.py @@ -77,7 +77,17 @@ def __init__(self, str_repr): [component[6], component[7], component[8], 0], [component[9] * 0.001, component[10] * 0.001, component[11] * 0.001, 0], ] + def __eq__(self, other): + return self.matrix == other.matrix + def __ne__(self, other): + return self.matrix == other.matrix + + def __str__(self): + return f"{self.matrix}" + + def __repr__(self): + return f"{self.matrix}" # A node link represents a link to another node in the XML tree, starting from # start_point and traversing the tree with a decimal-point notation in str_link.