diff --git a/leenkx/Sources/iron/Scene.hx b/leenkx/Sources/iron/Scene.hx index 75b1f62..3f8e174 100644 --- a/leenkx/Sources/iron/Scene.hx +++ b/leenkx/Sources/iron/Scene.hx @@ -783,6 +783,11 @@ class Scene { if (o.tilesheet_ref != null) { cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref); } + + if (o.camera_list != null){ + cast(object, MeshObject).cameraList = o.camera_list; + } + returnObject(object, o, done); }); } diff --git a/leenkx/Sources/iron/data/SceneFormat.hx b/leenkx/Sources/iron/data/SceneFormat.hx index a499d3f..fb5619b 100644 --- a/leenkx/Sources/iron/data/SceneFormat.hx +++ b/leenkx/Sources/iron/data/SceneFormat.hx @@ -441,6 +441,7 @@ typedef TObj = { @:optional public var traits: Array; @:optional public var properties: Array; @:optional public var vertex_groups: Array; + @:optional public var camera_list: Array; @:optional public var constraints: Array; @:optional public var dimensions: Float32Array; // Geometry objects @:optional public var object_actions: Array; diff --git a/leenkx/Sources/iron/object/MeshObject.hx b/leenkx/Sources/iron/object/MeshObject.hx index 4711a06..456d106 100644 --- a/leenkx/Sources/iron/object/MeshObject.hx +++ b/leenkx/Sources/iron/object/MeshObject.hx @@ -24,6 +24,7 @@ class MeshObject extends Object { public var render_emitter = true; #end public var cameraDistance: Float; + public var cameraList: Array = null; public var screenSize = 0.0; public var frustumCulling = true; public var activeTilesheet: Tilesheet = null; @@ -235,6 +236,8 @@ class MeshObject extends Object { if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return; var meshContext = raw != null ? context == "mesh" : false; + if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return; + #if lnx_particles if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner if (particleSystems != null && meshContext) { @@ -245,6 +248,7 @@ class MeshObject extends Object { Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) { if (o != null) { var c: MeshObject = cast o; + c.cameraList = this.cameraList; particleChildren.push(c); c.particleOwner = this; c.particleIndex = particleChildren.length - 1; diff --git a/leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx b/leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx new file mode 100644 index 0000000..6342642 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetCameraRenderFilterNode.hx @@ -0,0 +1,19 @@ +package leenkx.logicnode; + +import iron.object.MeshObject; +import iron.object.CameraObject; + +class GetCameraRenderFilterNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var mo: MeshObject = cast inputs[0].get(); + + if (mo == null) return null; + + return mo.cameraList; + } +} \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx b/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx new file mode 100644 index 0000000..f894e28 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetCameraRenderFilterNode.hx @@ -0,0 +1,38 @@ +package leenkx.logicnode; + +import iron.object.MeshObject; +import iron.object.CameraObject; + +class SetCameraRenderFilterNode extends LogicNode { + + public var property0: String; + + public function new(tree: LogicTree) { + super(tree); + } + + override function run(from: Int) { + var mo: MeshObject = cast inputs[1].get(); + var camera: CameraObject = inputs[2].get(); + + assert(Error, Std.isOfType(camera, CameraObject), "Camera must be a camera object!"); + + if (camera == null || mo == null) return; + + if (property0 == 'Add'){ + if (mo.cameraList == null || mo.cameraList.indexOf(camera.name) == -1){ + if (mo.cameraList == null) mo.cameraList = []; + mo.cameraList.push(camera.name); + } + } + else{ + if (mo.cameraList != null){ + mo.cameraList.remove(camera.name); + if (mo.cameraList.length == 0) + mo.cameraList = null; + } + } + + runOutput(0); + } +} \ No newline at end of file diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 0067299..173e62c 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -835,6 +835,13 @@ class LeenkxExporter: } out_object['vertex_groups'].append(out_vertex_groups) + if len(bobject.lnx_camera_list) > 0: + out_camera_list = [] + for camera in bobject.lnx_camera_list: + if camera.lnx_camera_object_ptr != None: + out_camera_list.append(camera.lnx_camera_object_ptr.name) + if len(out_camera_list) > 0: + out_object['camera_list'] = out_camera_list if len(bobject.lnx_propertylist) > 0: out_object['properties'] = [] diff --git a/leenkx/blender/lnx/logicnode/logic/LN_get_camera_render_filter.py b/leenkx/blender/lnx/logicnode/logic/LN_get_camera_render_filter.py new file mode 100644 index 0000000..b887e86 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/logic/LN_get_camera_render_filter.py @@ -0,0 +1,15 @@ +from lnx.logicnode.lnx_nodes import * + +class GetCameraRenderFilterNode(LnxLogicTreeNode): + """ + Gets Camera Render Filter array with the names of the cameras + that can render the mesh. If null all cameras can render the mesh. + """ + bl_idname = 'LNGetCameraRenderFilterNode' + bl_label = 'Get Object Camera Render Filter' + lnx_section = 'camera' + lnx_version = 1 + + def lnx_init(self, context): + self.add_input('LnxNodeSocketObject', 'Object') + self.add_output('LnxNodeSocketArray', 'Array') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py b/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py new file mode 100644 index 0000000..3a2ea2d --- /dev/null +++ b/leenkx/blender/lnx/logicnode/logic/LN_set_camera_render_filter.py @@ -0,0 +1,29 @@ +from lnx.logicnode.lnx_nodes import * + +class SetCameraRenderFilterNode(LnxLogicTreeNode): + """ + Sets Camera Render Filter array with the names of the cameras + that can render the mesh. If null all cameras can render the mesh. + A camera can be added or removed from the arraw list. + """ + bl_idname = 'LNSetCameraRenderFilterNode' + bl_label = 'Set Object Camera Render Filter' + lnx_section = 'camera' + lnx_version = 1 + + property0: HaxeEnumProperty( + 'property0', + items = [('Add', 'Add', 'Add'), + ('Remove', 'Remove', 'Remove')], + name='', default='Add', update='') + + + def lnx_init(self, context): + self.add_input('LnxNodeSocketAction', 'In') + self.add_input('LnxNodeSocketObject', 'Object') + self.add_input('LnxNodeSocketObject', 'Camera') + + self.add_output('LnxNodeSocketAction', 'Out') + + def draw_buttons(self, context, layout): + layout.prop(self, 'property0') \ No newline at end of file diff --git a/leenkx/blender/lnx/props_camera_render_filter.py b/leenkx/blender/lnx/props_camera_render_filter.py new file mode 100644 index 0000000..14558b4 --- /dev/null +++ b/leenkx/blender/lnx/props_camera_render_filter.py @@ -0,0 +1,141 @@ +import bpy +from bpy.props import * + +class LNX_UL_CameraList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.prop_search(item, "lnx_camera_object_ptr", bpy.data, "objects", text="", icon='VIEW_CAMERA') + +class LNX_CameraListItem(bpy.types.PropertyGroup): + lnx_camera_object_ptr: bpy.props.PointerProperty( + type=bpy.types.Object, + name="Camera Object", + poll=lambda self, obj_to_check: obj_to_check.type == 'CAMERA' and \ + (bpy.context.object is None or \ + not hasattr(bpy.context.object, 'lnx_camera_list') or \ + (obj_to_check.name not in [ + item.lnx_camera_object_ptr.name + for item in bpy.context.object.lnx_camera_list + if item.lnx_camera_object_ptr and item != self + ])) + ) + +class LNX_PT_LeenkxCameraRenderFilter(bpy.types.Panel): + bl_label = "Leenkx Camera Render Filter" + bl_idname = "LNX_PT_LeenkxCameraRenderFilter" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + + @classmethod + def poll(cls, context): + return context.object is not None and context.object.type == 'MESH' + + def draw(self, context): + layout = self.layout + obj = context.object + + row = layout.row() + + col = row.column() + col.template_list( + "LNX_UL_CameraList", + "", + obj, + "lnx_camera_list", + obj, + "lnx_active_camera_index", + rows=5 + ) + + col = row.column(align=True) + col.operator("lnx.add_camera", icon='ADD', text="") + + if obj.lnx_camera_list and obj.lnx_active_camera_index >= 0 and obj.lnx_active_camera_index < len(obj.lnx_camera_list): + op_remove = col.operator("lnx.remove_camera", icon='REMOVE', text="") + op_remove.lnx_index = obj.lnx_active_camera_index + + op_up = col.operator("lnx.move_camera", icon='TRIA_UP', text="") + op_up.lnx_index = obj.lnx_active_camera_index + op_up.direction = 'UP' + + op_down = col.operator("lnx.move_camera", icon='TRIA_DOWN', text="") + op_down.lnx_index = obj.lnx_active_camera_index + op_down.direction = 'DOWN' + +class LNX_OT_AddCamera(bpy.types.Operator): + bl_idname = "lnx.add_camera" + bl_label = "Add Camera" + + def execute(self, context): + obj = context.object + + new_item = obj.lnx_camera_list.add() + obj.lnx_active_camera_index = len(obj.lnx_camera_list) - 1 + + return {'FINISHED'} + +class LNX_OT_RemoveCamera(bpy.types.Operator): + bl_idname = "lnx.remove_camera" + bl_label = "Remove Camera" + + lnx_index: bpy.props.IntProperty() + + def execute(self, context): + obj = context.object + if self.lnx_index < len(obj.lnx_camera_list): + obj.lnx_camera_list.remove(self.lnx_index) + if obj.lnx_active_camera_index >= len(obj.lnx_camera_list): + obj.lnx_active_camera_index = len(obj.lnx_camera_list) - 1 + return {'FINISHED'} + +class LNX_OT_MoveCamera(bpy.types.Operator): + bl_idname = "lnx.move_camera" + bl_label = "Move Camera" + + lnx_index: bpy.props.IntProperty() + direction: bpy.props.EnumProperty( + items=[('UP', "Up", "Move camera up"), + ('DOWN', "Down", "Move camera down")], + default='UP' + ) + + def execute(self, context): + obj = context.object + camera_list = obj.lnx_camera_list + target_index = -1 + + if self.direction == 'UP': + if self.lnx_index > 0: + target_index = self.lnx_index - 1 + elif self.direction == 'DOWN': + if self.lnx_index < len(camera_list) - 1: + target_index = self.lnx_index + 1 + + if target_index != -1: + camera_list.move(self.lnx_index, target_index) + obj.lnx_active_camera_index = target_index + return {'FINISHED'} + +def register(): + bpy.utils.register_class(LNX_UL_CameraList) + bpy.utils.register_class(LNX_CameraListItem) + bpy.utils.register_class(LNX_PT_LeenkxCameraRenderFilter) + bpy.utils.register_class(LNX_OT_AddCamera) + bpy.utils.register_class(LNX_OT_RemoveCamera) + bpy.utils.register_class(LNX_OT_MoveCamera) + + bpy.types.Object.lnx_camera_list = bpy.props.CollectionProperty(type=LNX_CameraListItem) + bpy.types.Object.lnx_active_camera_index = bpy.props.IntProperty(name="Active Camera Index", default=-1) + +def unregister(): + bpy.utils.unregister_class(LNX_UL_CameraList) + bpy.utils.unregister_class(LNX_CameraListItem) + bpy.utils.unregister_class(LNX_PT_LeenkxCameraRenderFilter) + bpy.utils.unregister_class(LNX_OT_AddCamera) + bpy.utils.unregister_class(LNX_OT_RemoveCamera) + bpy.utils.unregister_class(LNX_OT_MoveCamera) + + if hasattr(bpy.types.Object, "lnx_camera_list"): + del bpy.types.Object.lnx_camera_list + if hasattr(bpy.types.Object, "lnx_active_camera_index"): + del bpy.types.Object.lnx_active_camera_index \ No newline at end of file diff --git a/leenkx/blender/start.py b/leenkx/blender/start.py index e161f2f..5f45bcb 100644 --- a/leenkx/blender/start.py +++ b/leenkx/blender/start.py @@ -16,6 +16,7 @@ import lnx.props_properties import lnx.props_collision_filter_mask import lnx.props import lnx.props_ui +import lnx.props_camera_render_filter import lnx.handlers import lnx.utils import lnx.keymap @@ -44,6 +45,7 @@ if lnx.is_reload(__name__): lnx.props_collision_filter_mask = lnx.reload_module(lnx.props_collision_filter_mask) lnx.props = lnx.reload_module(lnx.props) lnx.props_ui = lnx.reload_module(lnx.props_ui) + lnx.props_camera_render_filter = lnx.reload_module(lnx.props_camera_render_filter) lnx.handlers = lnx.reload_module(lnx.handlers) lnx.utils = lnx.reload_module(lnx.utils) lnx.keymap = lnx.reload_module(lnx.keymap) @@ -68,6 +70,7 @@ def register(local_sdk=False): lnx.props_properties.register() lnx.props.register() lnx.props_ui.register() + lnx.props_camera_render_filter.register() lnx.nodes_logic.register() lnx.nodes_material.register() lnx.keymap.register() @@ -100,3 +103,4 @@ def unregister(): lnx.props_renderpath.unregister() lnx.props_properties.unregister() lnx.props_collision_filter_mask.unregister() + lnx.props_camera_render_filter.unregister()