I don't think that's possible, without adding this to the source and compile it. As workaround you can try to create a custom render engine class and create property to add a path to your application like this:
The following script creates a discription scene file (text) for every frame you've selected in the scene and uses custom application to render it:
import bpy
import mathutils
from mathutils import Vector
# LOGIC
class CustomRenderEngine(bpy.types.RenderEngine):
# These three members are used by blender to set up the
# RenderEngine; define its internal name, visible name and capabilities.
bl_idname = 'custom_renderer'
bl_label = 'Application Name'
bl_use_preview = True
def formatNumbers(self, number):
return '%0*d' % (4, number)
def get_allObjectsInSceneByType(self, type_string):
objects = []
for objs in bpy.data.objects: # searches for type in the selection
if (objs.type == type_string):
if objs.hide == True:
pass
else:
objects.append(objs)
return objects
# Return Object Transformations
def get_objectLocation(self, object):
return bpy.data.objects[object.name].location
def get_objectRotation(self, object):
return bpy.data.objects[object.name].rotation_euler
def get_objectScale(self, object):
return bpy.data.objects[object.name].scale
# Return Geo Type
def get_geoType(self, object):
verticies = []
for vertex in object.data.vertices:
verticies.append(vertex.co)
if len(verticies) == 8:
return "CUBE"
else:
return "SPHERE"
# Return verticies of Object
def get_objectVerts(self, object):
verticies = []
for vertex in object.data.vertices:
verticies.append(vertex.co)
#print (vertex.co)
return verticies
# Return Geo Center
def get_GeometryCenter(self,obj):
sumWCoord = [0,0,0]
numbVert = 0
if obj.type == 'MESH':
for vert in obj.data.vertices:
wmtx = obj.matrix_world
worldCoord = vert.co * wmtx
sumWCoord[0] += worldCoord[0]
sumWCoord[1] += worldCoord[1]
sumWCoord[2] += worldCoord[2]
numbVert += 1
sumWCoord[0] = sumWCoord[0]/numbVert
sumWCoord[1] = sumWCoord[1]/numbVert
sumWCoord[2] = sumWCoord[2]/numbVert
return sumWCoord
# Return max vert distance
def max_vertDistance(self, ob):
verts = ob.data.vertices
centre = Vector([0,0,0])
for vert in verts:
centre += vert.co
centre = centre / len(verts)
distanceFromCentre = 0.0
for vert in verts:
vecFromCentre = vert.co - centre
vertDistanceFromCentre = vecFromCentre.length
if vertDistanceFromCentre > distanceFromCentre:
distanceFromCentre = vertDistanceFromCentre
vert1 = vert
distance = 0.0
for vert in verts:
vec = vert.co - vert1.co
vertsDistance = vec.length
if vertsDistance > distance:
distance = vertsDistance
vert2 = vert
#return vert1, vert2, distance
print ("vert:", vert1.co)
return vert1.co, vert2.co
# Return max vert distance NEW
def max_vertDistance2(self, ob):
verts = ob.data.vertices
centre = Vector([0,0,0])
for vert in verts:
centre += vert.co
centre = centre / len(verts)
distanceFromCentre = 0.0
for vert in verts:
vecFromCentre = vert.co - centre
vertDistanceFromCentre = vecFromCentre.length
if vertDistanceFromCentre > distanceFromCentre:
distanceFromCentre = vertDistanceFromCentre
vert1 = vert
distance = 0.0
for vert in verts:
vec = vert.co - vert1.co
vertsDistance = vec.length
if vertsDistance > distance:
distance = vertsDistance
vert2 = vert
twoVerts = [vert1, vert2]
newTwo = []
wm = ob.matrix_world
for v in twoVerts:
world = wm * v.co
#print(world)
newTwo.append(world)
#return vert1.co, vert2.co
return newTwo
# Calculate Raduis of sphere
def get_radiusSphere(self, obj):
return (obj.dimensions[0] / 2)
# Return Materials of Geo
def get_material(self, object):
mat_diff = bpy.data.objects[object.name].active_material.diffuse_color
mat_diff_int = bpy.data.objects[object.name].active_material.diffuse_intensity
mat_spec = bpy.data.objects[object.name].active_material.specular_color
mat_spec_int = bpy.data.objects[object.name].active_material.specular_intensity
mat_amb = bpy.data.worlds["World"].ambient_color
mat_amb_int = bpy.data.worlds["World"].custom.world_amount
return mat_diff, mat_diff_int, mat_spec, mat_spec_int, mat_amb_int
def diff_dimensions(self, dimensions):
dimList = [round(dimensions.x, 3), round(dimensions.y, 3), round(dimensions.z, 3)]
return [i for i in dimList if dimList.count(i) > 1]
# Return Material List
def get_materialList(self):
materialNames = []
for material in bpy.data.materials:
materialNames.append(material.name)
return materialNames
def app_materials(self):
string = ""
amb_r = bpy.context.scene.world.ambient_color.r
amb_g = bpy.context.scene.world.ambient_color.g
amb_b = bpy.context.scene.world.ambient_color.b
ambient_int = bpy.context.scene.world.custom.world_amount
for i in bpy.data.materials:
diff_r = bpy.data.materials[i.name].diffuse_color.r
diff_g = bpy.data.materials[i.name].diffuse_color.g
diff_b = bpy.data.materials[i.name].diffuse_color.b
diff_int = bpy.data.materials[i.name].diffuse_intensity
spec_r = bpy.data.materials[i.name].specular_color.r
spec_b = bpy.data.materials[i.name].specular_color.b
spec_g = bpy.data.materials[i.name].specular_color.g
spec_int = bpy.data.materials[i.name].specular_intensity
spec_hardness = bpy.data.materials[i.name].specular_hardness
string += ("define material ") + i.name + " "
# ambient
string += str(amb_r * ambient_int)[:5] + " "
string += str(amb_b * ambient_int)[:5] + " "
string += str(amb_g * ambient_int)[:5] + " "
# diffuse
string += str(diff_r * diff_int)[:5] + " "
string += str(diff_g * diff_int)[:5] + " "
string += str(diff_b * diff_int)[:5] + " "
# specular
string += str(spec_r * spec_int)[:5] + " "
string += str(spec_g * spec_int)[:5] + " "
string += str(spec_b* spec_int)[:5] + " "
string += str(spec_hardness)
string += "\n"
return string
# This is the only method called by blender, in this example
# we use it to detect preview rendering and call the implementation
# in another method.
def render(self, scene):
scale = scene.render.resolution_percentage / 100.0
self.size_x = int(scene.render.resolution_x * scale)
self.size_y = int(scene.render.resolution_y * scale)
if scene.name == 'preview':
self.render_preview(scene)
else:
self.render_scene(scene)
# In this example, we fill the preview renders with a flat green color.
def render_preview(self, scene):
pixel_count = self.size_x * self.size_y
# The framebuffer is defined as a list of pixels, each pixel
# itself being a list of R,G,B,A values
green_rect = [[0.0, 1.0, 0.0, 1.0]] * pixel_count
# Here we write the pixel values to the RenderResult
result = self.begin_result(0, 0, self.size_x, self.size_y)
layer = result.layers[0]
layer.rect = green_rect
self.end_result(result)
# In this example, we fill the full renders with a flat blue color.
def render_scene(self, scene):
pixel_count = self.size_x * self.size_y
materialstring = self.app_materials()
# collect geometry
geo = self.get_allObjectsInSceneByType("MESH")
geostring = ""
transformstring = ""
for item in geo:
if (self.get_geoType(item) == "CUBE"):
#print ("Location:", self.get_objectLocation(item))
#print ("CENTER IS:", self.get_GeometryCenter(item))
#print ("dimensions:", item.dimensions)
#self.get_objectVerts(item)
geostring += "define shape box "
geostring += item.name + " "
# first point
geostring += str(self.max_vertDistance2(item)[0][0])[:5] + " "
geostring += str(self.max_vertDistance2(item)[0][3])[:5] + " "
geostring += str(self.max_vertDistance2(item)[0][4])[:5] + " "
# second point
geostring += str(self.max_vertDistance2(item)[1][0])[:5] + " "
geostring += str(self.max_vertDistance2(item)[1][5])[:5] + " "
geostring += str(self.max_vertDistance2(item)[1][6])[:5] + " "
# material
geostring += " " + bpy.data.objects[item.name].active_material.name
geostring += "\n"
else:
geostring += "define shape sphere "
geostring += item.name + " "
# center
geostring += str(self.get_objectLocation(item)[0])[:5] + " "
geostring += str(self.get_objectLocation(item)[2])[:5] + " "
geostring += str(self.get_objectLocation(item)[1])[:5] + " "
# radius
geostring += str(self.get_radiusSphere(item))[:5]
geostring += " " + bpy.data.objects[item.name].active_material.name
geostring += "\n"
# check for transformation
dims = bpy.data.objects[item.name].dimensions
#print (round(dims.x, 3), round(dims.y, 3), round(dims.z, 3))
#print (self.diff_dimensions(dims))
if len(self.diff_dimensions(dims)) < 3:
#print ("Transformation found!", item.name)
scle = bpy.data.objects[item.name].scale
transformstring += "transform "
transformstring += item.name + " scale " + str(round(scle.x,3)) + " " + str(round(scle.z,3)) + " " + str(round(scle.y,3)) + "\n"
transformstring += "transform " + item.name + " translate 0 0 0\n"
'''
print (scene.custom.app_path)
print (scene.custom.scene_name)
print (bpy.data.worlds["World"].ambient_color)
print (bpy.context.scene.world.ambient_color)
print (self.get_materialList())
'''
# collect lamps
lamps = self.get_allObjectsInSceneByType("LAMP")
lampstring = ""
amb_r = bpy.context.scene.world.ambient_color.r
amb_g = bpy.context.scene.world.ambient_color.g
amb_b = bpy.context.scene.world.ambient_color.b
for i in lamps:
lampstring += "define light " + i.name + " "
# position
lampstring += str(self.get_objectLocation(i)[0])[:5] + " "
lampstring += str(self.get_objectLocation(i)[2])[:5] + " "
lampstring += str(self.get_objectLocation(i)[1])[:5] + " "
# light ambient
lampstring += str(amb_r)[:5] + " "
lampstring += str(amb_g)[:5] + " "
lampstring += str(amb_b)[:5] + " "
# light diffuse
lamp_energy = bpy.data.lamps[i.name].energy
lampstring += str(bpy.data.lamps[i.name].color[0] * lamp_energy)[:5] + " "
lampstring += str(bpy.data.lamps[i.name].color[1] * lamp_energy)[:5] + " "
lampstring += str(bpy.data.lamps[i.name].color[2] * lamp_energy)[:5] + " "
lampstring += "\n"
#print ("Lamp energy:", bpy.data.lamps[i.name].energy)
#print ("Lamp color:", bpy.data.lamps[i.name].color)
#print ("Lamp location:", self.get_objectLocation(i))
# get active camera
obj_camera = bpy.context.scene.camera
cameraName = obj_camera.name
camerastring = "define camera " + cameraName + " "
camerastring += str(bpy.data.cameras[cameraName].lens)[:5] + " "
#camerastring += "35" + " "
camerastring += str(self.get_objectLocation(obj_camera)[0])[:5] + " "
camerastring += str(self.get_objectLocation(obj_camera)[2])[:5] + " "
camerastring += str(self.get_objectLocation(obj_camera)[1])[:5] + " "
# calculate up and direction vector
cam = bpy.data.objects[obj_camera.name]
#up = cam.matrix_world * Vector((0.0, 1.0, 0.0))
#up = cam.matrix_world.to_3x3() * Vector((0.0, 1.0, 0.0))
up = cam.matrix_world.to_quaternion() * Vector((0.0, 1.0, 0.0))
#up = Vector((0.0, 1.0, 0.0))
print ("UPVECTOR:", up)
#cam_direction = cam.matrix_world * Vector((0.0, 0.0, -1.0))
#cam_direction = cam.matrix_world.to_3x3() * Vector((0.0, 0.0, -1.0))
cam_direction = cam.matrix_world.to_quaternion() * Vector((0.0, -1.0, -1.0))
#cam_direction = Vector((0.0, 0.0, -1.0))
#cam_direction.normalize()
#up.normalize()
camerastring += str(cam_direction[0])[:5] + " "
camerastring += str(cam_direction[1])[:5] + " "
camerastring += str(cam_direction[2])[:5] + " "
camerastring += str(up[0])[:5] + " "
camerastring += str(up[1])[:5] + " "
camerastring += str(up[2])[:5] + " "
camerastring += "\n"
#camerastring = "define camera auge 60.0 1.0 1.0 3 0 -0.1 -1.0 0 1 0\n"
# background color
bgColor = bpy.data.worlds["World"].ambient_color
bgColorstring = "define background-color " + str(bgColor[0]) + " " + str(bgColor[1]) + " " + str(bgColor[2]) + "\n"
# ------------------------------------------------------------------------
# render settings
# ------------------------------------------------------------------------
renderer = bpy.path.abspath(scene.custom.app_path)
frameNumber = bpy.data.scenes["Scene"].frame_current
scenename = scene.custom.scene_name
scale = scene.render.resolution_percentage / 100.0
size_x = int(scene.render.resolution_x * scale)
size_y = int(scene.render.resolution_y * scale)
if not scenename:
scenename = "AppScene"
outfile_ppm = bpy.data.scenes["Scene"].render.filepath + scenename + "_" + str(self.formatNumbers(frameNumber)) + ".ppm"
outfile_custom = bpy.data.scenes["Scene"].render.filepath + scenename + "_" + str(self.formatNumbers(frameNumber)) + ".sdf"
rendersettingstring = "render " + obj_camera.name + " "
rendersettingstring += outfile_ppm + " "
rendersettingstring += str(size_x) + " "
rendersettingstring += str(size_y) + " "
rendersettingstring += "\n"
file_content = bgColorstring + materialstring + geostring + transformstring + lampstring + camerastring + rendersettingstring
# write custom file
f = open(outfile_custom,'w')
f.write(file_content)
f.close()
import signal
import subprocess, time
# for single image
single_frame = subprocess.call([renderer, outfile_sdf])
# for animation
'''
app_animation = subprocess.Popen([renderer, outfile_sdf])
print ('app_animation\'s pid = ',app_animation.pid)
time.sleep(170)
app_animation.terminate()
'''
print (materialstring + geostring + lampstring + camerastring + rendersettingstring)
# The framebuffer is defined as a list of pixels, each pixel
# itself being a list of R,G,B,A values
blue_rect = [[0.0, 0.0, 1.0, 1.0]] * pixel_count
# Here we write the pixel values to the RenderResult
result = self.begin_result(0, 0, self.size_x, self.size_y)
layer = result.layers[0]
layer.rect = blue_rect
self.end_result(result)
# Register the RenderEngine
bpy.utils.register_class(CustomRenderEngine)
# ======================================================================================
# UI
# ======================================================================================
from bpy.types import (AddonPreferences,
PropertyGroup,
Operator,
)
from bpy.props import (StringProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
EnumProperty,
PointerProperty,
)
# -----------------------------------------------------------------------------------
# Render properties
# example: https://github.com/meta-androcto/blenderpython/blob/master/scripts/archive/game_gamekit/ui.py
# -----------------------------------------------------------------------------------
from bl_ui import properties_render
properties_render.RENDER_PT_render.COMPAT_ENGINES.add('custom_renderer')
properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('custom_renderer')
#properties_render.RENDER_PT_antialiasing.COMPAT_ENGINES.add('custom_renderer')
#properties_render.RENDER_PT_shading.COMPAT_ENGINES.add('custom_renderer')
properties_render.RENDER_PT_output.COMPAT_ENGINES.add('custom_renderer')
del properties_render
class RenderButtonsPanel():
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "render"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
rd = context.scene.render
return (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
class RenderCustomSettingsScene(PropertyGroup):
deletefiles_enable = BoolProperty(
name="Delete files",
description="Delete files after rendering.",
default=True)
scene_name = StringProperty(
name="Scene Name",
description="Name of sdf scene to create.",
maxlen=1024)
app_path = StringProperty(
name="Export scene path",
# description="Path to directory where the exported scene is created"
description="Path to Custom Engine",
maxlen=1024, subtype="FILE_PATH")
class RENDER_PT_custom_export_settings(RenderButtonsPanel, bpy.types.Panel):
bl_label = "Render Connection"
COMPAT_ENGINES = {'custom_renderer'}
def draw_header(self, context):
self.layout.label(icon='CONSOLE')
def draw(self, context):
layout = self.layout
scene = context.scene
row = layout.row()
row.column().prop(scene.custom, "app_path", text="Path to App")
row = layout.row()
row.column().prop(scene.custom, "scene_name", text="Scene Name")
#row = layout.row()
#row.column().prop(scene.custom, "deletefiles_enable", text="Delete files after Rendering")
# -----------------------------------------------------------------------------------
# Material properties
# -----------------------------------------------------------------------------------
from bl_ui import properties_material
properties_material.MATERIAL_PT_context_material.COMPAT_ENGINES.add('custom_renderer')
properties_material.MATERIAL_PT_diffuse.COMPAT_ENGINES.add('custom_renderer')
#properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.add('custom_renderer')
#properties_material.MATERIAL_PT_shading.COMPAT_ENGINES.add('custom_renderer')
#properties_material.MATERIAL_PT_shadow.COMPAT_ENGINES.add('custom_renderer')
properties_material.MATERIAL_PT_specular.COMPAT_ENGINES.add('custom_renderer')
#properties_material.MATERIAL_PT_transp.COMPAT_ENGINES.add('custom_renderer')
del properties_material
class MaterialButtonsPanel():
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
mat = context.material
rd = context.scene.render
return mat and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
class RenderCustomSettingsMaterial(PropertyGroup):
test_enable = BoolProperty(
name="Enable",
description="test enabler",
default=False)
test_amount = FloatProperty(
name="amount",
description="test",
min=0.0, max=1.0, soft_min=0.01, soft_max=1.0, default=0.25)
# -----------------------------------------------------------------------------------
# Camera properties
# -----------------------------------------------------------------------------------
from bl_ui import properties_data_camera
properties_data_camera.DATA_PT_lens.COMPAT_ENGINES.add('custom_renderer')
del properties_data_camera
# -----------------------------------------------------------------------------------
# Lamp properties
# -----------------------------------------------------------------------------------
from bl_ui import properties_data_lamp
properties_data_lamp.DATA_PT_lamp.COMPAT_ENGINES.add('custom_renderer')
properties_data_lamp.DATA_PT_preview.COMPAT_ENGINES.add('custom_renderer')
del properties_data_lamp
# -----------------------------------------------------------------------------------
# World properties
# -----------------------------------------------------------------------------------
from bl_ui import properties_world
class WorldButtonsPanel():
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "world"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
wld = context.world
rd = context.scene.render
return wld and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES)
class RenderCustomSettingsWorld(PropertyGroup):
world_amount = FloatProperty(
name="amount",
description="test",
min=0.0, max=1.0, soft_min=0.01, soft_max=1.0, default=0.25)
class WORLD_PT_world(WorldButtonsPanel, bpy.types.Panel):
bl_label = "World"
COMPAT_ENGINES = {'custom_renderer'}
def draw(self, context):
layout = self.layout
wld = context.world
row = layout.row()
row.column().prop(wld, "ambient_color")
#row = layout.row()
#row.column().prop(wld, "world_amount")
row = layout.row()
row.column().prop(wld.custom, "world_amount", slider=True)
#bpy.utils.register_class(WORLD_PT_world)
# ======================================================================================
# Register
# ======================================================================================
def register():
bpy.utils.register_module(__name__)
bpy.types.Scene.custom = PointerProperty(type=RenderCustomSettingsScene)
#bpy.types.Material.custom = PointerProperty(type=RenderCustomSettingsMaterial)
bpy.types.World.custom = PointerProperty(type=RenderCustomSettingsWorld)
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.Scene.custom
del bpy.types.Material.custom
del bpy.types.World.custom
del bpy.types.Scene.custom
if __name__ == "__main__":
register()
Note: The script was written a while ago, it is a bit messy but I hope you get the idea.
For a simpler approach you can also extend the render panel. Best practice is to take a look into the templates that are shipped with blender:
To define your custom file output you can create a custom file property like this:
app_path = StringProperty(
name="Export files to:",
description=""
description="Path",
maxlen=1024, subtype="DIR_PATH")
or use scn.render.filepath
. For this case here you can find a working example: Is it possible to render only keyframes from Dope-sheet?