diff --git a/3dcam-engine-helper.py b/3dcam-engine-helper.py new file mode 100644 index 0000000..1aaa8ce --- /dev/null +++ b/3dcam-engine-helper.py @@ -0,0 +1,187 @@ +import bpy +from bpy.props import IntProperty, StringProperty, PointerProperty, BoolProperty +from bpy.types import PropertyGroup +from bpy.app.handlers import persistent + +bl_info = { + "name": "3Dcam engine custom properties helper", + "author": "Schnappy", + "blender": (2,79,0), + "category": "Property helper", + "version": (0,0,1), + "location": "3D view > Object", + "location": "Properties panel > Data", + "description": "Set/Unset/Copy flags easily" +} +# Based on https://blog.hamaluik.ca/posts/dynamic-blender-properties by Kenton Hamaluik +bpy.propertyGroupLayouts = { + "Flags": [ + { "name": "isAnim", "type": "boolean" }, + { "name": "isProp", "type": "boolean" }, + { "name": "isRigidBody", "type": "boolean" }, + { "name": "isStaticBody","type": "boolean" }, + { "name": "isRound", "type": "boolean" }, + { "name": "isPrism", "type": "boolean" }, + { "name": "isActor", "type": "boolean" }, + { "name": "isLevel", "type": "boolean" }, + { "name": "isWall", "type": "boolean" }, + { "name": "isBG", "type": "boolean" }, + { "name": "isSprite", "type": "boolean" }, + { "name": "isLerp", "type": "boolean" } + ], + "Others": [ + { "name": "mass", "type": "int" } + ] + } +# store keymaps here to access after registration +addon_keymaps = [] +bpy.samplePropertyGroups = {} +last_selection = [] +store_att_names = [] + +def getActiveObjProps(active_obj): + object_custom_props = [prop for prop in store_att_names if prop in active_obj.data] + return object_custom_props + +def menu_func(self, context): + self.layout.operator(copyCustomPropToSelection.bl_idname) + +def copyCustomProps(context): + # get active object + active_obj = bpy.context.active_object + # get active object's custom properties + active_obj_custom_props = getActiveObjProps(active_obj) + # get selected objects + selection = bpy.context.selected_objects + # discriminates against active_obj + selection = [obj for obj in selection if obj != active_obj] + # for each object that's not active object, add custom prop + for obj in selection: + for prop in active_obj_custom_props: + obj.data[prop] = active_obj.data[prop] + +def updateCustomProps(self, context): + global last_selection + global store_att_names + for att in store_att_names: + if (att in last_selection.Flags): # and last_selection.Flags[att] : + if ( att not in last_selection.data or + last_selection.Flags[att] != last_selection.data[att] ): + last_selection.data[att] = last_selection.Flags[att] + if (att in last_selection.Others): + if ( att not in last_selection.data or + last_selection.Others[att] != last_selection.data[att] ): + last_selection.data[att] = last_selection.Others[att] +@persistent +def selection_callback(scene): + global last_selection + global store_att_names + print("1 " + str(last_selection)) + print("2 " + str(bpy.context.active_object)) + if bpy.context.active_object != last_selection: + last_selection = bpy.context.active_object + for groupName, attributeDefinitions in bpy.propertyGroupLayouts.items(): + # build the attribute dictionary for this group + attributes = {} + for attributeDefinition in attributeDefinitions: + attType = attributeDefinition['type'] + attName = attributeDefinition['name'] + store_att_names.append(attName) + value = 0 + if last_selection: + if attName in last_selection.data: + value = last_selection.data[attName] + if attType == 'boolean': + print(attName + ":" + str(value)) + attributes[attName] = BoolProperty(name=attName, default=value, update=updateCustomProps ) + elif attType == 'int': + attributes[attName] = IntProperty(name=attName, default=value, update=updateCustomProps) + elif attType == 'string': + attributes[attName] = StringProperty(name=attName, default=value, update=updateCustomProps) + else: + raise TypeError('Unsupported type (%s) for %s on %s!' % (attType, attName, groupName)) + # now build the property group class + propertyGroupClass = type(groupName, (PropertyGroup,), attributes) + # register it with Blender + bpy.utils.register_class(propertyGroupClass) + # apply it to all Objects + setattr(bpy.types.Object, groupName, PointerProperty(type=propertyGroupClass)) + # store it for later + bpy.samplePropertyGroups[groupName] = propertyGroupClass + +class copyCustomPropToSelection(bpy.types.Operator): + """Copy last selected object's custom properties to other selected objects""" + bl_idname = "object.copy_custom_properties" + bl_label = "Copy custom properties to selection" + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + copyCustomProps(context) + return {'FINISHED'} + +class customPropsPanel(bpy.types.Panel): + bl_label = "3D Cam engine custom properties" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + + def draw(self, context): + layout = self.layout + obj = context.object + # use our layout definition to dynamically create our panel items + for groupName, attributeDefinitions in sorted(bpy.propertyGroupLayouts.items()): + # get the instance of our group + # dynamic equivalent of `obj.samplePropertyGroup` from before + propertyGroup = getattr(obj, groupName) + # start laying this group out + col = layout.column_flow(columns=2) + col.label(groupName) + i = 0 + # loop through all the attributes and show them + for attributeDefinition in attributeDefinitions: + if i == len(attributeDefinitions)/2: + col.label("") + col.prop(propertyGroup, attributeDefinition["name"]) + i += 1 + # draw a separation between groups + layout.separator() + +def register(): + # register the panel class + bpy.utils.register_class(customPropsPanel) + bpy.app.handlers.scene_update_post.clear() + bpy.app.handlers.scene_update_post.append(selection_callback) + # copy helper + bpy.utils.register_class(copyCustomPropToSelection) + bpy.types.VIEW3D_MT_object.append(menu_func) + # shortcut + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') + kmi = km.keymap_items.new(copyCustomPropToSelection.bl_idname, 'P', 'PRESS', ctrl=False, shift=True) + # ~ kmi.properties.total = 4 + addon_keymaps.append((km, kmi)) + +def unregister(): + # unregister the panel class + bpy.utils.unregister_class(customPropsPanel) + # unregister our components + try: + for key, value in bpy.samplePropertyGroups.items(): + delattr(bpy.types.Object, key) + bpy.utils.unregister_class(value) + except UnboundLocalError: + pass + bpy.samplePropertyGroups = {} + # copy helper + bpy.utils.unregister_class(copyCustomPropToSelection) + bpy.types.VIEW3D_MT_object.remove(menu_func) + # handle the keymap + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() + +if __name__ == "__main__": + register() diff --git a/README.md b/README.md index e471a61..e482968 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,15 @@ Following [those steps](https://github.com/ABelliqueux/3dcam-headers#compiling), # Custom properties helper add-on -A [small blender addon](https://github.com/ABelliqueux/blender_io_export_psx_mesh/blob/main/copy_custom_prop.py) is provided that facilitates copying [flags](https://github.com/ABelliqueux/blender_io_export_psx_mesh/wiki/Flags) between several objects in your scene. +## 3dcam-helper -Once enabled, first select the objects you want to copy the flags to, then the object that has the flags you want copied, then access the functionality via the `Object` menu in the 3D viewport, or use the `Shift+P` keyboard shortcut. +A [small blender addon](https://github.com/ABelliqueux/blender_io_export_psx_mesh/blob/main/3dcam-engine-helper.py) is provided that facilitates setting and copying [flags](https://github.com/ABelliqueux/blender_io_export_psx_mesh/wiki/Flags) between several objects in your scene. + +![Setting an object's flags](https://wiki.arthus.net/assets/3dcam-helper-flags.gif) + +See [the documentation](https://github.com/ABelliqueux/blender_io_export_psx_mesh/wiki/Flags#3dcam-helper) for usage instruction. + +**The script only does the job of creating/updating the object's custom properties, so it is not mandatory to use it.** # Credits diff --git a/copy_custom_prop.py b/copy_custom_prop.py deleted file mode 100644 index 1addec4..0000000 --- a/copy_custom_prop.py +++ /dev/null @@ -1,85 +0,0 @@ -import bpy - -bl_info = { - "name": "Copy custom properties", - "author": "Schnappy", - "blender": (2,79,0), - "category": "Property helper", - "version": (0,0,1), - "location": "3D view > Object", - "description": "Copy custom property from last selected object to other objects in selection" -} -# store keymaps here to access after registration -addon_keymaps = [] -custom_props = [ 'isAnim', - 'isProp', - 'isRigidBody', - 'isStaticBody', - 'isRound', - 'isPrism', - 'isActor', - 'isLevel', - 'isWall', - 'isBG', - 'isSprite', - 'mass', - 'restitution', - 'lerp' - ] - -def getActiveObjProps(active_obj): - object_custom_props = [prop for prop in custom_props if prop in active_obj.data] - return object_custom_props - -def main(context): - # get active object - active_obj = bpy.context.active_object - # get active object's custom properties - active_obj_custom_props = getActiveObjProps(active_obj) - # get selected objects - selection = bpy.context.selected_objects - # discriminates against active_obj - selection = [obj for obj in selection if obj != active_obj] - # for each object that's not active object, add custom prop - for obj in selection: - for prop in active_obj_custom_props: - obj.data[prop] = active_obj.data[prop] - -class copyCustomPropToSelection(bpy.types.Operator): - """Copy last selected object's custom properties to other selected objects""" - bl_idname = "object.copy_custom_properties" - bl_label = "Copy custom properties to selection" - - @classmethod - def poll(cls, context): - return context.active_object is not None - - def execute(self, context): - main(context) - return {'FINISHED'} - -def menu_func(self, context): - self.layout.operator(copyCustomPropToSelection.bl_idname) - -def register(): - bpy.utils.register_class(copyCustomPropToSelection) - bpy.types.VIEW3D_MT_object.append(menu_func) - - # shortcut - wm = bpy.context.window_manager - km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY') - - kmi = km.keymap_items.new(copyCustomPropToSelection.bl_idname, 'P', 'PRESS', ctrl=False, shift=True) - # ~ kmi.properties.total = 4 - addon_keymaps.append((km, kmi)) - -def unregister(): - bpy.utils.unregister_class(copyCustomPropToSelection) - bpy.types.VIEW3D_MT_object.remove(menu_func) - # handle the keymap - for km, kmi in addon_keymaps: - km.keymap_items.remove(kmi) - addon_keymaps.clear() - -if __name__ == "__main__": - register() diff --git a/startfile.blend b/startfile.blend index a162635..499e514 100644 Binary files a/startfile.blend and b/startfile.blend differ