Sending scene with linked files

Hello Blender Coders,

If you familiar with RayPump project, you might know that it handles scenes with external linkage only “the hard way”, that means using:

bpy.ops.object.make_local(type='ALL')

which sux, of course. :eek:

If anyone would like to help me implementing the smart, non-invasive method of pushing all dependent scene files - I would be happy to give something in return - for example a lot of Render Points to use on RayPump with pro job types.

My Python skill are close to zero, I’m C++ guy, so please forgive that current script is more than simple, and rough at the edges:


bl_info = {
    'name': 'RayPump Online Accelerator',
    'author': '[email protected]',
    'version': '(0, 3, 1)',
    'blender': (2, 6, 8),
    'location': 'Properties > Render > RayPump.com',
    'description': 'Eeasy to use free online GPU-farm for Cycles',
    'category': 'Render'
    }

import bpy
import socket
import json
import os
import os.path

from bpy.props import *
from subprocess import call

TCP_IP = '127.0.0.1'
TCP_PORT = 5005
SOCKET = None 
RAYPUMP_PATH = None
RAYPUMP_VERSION = 0.991 # what version we will connect to?
        
class ConnectClientOperator(bpy.types.Operator):
    bl_idname = "object.raypump_connect_operator"
    bl_label = "Connect/Show local RayPump"
    bl_description = "(re)Initializes connection with the local RayPump client"

    def execute(self, context):
        global SOCKET, TCP_IP, TCP_PORT, RAYPUMP_PATH
        scene = context.scene

        if (SOCKET != None):
            SOCKET.close()
        try:
            SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.error as msg:
            self.report({'ERROR'}, "Cannot create a socket")
            SOCKET = None
            return {'CANCELLED'}

        try:
            SOCKET.connect((TCP_IP, TCP_PORT))
        except socket.error as msg:
            self.report({'ERROR'}, "Failed to connect: is RayPump running?")
            SOCKET.close()
            SOCKET = None
            return {'CANCELLED'}
        
        RAYPUMP_PATH = SOCKET.makefile().readline().rstrip()
        self.report({'INFO'}, "Connected with RayPump")
        the_version = json.dumps({
            'VERSION':RAYPUMP_VERSION
        })
        SOCKET.sendall(bytes(the_version, 'UTF-8'))
        return {'FINISHED'}


class MessageRenderOperator(bpy.types.Operator):
    bl_idname = "object.raypump_message_operator"
    bl_label = "Send To RayPump"
    bl_description = "Sends and schedules current scene to the RayPump Accelerator" 

    def execute(self, context):
        global SOCKET, RAYPUMP_PATH
        if (SOCKET == None):
            self.report({'ERROR'}, "Not connected to RayPump client")
            return {'CANCELLED'}
        else:
            try:
                bpy.ops.file.pack_all()
            except RuntimeError as msg:
                self.report({'ERROR'}, "Packing has failed (missing textures?)")
                print(msg)
                return {'CANCELLED'}
            
            original_fpath = bpy.context.blend_data.filepath
            basename = os.path.basename(original_fpath)
            destination_fpath = RAYPUMP_PATH + "/" + basename

            #bpy.ops.wm.save_mainfile()

            bpy.ops.object.make_local(type='ALL') #this should be optional
            bpy.ops.wm.save_as_mainfile(filepath=destination_fpath, copy=True, compress=False) #we compress the files ourself, at the RayPump side
            
            try:
                the_dump = json.dumps({
                    'SCHEDULE':bpy.context.blend_data.filepath,
                    'FRAME_CURRENT':bpy.context.scene.frame_current,
                    'FRAME_START':bpy.context.scene.frame_start,
                    'FRAME_END':bpy.context.scene.frame_end,
                    'JOB_TYPE':bpy.context.scene.raypump_jobtype
                    })
                SOCKET.sendall(bytes(the_dump, 'UTF-8'))
                SYNCHRONIZING = True
                
            except socket.error as msg:
                self.report({'ERROR'}, "Error connecting RayPump client")
                SOCKET = None
                # @todo restore scene
                return {'CANCELLED'}

        SynchroSuccessful = SOCKET.makefile().readline().rstrip()
        if (SynchroSuccessful == 'SUCCESS'):
            self.report({'INFO'}, 'Job send')
        else:
            self.report({'ERROR'}, 'Failed to schedule. Check RayPump messages')
        #bpy.ops.wm.open_mainfile(filepath=original_fpath)
        # @badcode this is read-only: bpy.context.blend_data.filepath = original_fpatch
        return {'FINISHED'}

class RemoveMissedTexturesOperator(bpy.types.Operator):
    bl_idname = "object.raypump_remove_missing_textures_operator"
    bl_label = "Fix Textures"
    bl_description = "Removes invalid image file names from the scene"
    
    def execute(self, context):
        fixApplied = False
        for image in bpy.data.images:
            path = image.filepath
            if path:
                if not os.path.exists(path):
                    print("Image path: " + image.filepath + " does not exist")
                    image.filepath = ""
                    fixApplied = True
            
        if fixApplied:
            self.report({'INFO'}, 'Invalid entries removed')
        else:
            self.report({'INFO'}, 'No invalid entries found')   
        return {'FINISHED'}

def init_properties():
    bpy.types.Scene.raypump_jobtype = EnumProperty(
        items = [('FREE', 'Free', 'Suitable for less demanding jobs (limited daily)'), 
                ('STATIC', 'Static', 'Renders current frame using Render Points'),
                ('ANIMATION', 'Animation', 'Renders animation using Render Points'), 
                ('STRESS-TEST', 'Stress-Test', 'Estimates cost and test GPU compatibility')
                ],
        default = 'FREE',
        #description = 'Set the way RayPump will treat scheduled job',
        name = "Job Type")
        
    # @todo either addon, either blender setting (currently not used, anyway)
    bpy.types.Scene.ray_pump_path = StringProperty(
        name="RayPump (exe)",
        subtype="FILE_PATH",
        description="Path to RayPump executable")

    

class RenderPumpPanel(bpy.types.Panel):
    init_properties()
    """Creates a Panel in the scene context of the properties editor"""
    bl_label = "RayPump.com"
    bl_idname = "SCENE_PT_layout"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "render"

    def draw(self, context):
        layout = self.layout
        scene = context.scene

        #Section: connection and textures fix
        row = layout.row(align=True)
        split = row.split(percentage=0.66)
        col = split.column()
        col.operator("object.raypump_connect_operator")
        col = split.column()
        col.operator("object.raypump_remove_missing_textures_operator")
        
        #Section: schedule       
        row = layout.row()
        row.scale_y = 2.0
        row.operator("object.raypump_message_operator")
        
        #Section: image format
        row = layout.row()
        row.prop(scene, "raypump_jobtype", text="Job Type")
        
def register():
    #init_properties()
    bpy.utils.register_class(RenderPumpPanel)
    bpy.utils.register_class(MessageRenderOperator)
    bpy.utils.register_class(ConnectClientOperator)
    bpy.utils.register_class(RemoveMissedTexturesOperator)

def unregister():
    bpy.utils.unregister_class(RenderPumpPanel)
    bpy.utils.unregister_class(MessageRenderOperator)
    bpy.utils.unregister_class(ConnectClientOperator)
    bpy.utils.unregister_class(RemoveMissedTexturesOperator)


if __name__ == "__main__":
    register()

Me not working there but … you be more explicit concerning “sux”, “pushing” and “something” ?

Hey! Ive got the same issue with anothe rendering company. Trying to sort it out at the moment if you have any luck SuperMelon, would be keen to hear how you got around it. I agree the make_local(type=‘ALL’) is not working too well.