def __init__(self): # rest self.gridinfo = GridInfo() self.sim = SimConnection() self.ogre = OgreExporter() self.settings = {} self.actions_map = {} for name in self.action_uuids: self.actions_map[name.lower()] = name
def __init__(self, title="RealXtend"): self.command_queue = [] self.wanted_workers = 1 self._callbacks = defaultdict(DefaultMap) self.second_start = time.time() self.second_budget = 0 self._lastthrottle = 0 self._last_time = time.time() self.pool = ThreadPool(1) self.workpool = ThreadPool(5) self.rawselected = set() self.caps = {} self.agent_id = "" self.loglevel = "standard" self.agent_access = "" self.rt_support = eventlet_present self.stats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] self.status = "b2rex started" self.status_level = OK self.selected = {} self.sim_selection = set() self.connected = False self.positions = {} self.rotations = {} self.scales = {} self.rt_on = False self.simrt = None self.screen = self self.gridinfo = GridInfo() self.buttons = {} self.settings_visible = False self._requested_urls = [] self._modules = {} self._module_cb = defaultdict(list) self.initializeCommands() self.initializeModules() Importer.__init__(self, self.gridinfo) Exporter.__init__(self, self.gridinfo)
def __init__(self, title="RealXtend"): self.command_queue = [] self.wanted_workers = 1 self._callbacks = defaultdict(DefaultMap) self.second_start = time.time() self.second_budget = 0 self._lastthrottle = 0 self._last_time = time.time() self.pool = ThreadPool(1) self.workpool = ThreadPool(5) self.rawselected = set() self.caps = {} self.agent_id = "" self.loglevel = "standard" self.agent_access = "" self.rt_support = eventlet_present self.stats = [0,0,0,0,0,0,0,0,0,0,0,0,0] self.status = "b2rex started" self.status_level = OK self.selected = {} self.sim_selection = set() self.connected = False self.positions = {} self.rotations = {} self.scales = {} self.rt_on = False self.simrt = None self.screen = self self.gridinfo = GridInfo() self.buttons = {} self.settings_visible = False self._requested_urls = [] self._modules = {} self._module_cb = defaultdict(list) self.initializeCommands() self.initializeModules() Importer.__init__(self, self.gridinfo) Exporter.__init__(self, self.gridinfo)
class CharacterExporter(object): action_uuids = { "Walk": "6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0", "CrouchWalk": "47f5f6fb-22e5-ae44-f871-73aaaf4a6022", "Fly": "aec4610c-757f-bc4e-c092-c6e9caf18daf", "HoverDown": "20f063ea-8306-2562-0b07-5c853b37b31e", "HoverUp": "62c5de58-cb33-5743-3d07-9e4cd4352864", "Hover": "4ae8016b-31b9-03bb-c401-b1ea941db41d", "Run": "05ddbff8-aaa9-92a1-2b74-8fe77a29b445", "Sit": "1a5fe8ac-a804-8a5d-7cbd-56bd83184568", "SitGround": "1c7600d6-661f-b87b-efe2-d7421eb93c86", "Stand": "2408fe9e-df1d-1d7d-f4ff-1384fa7b350f", } def __init__(self): # rest self.gridinfo = GridInfo() self.sim = SimConnection() self.ogre = OgreExporter() self.settings = {} self.actions_map = {} for name in self.action_uuids: self.actions_map[name.lower()] = name def connect(self, base_url): """ Connect to an opensim instance """ self.gridinfo.connect(base_url) print self.sim.connect(base_url) def test(self): """ Api tests """ print self.gridinfo.getGridInfo()["gridnick"] regions = self.gridinfo.getRegions() for id in regions: region = regions[id] print " *", region["name"], region["x"], region["y"], id # xmlrpc print self.sim.login("caedes", "caedes", "pass") print self.sim.sceneClear("d9d1b302-5049-452d-b176-3a9561189ca4", "cube") print self.sim.sceneUpload("d9d1b302-5049-452d-b176-3a9561189ca4", "cube", "/home/caedes/groupmembers.zip") def writeAnimation(self, f, id, name, internal_name): """ Write an animation to the avatar file """ f.write(indent(1) + '<animation name="' + name + '" ') f.write('id="' + id + '" internal_name="' + internal_name + '" ') f.write('looped="1" speedfactor="1.0" ') if "walk" in name.lower() or "run" in name.lower(): f.write('usevelocity="1" ') f.write('fadein="0.25" ') f.write('fadeout="0.25" />\n') def writeAnimations(self, f): """ Write all animations to the avatar file """ actions = Blender.Armature.NLA.GetActions() for name, action in actions.items(): if action.name.lower() in self.actions_map: action_name = self.actions_map[action.name.lower()] action_uuid = self.action_uuids[action_name] else: action_name = action.name action_uuid = "not-needed" # has to exist according to manual self.writeAnimation(f, action_uuid, action_name, action.name) def writeProperty(self, f, name, value): """ Write an avatar property """ f.write(indent(1) + '<property name="' + name + '" value="' + value + '" />') def writeProperties(self, f): """ Write all properties """ if self.settings["MovementSpeed"]: self.writeProperty(f, "MovementSpeed", self.settings["MovementSpeed"]) # needed?? # automatic ground offset: # bone which should be adjusted to align with the ground if self.settings["basebone"]: self.writeProperty(f, "basebone", self.settings["basebone"]) # avatar skeleton's hierarchy root if self.settings["rootbone"]: self.writeProperty(f, "rootbone", self.settings["rootbone"]) # finetuning if self.settings["baseoffset"]: self.writeProperty(f, "baseoffset", self.settings["baseoffset"]) return # parametrized head turning: if self.settings["headbone"]: self.writeProperty(f, "headbone", "") if self.settings["neckbone"]: self.writeProperty(f, "neckbone", "") if self.settings["torsobone"]: self.writeProperty(f, "torsobone", "") if self.settings["headboneaxis"]: self.writeProperty(f, "headboneaxis", "") # optional if self.settings["neckboneaxis"]: self.writeProperty(f, "neckboneaxis", "") # optional if self.settings["torsoboneaxis"]: self.writeProperty(f, "torsoboneaxis", "") # optional def writeAvatarFile(self, f): """ Write an avatar file for the selected mesh. """ f.write('<?xml version="1.0" encoding="utf-8" ?>\n') f.write("<avatar>\n") f.write(indent(1) + "<version>0.2</version>\n") f.write(indent(1) + '<base name="default_female" mesh="' + self.settings["mesh_file"] + '" />\n') f.write(indent(1) + '<skeleton name="' + self.settings["skeleton_file"] + '" />\n') # f.write(indent(1)+'<material name="male/Body" />\n') # f.write(indent(1)+'<material name="male/Face" />\n') first_face_image = self.getMesh().getData(0, True).faces[0].image if first_face_image: texture_name = os.path.basename(first_face_image.getFilename()) else: texture_name = "" f.write(indent(1) + '<texture_body name="' + texture_name + '" />\n') # f.write(indent(1)+'<texture_face name="" />\n') f.write(indent(1) + '<appearance height="1.800000" weight="1" />\n') f.write( indent(1) + '<transformation position="%s" rotation="%s" \ scale="%s" />\n' % (self.settings["translation"], self.settings["rotation"], self.settings["scale"]) ) self.writeProperties(f) self.writeAnimations(f) f.write("</avatar>") def createAvatarFile(self, path): """ Create the avatar file at the specified location. """ character_name = self.settings["character_name"] f = open(os.path.join(path, character_name + ".xml"), "w") self.writeAvatarFile(f) f.close() def getMesh(self): """ Get the selected mesh """ selected = Blender.Object.GetSelected() for sel in selected: if sel.getType() == "Mesh": return sel def getArmature(self): """ Get the selected object's armature """ bObject = self.getMesh() return GetArmatureObject(bObject) def parseSettings(self, exportSettings): """ Decide settings for export """ mesh = self.getMesh() name = mesh.getData(0, True).name armature_name = self.getArmature().name self.settings["character_name"] = mesh.name self.settings["mesh_file"] = name + ".mesh" self.settings["skeleton_file"] = armature_name + ".skeleton" self.settings.update(exportSettings.getDict()) def setupAnimations(self): """ Setup animations on the ogre exporter. """ ogreSelection = self.ogre.meshapp.selectedObjectManager ogreSelection.updateSelection() armatureManager = ogreSelection.getArmatureAnimationProxyManager(self.getMesh().getData(True)) armatureManager.removeProxies() # cleanup armatureManager.animationProxyKeyList = [] # shouldnt be needed armatureManager.update() actionList = armatureManager.getActions() for action in actionList: bAction = action.bAction anim = ArmatureAnimationProxy( armatureManager, action, action.getName(), action.getFirstFrame(), action.getLastFrame() ) armatureManager.addProxy(anim) armatureManager.savePackageSettings() def export(self, path, pack_name, offset, exportSettings): """ Export the character and its avatar file. """ b2rexpkg.start() self.setupAnimations() self.ogre.export(path, pack_name, offset) self.parseSettings(exportSettings) self.createAvatarFile(path)
class CharacterExporter(object): action_uuids = { 'Walk': '6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0', 'CrouchWalk': "47f5f6fb-22e5-ae44-f871-73aaaf4a6022", 'Fly': "aec4610c-757f-bc4e-c092-c6e9caf18daf", "HoverDown": "20f063ea-8306-2562-0b07-5c853b37b31e", "HoverUp": "62c5de58-cb33-5743-3d07-9e4cd4352864", "Hover": "4ae8016b-31b9-03bb-c401-b1ea941db41d", "Run": "05ddbff8-aaa9-92a1-2b74-8fe77a29b445", "Sit": "1a5fe8ac-a804-8a5d-7cbd-56bd83184568", "SitGround": "1c7600d6-661f-b87b-efe2-d7421eb93c86", "Stand": "2408fe9e-df1d-1d7d-f4ff-1384fa7b350f" } def __init__(self): # rest self.gridinfo = GridInfo() self.sim = SimConnection() self.ogre = OgreExporter() self.settings = {} self.actions_map = {} for name in self.action_uuids: self.actions_map[name.lower()] = name def connect(self, base_url): """ Connect to an opensim instance """ self.gridinfo.connect(base_url) print self.sim.connect(base_url) def test(self): """ Api tests """ print self.gridinfo.getGridInfo()["gridnick"] regions = self.gridinfo.getRegions() for id in regions: region = regions[id] print " *", region["name"], region["x"], region["y"], id # xmlrpc print self.sim.login("caedes", "caedes", "pass") print self.sim.sceneClear("d9d1b302-5049-452d-b176-3a9561189ca4", "cube") print self.sim.sceneUpload("d9d1b302-5049-452d-b176-3a9561189ca4", "cube", "/home/caedes/groupmembers.zip") def writeAnimation(self, f, id, name, internal_name): """ Write an animation to the avatar file """ f.write(indent(1) + '<animation name="' + name + '" ') f.write('id="' + id + '" internal_name="' + internal_name + '" ') f.write('looped="1" speedfactor="1.0" ') if 'walk' in name.lower() or 'run' in name.lower(): f.write('usevelocity="1" ') f.write('fadein="0.25" ') f.write('fadeout="0.25" />\n') def writeAnimations(self, f): """ Write all animations to the avatar file """ actions = Blender.Armature.NLA.GetActions() for name, action in actions.items(): if action.name.lower() in self.actions_map: action_name = self.actions_map[action.name.lower()] action_uuid = self.action_uuids[action_name] else: action_name = action.name action_uuid = 'not-needed' # has to exist according to manual self.writeAnimation(f, action_uuid, action_name, action.name) def writeProperty(self, f, name, value): """ Write an avatar property """ f.write( indent(1) + '<property name="' + name + '" value="' + value + '" />') def writeProperties(self, f): """ Write all properties """ if self.settings['MovementSpeed']: self.writeProperty(f, 'MovementSpeed', self.settings['MovementSpeed']) # needed?? # automatic ground offset: # bone which should be adjusted to align with the ground if self.settings['basebone']: self.writeProperty(f, 'basebone', self.settings['basebone']) # avatar skeleton's hierarchy root if self.settings['rootbone']: self.writeProperty(f, 'rootbone', self.settings['rootbone']) # finetuning if self.settings['baseoffset']: self.writeProperty(f, 'baseoffset', self.settings['baseoffset']) return # parametrized head turning: if self.settings['headbone']: self.writeProperty(f, 'headbone', '') if self.settings['neckbone']: self.writeProperty(f, 'neckbone', '') if self.settings['torsobone']: self.writeProperty(f, 'torsobone', '') if self.settings['headboneaxis']: self.writeProperty(f, 'headboneaxis', '') # optional if self.settings['neckboneaxis']: self.writeProperty(f, 'neckboneaxis', '') # optional if self.settings['torsoboneaxis']: self.writeProperty(f, 'torsoboneaxis', '') # optional def writeAvatarFile(self, f): """ Write an avatar file for the selected mesh. """ f.write('<?xml version="1.0" encoding="utf-8" ?>\n') f.write('<avatar>\n') f.write(indent(1) + '<version>0.2</version>\n') f.write( indent(1) + '<base name="default_female" mesh="' + self.settings['mesh_file'] + '" />\n') f.write( indent(1) + '<skeleton name="' + self.settings['skeleton_file'] + '" />\n') #f.write(indent(1)+'<material name="male/Body" />\n') #f.write(indent(1)+'<material name="male/Face" />\n') first_face_image = self.getMesh().getData(0, True).faces[0].image if first_face_image: texture_name = os.path.basename(first_face_image.getFilename()) else: texture_name = '' f.write(indent(1) + '<texture_body name="' + texture_name + '" />\n') #f.write(indent(1)+'<texture_face name="" />\n') f.write(indent(1) + '<appearance height="1.800000" weight="1" />\n') f.write( indent(1) + '<transformation position="%s" rotation="%s" \ scale="%s" />\n' % (self.settings['translation'], self.settings['rotation'], self.settings['scale'])) self.writeProperties(f) self.writeAnimations(f) f.write('</avatar>') def createAvatarFile(self, path): """ Create the avatar file at the specified location. """ character_name = self.settings['character_name'] f = open(os.path.join(path, character_name + '.xml'), 'w') self.writeAvatarFile(f) f.close() def getMesh(self): """ Get the selected mesh """ selected = Blender.Object.GetSelected() for sel in selected: if sel.getType() == 'Mesh': return sel def getArmature(self): """ Get the selected object's armature """ bObject = self.getMesh() return GetArmatureObject(bObject) def parseSettings(self, exportSettings): """ Decide settings for export """ mesh = self.getMesh() name = mesh.getData(0, True).name armature_name = self.getArmature().name self.settings['character_name'] = mesh.name self.settings['mesh_file'] = name + '.mesh' self.settings['skeleton_file'] = armature_name + '.skeleton' self.settings.update(exportSettings.getDict()) def setupAnimations(self): """ Setup animations on the ogre exporter. """ ogreSelection = self.ogre.meshapp.selectedObjectManager ogreSelection.updateSelection() armatureManager = ogreSelection.getArmatureAnimationProxyManager( self.getMesh().getData(True)) armatureManager.removeProxies() # cleanup armatureManager.animationProxyKeyList = [] # shouldnt be needed armatureManager.update() actionList = armatureManager.getActions() for action in actionList: bAction = action.bAction anim = ArmatureAnimationProxy(armatureManager, action, action.getName(), action.getFirstFrame(), action.getLastFrame()) armatureManager.addProxy(anim) armatureManager.savePackageSettings() def export(self, path, pack_name, offset, exportSettings): """ Export the character and its avatar file. """ b2rexpkg.start() self.setupAnimations() self.ogre.export(path, pack_name, offset) self.parseSettings(exportSettings) self.createAvatarFile(path)
class BaseApplication(Importer, Exporter): def __init__(self, title="RealXtend"): self.command_queue = [] self.wanted_workers = 1 self._callbacks = defaultdict(DefaultMap) self.second_start = time.time() self.second_budget = 0 self._lastthrottle = 0 self._last_time = time.time() self.pool = ThreadPool(1) self.workpool = ThreadPool(5) self.rawselected = set() self.caps = {} self.agent_id = "" self.loglevel = "standard" self.agent_access = "" self.rt_support = eventlet_present self.stats = [0,0,0,0,0,0,0,0,0,0,0,0,0] self.status = "b2rex started" self.status_level = OK self.selected = {} self.sim_selection = set() self.connected = False self.positions = {} self.rotations = {} self.scales = {} self.rt_on = False self.simrt = None self.screen = self self.gridinfo = GridInfo() self.buttons = {} self.settings_visible = False self._requested_urls = [] self._modules = {} self._module_cb = defaultdict(list) self.initializeCommands() self.initializeModules() Importer.__init__(self, self.gridinfo) Exporter.__init__(self, self.gridinfo) # Modules def registerModule(self, module): """ Register a module with the editor. The module will be registrered as an attribute in the editor, generally taking the name and removing "Module", so for example AssetModule becomes self.Asset. Also, some special callbacks will be looked for and registered for calling, at the moment we have the following: * check: check during the command processing stage * draw: gets drawn into the scene panel """ self._modules[module.getName()] = module module.register(self) setattr(self, module.getName(), module) for section in ["check", "draw", "draw_object"]: if hasattr(module, section): self._module_cb[section].append(getattr(module, section)) def drawModules(self, layout, props): """ Calls to draw all modules registered in the system. """ for draw_cb in self._module_cb["draw"]: draw_cb(layout, self, props) def drawObjectModules(self, layout, obj): """ Calls to draw all modules registered in the system. """ for draw_cb in self._module_cb["draw_object"]: draw_cb(layout, self, obj) # Generic callback functionality def add_callback(self, section, signal, callback, *parameters): """ Add a callback into a given section, waiting for a specific signal. The callback will be called with the specified parameters in expanded form. """ self._callbacks[str(section)][str(signal)].append((callback, parameters)) def insert_callback(self, section, signal, callback, *parameters): """ Like ass_callback but inserts the callback at the beginning of the queue. """ self._callbacks[str(section)][str(signal)].insert(0, (callback, parameters)) def trigger_callback(self, section, signal): """ Trigger the callback for the given section and signal. """ for callback, parameters in self._callbacks[str(section)][str(signal)]: callback(*parameters) del self._callbacks[str(section)][str(signal)] # Initialization def initializeModules(self): """ Initialize editor modules. """ self.registerModule(AssetModule(self)) self.registerModule(PrimsModule(self)) self.registerModule(MapModule(self)) self.registerModule(GameModule(self)) self.registerModule(CapsModule(self)) self.registerModule(ObjectModule(self)) self.registerModule(RexDataModule(self)) self.registerModule(ObjectPropertiesModule(self)) self.registerModule(RegionHandshakeModule(self)) self.registerModule(TerrainModule(self)) self.registerModule(StatsModule(self)) self.registerModule(OnlineModule(self)) self.registerModule(RexLogicModule(self)) self.registerModule(RexExportModule(self)) self.registerModule(AgentsModule(self)) self.registerModule(ScriptingModule(self)) self.registerModule(FsmEditorModule(self)) def initializeCommands(self): """ Initialize internal editor commands. """ self._cmd_matrix = {} self.registerCommand('pos', self.processPosCommand) self.registerCommand('rot', self.processRotCommand) self.registerCommand('scale', self.processScaleCommand) self.registerCommand('msg', self.processMsgCommand) self.registerCommand('connected', self.processConnectedCommand) # internal self.registerCommand('error', self.processError) self.registerCommand('agentquit', self.processAgentQuit) self.registerCommand('materialarrived', self.processMaterialArrived) self.registerCommand('texturearrived', self.processTextureArrived) # Threaded Downloader def addDownload(self, http_url, cb, cb_pars=(), error_cb=None, extra_main=None): """ Add a download into the internal threaded downloader. If the url is already downloaded False will be returned, otherwise the url will be downloaded, then the callback cb will be called with cb_pars. If extra_main is supplied it will be run in a thread before returning the final callback. If error_cb is supplied it will be called on errors. """ if http_url in self._requested_urls: return False self._requested_urls.append(http_url) if not error_cb: _error_cb = self.default_error_db else: def _error_cb(request, result): error_cb(result) def _cb(request, result): cb(result, *cb_pars) def _extra_cb(request, result): self.workpool.addRequest(extra_main, [[http_url, cb_pars, result]], _cb, _error_cb) if extra_main: _main_cb = _extra_cb else: _main_cb = _cb self.pool.addRequest(self.doDownload, [[http_url, cb_pars]], _main_cb, _error_cb) return True def default_error_db(self, request, error): """ Default error function for thread pool jobs. """ if hasattr(error[1], "code") and error[1].code in [404]: pass else: logger.warning("error downloading "+str(request)+": "+str(error)) if hasattr(error[1], "code"): print("error downloading "+str(request)+": "+str(error[1].code)) traceback.print_tb(error[2]) def doDownload(self, pars): """ Internal downloading function to be run in a thread. """ http_url, pars = pars req = urllib2.urlopen(http_url) return req.read() # Connection def processConnectedCommand(self, agent_id, agent_access): """ Connected messaage received from agent. """ self.agent_id = agent_id self.agent_access = agent_access self.addStatus("Connected to region") def connect(self, base_url, username="", password=""): """ Connect to an opensim instance """ self.sim.connect(base_url+'/xml-rpc.php') firstname, lastname = username.split() coninfo = self.sim.login(firstname, lastname, password) self._sim_port = coninfo['sim_port'] self._sim_ip = coninfo['sim_ip'] self._sim_url = 'http://'+str(self._sim_ip)+':'+str(self._sim_port) logger.info("reconnect to " + self._sim_url) self.gridinfo.connect('http://'+str(self._sim_ip)+':'+str(9000), username, password) self.sim.connect(self._sim_url) def onConnectAction(self): """ Connect Action """ base_url = self.exportSettings.server_url self.addStatus("Connecting to " + base_url, IMMEDIATE) self.region_uuid = '' self.regionLayout = None self.connect(base_url, self.exportSettings.username, self.exportSettings.password) try: self.regions = self.gridinfo.getRegions() self.griddata = self.gridinfo.getGridInfo() except: self.addStatus("Error: couldnt connect to " + base_url, ERROR) traceback.print_exc() return # create the regions panel self.addRegionsPanel(self.regions, self.griddata) if eventlet_present: self.addRtCheckBox() else: logger.warning("no support for real time communications") self.connected = True self.addStatus("Connected to " + self.griddata['gridnick']) # Rt enable/disable def onToggleRt(self, context=None): """ Toggle the real time agent. """ if context: if context.scene: # scene will not be defined when exiting the program self.exportSettings = context.scene.b2rex_props if self.rt_on: self.simrt.quit() self.rt_on = False self.simrt = None else: self.enableRt(context) self.rt_on = True for mod in self._modules.values(): mod.onToggleRt(self.rt_on) def enableRt(self, context): """ Enable the real time agent. """ # for the moment we fet some parameters in a different way depending # on the editor version. if sys.version_info[0] == 3: pars = self.exportSettings.getCurrentConnection() server_url = pars.url credentials = self.credentials else: pars = self.exportSettings server_url = pars.server_url credentials = self.exportSettings.credentials props = self.exportSettings # no functionality yet to change region. region_name = 'last' # we will send the editor version as our entrance punchline. firstline = editor.getVersion() # get the password from the credential manager since we dont store # it with other blender data. username, password = credentials.get_credentials(server_url, pars.username) # if defined we insert the agent library path into the environment. if props.agent_libs_path: os.environ['SIMRT_LIBS_PATH'] = props.agent_libs_path elif 'SIMRT_LIBS_PATH' in os.environ: del os.environ['SIMRT_LIBS_PATH'] # tools path, where python may be located if props.tools_path: os.environ['SIMRT_TOOLS_PATH'] = props.tools_path elif 'SIMRT_TOOLS_PATH' in os.environ: del os.environ['SIMRT_TOOLS_PATH'] # now setup the login parameters to be forwarded to the agent. login_params = { 'region': region_name, 'firstline': firstline } # depending on the usename form we will use a different kind of login. if '@' in pars.username: # federated login auth_uri = pars.username.split('@')[1] con = SimConnection() con.connect('http://'+auth_uri) account = pars.username passwd_hash = '$1$'+md5(password.encode('ascii')).hexdigest() res = con._con.ClientAuthentication({'account':account, 'passwd':passwd_hash, 'loginuri':server_url}) avatarStorageUrl = res['avatarStorageUrl'] sessionHash = res['sessionHash'] gridUrl = res['gridUrl'] login_params['first'] = 'NotReallyNeeded' login_params['last'] = 'NotReallyNeeded' login_params['AuthenticationAddress'] = auth_uri login_params['account'] = pars.username login_params['passwd'] = passwd_hash login_params['sessionhash'] = sessionHash else: # normal opensim login login_params['first'] = pars.username.split()[0] login_params['last'] = pars.username.split()[1] login_params['passwd'] = password # start the agent. self.simrt = simrt.run_thread(self, server_url, login_params) self.connected = True # send our preferred throttle. self._lastthrottle = self.exportSettings.kbytesPerSecond*1024 self.simrt.Throttle(self._lastthrottle) if not context: Blender.Window.QAdd(Blender.Window.GetAreaID(),Blender.Draw.REDRAW,0,1) # Redraw from thread def redraw(self): """ Queue a redraw of the application. This function is designed to be run from a thread, and for the moment is dangerous so can be controlled with a global toggle at b2rexpkg.safe_mode. """ if b2rexpkg.safe_mode: return if not self.stats[5] and self._last_time + 1 < time.time(): # we're using the commands left stats to keep our counter self.stats[5] += 1 self._last_time = time.time() self.queueRedraw(True) # Commands def registerCommand(self, cmd, callback): """ Register a command with the command processing subsystem. """ self._cmd_matrix[cmd] = callback def unregisterCommand(self, cmd): """ Unregister a command from the command processing subsystem. """ del self._cmd_matrix[cmd] def processCommand(self, cmd, *args): """ Process the given command with the given arguments. """ self.stats[0] += 1 cmdHandler = self._cmd_matrix.get(cmd, None) if cmdHandler: try: cmdHandler(*args) except Exception as e: print("Error executing", cmd, e) traceback.print_exc() # Material related callbacks def materialArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material has arrived from downloading. """ self.command_queue.append(["materialarrived", data, objId, meshId, matId, assetType, matIdx]) def materialTextureArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material texture has arrived (a material with AssetType 0 which means there is no formal material declaration and we will use the texture directly). """ self.create_material_fromimage(matId, data, meshId, matIdx) def processMaterialArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material has arrived and has to be processed in the queue. """ if assetType == AssetType.OgreMaterial: self.parse_material(matId, {"name":matId, "data":data}, meshId, matIdx) # Object actions def doRtUpload(self, context): """ Upload the active object using the real time connection. """ selected = bpy.context.selected_objects if selected: # just the first for now selected = selected[0] if not selected.opensim.uuid: self.Object.doRtObjectUpload(context, selected) return def doDelete(self): """ Delete all selected objects from the simulator. """ selected = editor.getSelected() if selected: for obj in selected: if obj.opensim.uuid: self.simrt.Delete(obj.opensim.uuid) def doDeRezObject(self): """ DeRez all active objects into inventory. """ selected = editor.getSelected() if selected: for obj in selected: if obj.opensim.uuid: editor.set_loading_state(obj, 'TAKING') self.simrt.DeRezObject(obj.opensim.uuid) # Misc common functionality def registerTextureImage(self, image): """ Register an image with the system. """ # register a texture with the sim if not image.opensim.uuid: image.opensim.uuid = str(uuid.uuid4()) return image.opensim.uuid def processError(self, error): """ Error received from the agent. """ self.addStatus(error, ERROR) self.queueRedraw(immediate=True) def processAgentQuit(self, msg): """ Quit received from the agent. """ if self.rt_on: self.onToggleRt() self.queueRedraw() def processMsgCommand(self, username, message): """ Process a chat message, can be overloaded by additional modules ;) """ self.addStatus("message from "+username+": "+message) def findWithUUID(self, objId): """ Find the editor object with the given uuid. """ obj = self.find_with_uuid(str(objId), bpy.data.objects, "objects") return obj def setMeshMaterials(self, mesh, materials): """ Set the materials for a mesh from an idx, matId, asset_type list. """ presentIds = list(map(lambda s: s.opensim.uuid, mesh.materials)) for idx, matId, asset_type in materials: mat = self.find_with_uuid(matId, bpy.data.materials, 'materials') if mat and not matId in presentIds: mesh.materials.append(mat) # Position commands def processPosCommand(self, objId, pos, rot=None): """ Position for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processPosCommand(obj, objId, pos) if rot: self._processRotCommand(obj, objId, rot) else: self.add_callback('object.create', objId, self.processPosCommand, objId, pos, rot) def processScaleCommand(self, objId, scale): """ Scale for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processScaleCommand(obj, objId, scale) else: self.add_callback('object.create', objId, self.processScaleCommand, objId, scale) def processRotCommand(self, objId, rot): """ Rotation for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processRotCommand(obj, objId, rot) else: self.add_callback('object.create', objId, self.processRotCommand, objId, rot) # Checks def processUpdate(self, obj): """ Process all object updates not requiring modification of the object (executed from the view). """ obj_uuid = self.get_uuid(obj) if obj_uuid: pos, rot, scale = self.getObjectProperties(obj) pos = list(pos) rot = list(rot) scale = list(scale) # check parent if obj_uuid in self.selected: parent_pointer = None prevstate = self.selected[obj_uuid] if obj.parent and obj.parent.opensim.uuid: parent_pointer = obj.parent.as_pointer() if prevstate.parent != parent_pointer: if parent_pointer: parent_uuid = obj.parent.opensim.uuid self.simrt.Link(parent_uuid, obj_uuid) else: parent_uuid = prevstate.parent_uuid self.simrt.Unlink(parent_uuid, obj_uuid) # save properties and dont process position updates prevstate.update(obj) self.positions[obj_uuid] = pos self.rotations[obj_uuid] = rot self.scales[obj_uuid] = scale return obj_uuid if not obj_uuid in self.rotations or not rot == self.rotations[obj_uuid]: self.stats[1] += 1 if obj.parent: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos,0,0,0), self.unapply_rotation(rot)) else: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos), self.unapply_rotation(rot)) self.positions[obj_uuid] = pos self.rotations[obj_uuid] = rot elif not obj_uuid in self.positions or not pos == self.positions[obj_uuid]: self.stats[1] += 1 if obj.parent: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos,0,0,0)) else: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos)) self.positions[obj_uuid] = pos if not obj_uuid in self.scales or not scale == self.scales[obj_uuid]: self.stats[1] += 1 self.simrt.apply_scale(obj_uuid, self.unapply_scale(obj, scale)) self.scales[obj_uuid] = scale return obj_uuid def checkPool(self): """ Check the pool size and apply any needed adjustments. """ # check thread pool size if self.wanted_workers != self.exportSettings.pool_workers: current_workers = self.wanted_workers wanted_workers = self.exportSettings.pool_workers if current_workers < wanted_workers: self.pool.createWorkers(wanted_workers-current_workers) else: self.pool.dismissWorkers(current_workers-wanted_workers) self.wanted_workers = self.exportSettings.pool_workers def processUpdates(self): """ Process all pending updates up to the configured time budget. """ starttime = time.time() self._last_time = time.time() framebudget = float(self.exportSettings.rt_budget)/1000.0 try: self.pool.poll() except NoResultsPending: pass try: self.workpool.poll() except NoResultsPending: pass props = self.exportSettings if props.kbytesPerSecond*1024 != self._lastthrottle: self.simrt.Throttle(props.kbytesPerSecond*1024) # check consistency self.checkUuidConsistency(set(editor.getSelected())) # per second checks if time.time() - self.second_start > 1: self.checkPool() self.second_budget = 0 self.second_start = time.time() for check_cb in self._module_cb["check"]: check_cb(starttime, framebudget) self.checkObjects() # process command queue if time.time() - starttime < framebudget: self.processCommandQueue(starttime, framebudget) # redraw if we have commands left if len(self.command_queue): self.queueRedraw() def checkObjects(self): """ Check objects during operator process. This is the main place to perform checks on objects to see if we have to send any updates. Note at the moment also processUpdate performs some checks. """ selected = set(editor.getSelected()) all_selected = set() # look for changes in objects for obj in selected: obj_id = self.get_uuid(obj) if obj_id in self.selected and obj.as_pointer() == self.selected[obj_id].pointer: if obj.name != obj.opensim.name: if not obj.name.startswith('opensim') and not obj.name.startswith(obj.opensim.name): print("Sending New Name", obj.name, obj.opensim.name) self.simrt.SetName(obj_id, obj.name) obj.opensim.name = obj.name print(obj.opensim.name) def processCommandQueue(self, starttime, budget): """ Process the command queue up to the given budget, considering start of budget consumption at starttime. """ # the command queue can change while we execute here, but it should # be ok as long as things are just added at the end. # note if they are added at the beginning we would have problems # when deleting things after processing. self.command_queue += self.simrt.getQueue() cmds = self.command_queue second_budget = float(self.exportSettings.rt_sec_budget)/1000.0 currbudget = 0 self.stats[8] += 1 if cmds: self.stats[2] += 1 # first check the priority commands processed = [] for idx, cmd in enumerate(cmds): currbudget = time.time()-starttime if currbudget < budget and self.second_budget+currbudget < second_budget: if cmd[0] in priority_commands: processed.append(idx) self.processCommand(*cmd) else: break # delete all processed elements. in reversed order # to avoid problems because of index changing for idx in reversed(processed): cmds.pop(idx) # now all other commands, note there should be no priority # commands so we just ignore they exist and process all commands. if time.time()-starttime < budget: processed = [] for idx, cmd in enumerate(cmds): currbudget = time.time()-starttime if currbudget < budget and self.second_budget+currbudget < second_budget: processed.append(idx) self.processCommand(*cmd) else: break for idx in reversed(processed): cmds.pop(idx) self.second_budget += currbudget self.stats[5] = len(self.command_queue) self.stats[6] = (currbudget)*1000 # processed self.stats[7] = threading.activeCount()-1 def checkUuidConsistency(self, selected): """ Check the selection for uuid inconsistencies derived from object cloning or copying. """ # look for duplicates if self.rawselected == selected: return oldselected = self.selected newselected = {} isobjcopy = True for obj in selected: obj_uuid = obj.opensim.uuid if obj.type == 'MESH' and obj_uuid: mesh_uuid = obj.data.opensim.uuid if obj.opensim.uuid in oldselected: prevstate = oldselected[obj_uuid] if prevstate.pointer == obj.as_pointer(): newselected[obj_uuid] = oldselected[obj_uuid] if mesh_uuid: newselected[mesh_uuid] = oldselected[mesh_uuid] else: # check for copy or clone # copy or clone if mesh_uuid in oldselected and not oldselected[mesh_uuid].pointer == obj.data.as_pointer(): # copy ismeshcopy = True obj.data.opensim.uuid = "" else: # clone if mesh_uuid in oldselected: newselected[mesh_uuid] = oldselected[mesh_uuid] obj.opensim.uuid = "" obj.opensim.state = "OFFLINE" else: newselected[obj_uuid] = ObjectState(obj) newselected[mesh_uuid] = ObjectState(obj.data) self.selected = newselected self.rawselected = selected def processView(self): """ Process any needed updates from the view. """ self.stats[9] += 1 selected = set(editor.getSelected()) all_selected = set() # changes in our own avatar agent = self.findWithUUID(self.agent_id) if agent: self.processUpdate(agent) # look for changes in objects for obj in selected: obj_id = self.get_uuid(obj) if obj_id in self.selected and obj.as_pointer() == self.selected[obj_id].pointer: self.processUpdate(obj) all_selected.add(obj_id) # update selection if not all_selected == self.sim_selection: self.simrt.Select(*all_selected) self.sim_selection = all_selected def getSelected(self): """ Get the selected objects, possibly proxyed into editor objects. """ return editor.getSelected() # Callbacks to override in specific applications def addStatus(self, text, priority=0): """ Add a status line to the editor. """ pass def initGui(self, title): """ Init the gui. """ pass def go(self): """ Start the ogre interface system """ self.screen.activate() def addRegionsPanel(self, regions, griddata): """ Add the regions panel after getting the region list from /admin/regions """ pass def queueRedraw(self, pars=None, immediate=False): """ queue a redraw for the application. """ pass def applyObjectProperties(self, obj, pars): """ Apply object properties into the editor backend. """ pass def addRtCheckBox(self): """ Add a checkbox to enable or disable the real time agent. """ pass
class BaseApplication(Importer, Exporter): def __init__(self, title="RealXtend"): self.command_queue = [] self.wanted_workers = 1 self._callbacks = defaultdict(DefaultMap) self.second_start = time.time() self.second_budget = 0 self._lastthrottle = 0 self._last_time = time.time() self.pool = ThreadPool(1) self.workpool = ThreadPool(5) self.rawselected = set() self.caps = {} self.agent_id = "" self.loglevel = "standard" self.agent_access = "" self.rt_support = eventlet_present self.stats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] self.status = "b2rex started" self.status_level = OK self.selected = {} self.sim_selection = set() self.connected = False self.positions = {} self.rotations = {} self.scales = {} self.rt_on = False self.simrt = None self.screen = self self.gridinfo = GridInfo() self.buttons = {} self.settings_visible = False self._requested_urls = [] self._modules = {} self._module_cb = defaultdict(list) self.initializeCommands() self.initializeModules() Importer.__init__(self, self.gridinfo) Exporter.__init__(self, self.gridinfo) # Modules def registerModule(self, module): """ Register a module with the editor. The module will be registrered as an attribute in the editor, generally taking the name and removing "Module", so for example AssetModule becomes self.Asset. Also, some special callbacks will be looked for and registered for calling, at the moment we have the following: * check: check during the command processing stage * draw: gets drawn into the scene panel """ self._modules[module.getName()] = module module.register(self) setattr(self, module.getName(), module) for section in ["check", "draw", "draw_object"]: if hasattr(module, section): self._module_cb[section].append(getattr(module, section)) def drawModules(self, layout, props): """ Calls to draw all modules registered in the system. """ for draw_cb in self._module_cb["draw"]: draw_cb(layout, self, props) def drawObjectModules(self, layout, obj): """ Calls to draw all modules registered in the system. """ for draw_cb in self._module_cb["draw_object"]: draw_cb(layout, self, obj) # Generic callback functionality def add_callback(self, section, signal, callback, *parameters): """ Add a callback into a given section, waiting for a specific signal. The callback will be called with the specified parameters in expanded form. """ self._callbacks[str(section)][str(signal)].append( (callback, parameters)) def insert_callback(self, section, signal, callback, *parameters): """ Like ass_callback but inserts the callback at the beginning of the queue. """ self._callbacks[str(section)][str(signal)].insert( 0, (callback, parameters)) def trigger_callback(self, section, signal): """ Trigger the callback for the given section and signal. """ for callback, parameters in self._callbacks[str(section)][str(signal)]: callback(*parameters) del self._callbacks[str(section)][str(signal)] # Initialization def initializeModules(self): """ Initialize editor modules. """ self.registerModule(AssetModule(self)) self.registerModule(PrimsModule(self)) self.registerModule(MapModule(self)) self.registerModule(GameModule(self)) self.registerModule(CapsModule(self)) self.registerModule(ObjectModule(self)) self.registerModule(RexDataModule(self)) self.registerModule(ObjectPropertiesModule(self)) self.registerModule(RegionHandshakeModule(self)) self.registerModule(TerrainModule(self)) self.registerModule(StatsModule(self)) self.registerModule(OnlineModule(self)) self.registerModule(RexLogicModule(self)) self.registerModule(RexExportModule(self)) self.registerModule(AgentsModule(self)) self.registerModule(ScriptingModule(self)) self.registerModule(FsmEditorModule(self)) def initializeCommands(self): """ Initialize internal editor commands. """ self._cmd_matrix = {} self.registerCommand('pos', self.processPosCommand) self.registerCommand('rot', self.processRotCommand) self.registerCommand('scale', self.processScaleCommand) self.registerCommand('msg', self.processMsgCommand) self.registerCommand('connected', self.processConnectedCommand) # internal self.registerCommand('error', self.processError) self.registerCommand('agentquit', self.processAgentQuit) self.registerCommand('materialarrived', self.processMaterialArrived) self.registerCommand('texturearrived', self.processTextureArrived) # Threaded Downloader def addDownload(self, http_url, cb, cb_pars=(), error_cb=None, extra_main=None): """ Add a download into the internal threaded downloader. If the url is already downloaded False will be returned, otherwise the url will be downloaded, then the callback cb will be called with cb_pars. If extra_main is supplied it will be run in a thread before returning the final callback. If error_cb is supplied it will be called on errors. """ if http_url in self._requested_urls: return False self._requested_urls.append(http_url) if not error_cb: _error_cb = self.default_error_db else: def _error_cb(request, result): error_cb(result) def _cb(request, result): cb(result, *cb_pars) def _extra_cb(request, result): self.workpool.addRequest(extra_main, [[http_url, cb_pars, result]], _cb, _error_cb) if extra_main: _main_cb = _extra_cb else: _main_cb = _cb self.pool.addRequest(self.doDownload, [[http_url, cb_pars]], _main_cb, _error_cb) return True def default_error_db(self, request, error): """ Default error function for thread pool jobs. """ if hasattr(error[1], "code") and error[1].code in [404]: pass else: logger.warning("error downloading " + str(request) + ": " + str(error)) if hasattr(error[1], "code"): print("error downloading " + str(request) + ": " + str(error[1].code)) traceback.print_tb(error[2]) def doDownload(self, pars): """ Internal downloading function to be run in a thread. """ http_url, pars = pars req = urllib2.urlopen(http_url) return req.read() # Connection def processConnectedCommand(self, agent_id, agent_access): """ Connected messaage received from agent. """ self.agent_id = agent_id self.agent_access = agent_access self.addStatus("Connected to region") def connect(self, base_url, username="", password=""): """ Connect to an opensim instance """ self.sim.connect(base_url + '/xml-rpc.php') firstname, lastname = username.split() coninfo = self.sim.login(firstname, lastname, password) self._sim_port = coninfo['sim_port'] self._sim_ip = coninfo['sim_ip'] self._sim_url = 'http://' + str(self._sim_ip) + ':' + str( self._sim_port) logger.info("reconnect to " + self._sim_url) self.gridinfo.connect('http://' + str(self._sim_ip) + ':' + str(9000), username, password) self.sim.connect(self._sim_url) def onConnectAction(self): """ Connect Action """ base_url = self.exportSettings.server_url self.addStatus("Connecting to " + base_url, IMMEDIATE) self.region_uuid = '' self.regionLayout = None self.connect(base_url, self.exportSettings.username, self.exportSettings.password) try: self.regions = self.gridinfo.getRegions() self.griddata = self.gridinfo.getGridInfo() except: self.addStatus("Error: couldnt connect to " + base_url, ERROR) traceback.print_exc() return # create the regions panel self.addRegionsPanel(self.regions, self.griddata) if eventlet_present: self.addRtCheckBox() else: logger.warning("no support for real time communications") self.connected = True self.addStatus("Connected to " + self.griddata['gridnick']) # Rt enable/disable def onToggleRt(self, context=None): """ Toggle the real time agent. """ if context: if context.scene: # scene will not be defined when exiting the program self.exportSettings = context.scene.b2rex_props if self.rt_on: self.simrt.quit() self.rt_on = False self.simrt = None else: self.enableRt(context) self.rt_on = True for mod in self._modules.values(): mod.onToggleRt(self.rt_on) def enableRt(self, context): """ Enable the real time agent. """ # for the moment we fet some parameters in a different way depending # on the editor version. if sys.version_info[0] == 3: pars = self.exportSettings.getCurrentConnection() server_url = pars.url credentials = self.credentials else: pars = self.exportSettings server_url = pars.server_url credentials = self.exportSettings.credentials props = self.exportSettings # no functionality yet to change region. region_name = 'last' # we will send the editor version as our entrance punchline. firstline = editor.getVersion() # get the password from the credential manager since we dont store # it with other blender data. username, password = credentials.get_credentials( server_url, pars.username) # if defined we insert the agent library path into the environment. if props.agent_libs_path: os.environ['SIMRT_LIBS_PATH'] = props.agent_libs_path elif 'SIMRT_LIBS_PATH' in os.environ: del os.environ['SIMRT_LIBS_PATH'] # tools path, where python may be located if props.tools_path: os.environ['SIMRT_TOOLS_PATH'] = props.tools_path elif 'SIMRT_TOOLS_PATH' in os.environ: del os.environ['SIMRT_TOOLS_PATH'] # now setup the login parameters to be forwarded to the agent. login_params = {'region': region_name, 'firstline': firstline} # depending on the usename form we will use a different kind of login. if '@' in pars.username: # federated login auth_uri = pars.username.split('@')[1] con = SimConnection() con.connect('http://' + auth_uri) account = pars.username passwd_hash = '$1$' + md5(password.encode('ascii')).hexdigest() res = con._con.ClientAuthentication({ 'account': account, 'passwd': passwd_hash, 'loginuri': server_url }) avatarStorageUrl = res['avatarStorageUrl'] sessionHash = res['sessionHash'] gridUrl = res['gridUrl'] login_params['first'] = 'NotReallyNeeded' login_params['last'] = 'NotReallyNeeded' login_params['AuthenticationAddress'] = auth_uri login_params['account'] = pars.username login_params['passwd'] = passwd_hash login_params['sessionhash'] = sessionHash else: # normal opensim login login_params['first'] = pars.username.split()[0] login_params['last'] = pars.username.split()[1] login_params['passwd'] = password # start the agent. self.simrt = simrt.run_thread(self, server_url, login_params) self.connected = True # send our preferred throttle. self._lastthrottle = self.exportSettings.kbytesPerSecond * 1024 self.simrt.Throttle(self._lastthrottle) if not context: Blender.Window.QAdd(Blender.Window.GetAreaID(), Blender.Draw.REDRAW, 0, 1) # Redraw from thread def redraw(self): """ Queue a redraw of the application. This function is designed to be run from a thread, and for the moment is dangerous so can be controlled with a global toggle at b2rexpkg.safe_mode. """ if b2rexpkg.safe_mode: return if not self.stats[5] and self._last_time + 1 < time.time(): # we're using the commands left stats to keep our counter self.stats[5] += 1 self._last_time = time.time() self.queueRedraw(True) # Commands def registerCommand(self, cmd, callback): """ Register a command with the command processing subsystem. """ self._cmd_matrix[cmd] = callback def unregisterCommand(self, cmd): """ Unregister a command from the command processing subsystem. """ del self._cmd_matrix[cmd] def processCommand(self, cmd, *args): """ Process the given command with the given arguments. """ self.stats[0] += 1 cmdHandler = self._cmd_matrix.get(cmd, None) if cmdHandler: try: cmdHandler(*args) except Exception as e: print("Error executing", cmd, e) traceback.print_exc() # Material related callbacks def materialArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material has arrived from downloading. """ self.command_queue.append( ["materialarrived", data, objId, meshId, matId, assetType, matIdx]) def materialTextureArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material texture has arrived (a material with AssetType 0 which means there is no formal material declaration and we will use the texture directly). """ self.create_material_fromimage(matId, data, meshId, matIdx) def processMaterialArrived(self, data, objId, meshId, matId, assetType, matIdx): """ A material has arrived and has to be processed in the queue. """ if assetType == AssetType.OgreMaterial: self.parse_material(matId, { "name": matId, "data": data }, meshId, matIdx) # Object actions def doRtUpload(self, context): """ Upload the active object using the real time connection. """ selected = bpy.context.selected_objects if selected: # just the first for now selected = selected[0] if not selected.opensim.uuid: self.Object.doRtObjectUpload(context, selected) return def doDelete(self): """ Delete all selected objects from the simulator. """ selected = editor.getSelected() if selected: for obj in selected: if obj.opensim.uuid: self.simrt.Delete(obj.opensim.uuid) def doDeRezObject(self): """ DeRez all active objects into inventory. """ selected = editor.getSelected() if selected: for obj in selected: if obj.opensim.uuid: editor.set_loading_state(obj, 'TAKING') self.simrt.DeRezObject(obj.opensim.uuid) # Misc common functionality def registerTextureImage(self, image): """ Register an image with the system. """ # register a texture with the sim if not image.opensim.uuid: image.opensim.uuid = str(uuid.uuid4()) return image.opensim.uuid def processError(self, error): """ Error received from the agent. """ self.addStatus(error, ERROR) self.queueRedraw(immediate=True) def processAgentQuit(self, msg): """ Quit received from the agent. """ if self.rt_on: self.onToggleRt() self.queueRedraw() def processMsgCommand(self, username, message): """ Process a chat message, can be overloaded by additional modules ;) """ self.addStatus("message from " + username + ": " + message) def findWithUUID(self, objId): """ Find the editor object with the given uuid. """ obj = self.find_with_uuid(str(objId), bpy.data.objects, "objects") return obj def setMeshMaterials(self, mesh, materials): """ Set the materials for a mesh from an idx, matId, asset_type list. """ presentIds = list(map(lambda s: s.opensim.uuid, mesh.materials)) for idx, matId, asset_type in materials: mat = self.find_with_uuid(matId, bpy.data.materials, 'materials') if mat and not matId in presentIds: mesh.materials.append(mat) # Position commands def processPosCommand(self, objId, pos, rot=None): """ Position for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processPosCommand(obj, objId, pos) if rot: self._processRotCommand(obj, objId, rot) else: self.add_callback('object.create', objId, self.processPosCommand, objId, pos, rot) def processScaleCommand(self, objId, scale): """ Scale for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processScaleCommand(obj, objId, scale) else: self.add_callback('object.create', objId, self.processScaleCommand, objId, scale) def processRotCommand(self, objId, rot): """ Rotation for an object arrived from the agent. """ obj = self.findWithUUID(objId) if obj and editor.get_loading_state(obj) == 'OK': self._processRotCommand(obj, objId, rot) else: self.add_callback('object.create', objId, self.processRotCommand, objId, rot) # Checks def processUpdate(self, obj): """ Process all object updates not requiring modification of the object (executed from the view). """ obj_uuid = self.get_uuid(obj) if obj_uuid: pos, rot, scale = self.getObjectProperties(obj) pos = list(pos) rot = list(rot) scale = list(scale) # check parent if obj_uuid in self.selected: parent_pointer = None prevstate = self.selected[obj_uuid] if obj.parent and obj.parent.opensim.uuid: parent_pointer = obj.parent.as_pointer() if prevstate.parent != parent_pointer: if parent_pointer: parent_uuid = obj.parent.opensim.uuid self.simrt.Link(parent_uuid, obj_uuid) else: parent_uuid = prevstate.parent_uuid self.simrt.Unlink(parent_uuid, obj_uuid) # save properties and dont process position updates prevstate.update(obj) self.positions[obj_uuid] = pos self.rotations[obj_uuid] = rot self.scales[obj_uuid] = scale return obj_uuid if not obj_uuid in self.rotations or not rot == self.rotations[ obj_uuid]: self.stats[1] += 1 if obj.parent: self.simrt.apply_position( obj_uuid, self.unapply_position(obj, pos, 0, 0, 0), self.unapply_rotation(rot)) else: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos), self.unapply_rotation(rot)) self.positions[obj_uuid] = pos self.rotations[obj_uuid] = rot elif not obj_uuid in self.positions or not pos == self.positions[ obj_uuid]: self.stats[1] += 1 if obj.parent: self.simrt.apply_position( obj_uuid, self.unapply_position(obj, pos, 0, 0, 0)) else: self.simrt.apply_position(obj_uuid, self.unapply_position(obj, pos)) self.positions[obj_uuid] = pos if not obj_uuid in self.scales or not scale == self.scales[ obj_uuid]: self.stats[1] += 1 self.simrt.apply_scale(obj_uuid, self.unapply_scale(obj, scale)) self.scales[obj_uuid] = scale return obj_uuid def checkPool(self): """ Check the pool size and apply any needed adjustments. """ # check thread pool size if self.wanted_workers != self.exportSettings.pool_workers: current_workers = self.wanted_workers wanted_workers = self.exportSettings.pool_workers if current_workers < wanted_workers: self.pool.createWorkers(wanted_workers - current_workers) else: self.pool.dismissWorkers(current_workers - wanted_workers) self.wanted_workers = self.exportSettings.pool_workers def processUpdates(self): """ Process all pending updates up to the configured time budget. """ starttime = time.time() self._last_time = time.time() framebudget = float(self.exportSettings.rt_budget) / 1000.0 try: self.pool.poll() except NoResultsPending: pass try: self.workpool.poll() except NoResultsPending: pass props = self.exportSettings if props.kbytesPerSecond * 1024 != self._lastthrottle: self.simrt.Throttle(props.kbytesPerSecond * 1024) # check consistency self.checkUuidConsistency(set(editor.getSelected())) # per second checks if time.time() - self.second_start > 1: self.checkPool() self.second_budget = 0 self.second_start = time.time() for check_cb in self._module_cb["check"]: check_cb(starttime, framebudget) self.checkObjects() # process command queue if time.time() - starttime < framebudget: self.processCommandQueue(starttime, framebudget) # redraw if we have commands left if len(self.command_queue): self.queueRedraw() def checkObjects(self): """ Check objects during operator process. This is the main place to perform checks on objects to see if we have to send any updates. Note at the moment also processUpdate performs some checks. """ selected = set(editor.getSelected()) all_selected = set() # look for changes in objects for obj in selected: obj_id = self.get_uuid(obj) if obj_id in self.selected and obj.as_pointer( ) == self.selected[obj_id].pointer: if obj.name != obj.opensim.name: if not obj.name.startswith( 'opensim') and not obj.name.startswith( obj.opensim.name): print("Sending New Name", obj.name, obj.opensim.name) self.simrt.SetName(obj_id, obj.name) obj.opensim.name = obj.name print(obj.opensim.name) def processCommandQueue(self, starttime, budget): """ Process the command queue up to the given budget, considering start of budget consumption at starttime. """ # the command queue can change while we execute here, but it should # be ok as long as things are just added at the end. # note if they are added at the beginning we would have problems # when deleting things after processing. self.command_queue += self.simrt.getQueue() cmds = self.command_queue second_budget = float(self.exportSettings.rt_sec_budget) / 1000.0 currbudget = 0 self.stats[8] += 1 if cmds: self.stats[2] += 1 # first check the priority commands processed = [] for idx, cmd in enumerate(cmds): currbudget = time.time() - starttime if currbudget < budget and self.second_budget + currbudget < second_budget: if cmd[0] in priority_commands: processed.append(idx) self.processCommand(*cmd) else: break # delete all processed elements. in reversed order # to avoid problems because of index changing for idx in reversed(processed): cmds.pop(idx) # now all other commands, note there should be no priority # commands so we just ignore they exist and process all commands. if time.time() - starttime < budget: processed = [] for idx, cmd in enumerate(cmds): currbudget = time.time() - starttime if currbudget < budget and self.second_budget + currbudget < second_budget: processed.append(idx) self.processCommand(*cmd) else: break for idx in reversed(processed): cmds.pop(idx) self.second_budget += currbudget self.stats[5] = len(self.command_queue) self.stats[6] = (currbudget) * 1000 # processed self.stats[7] = threading.activeCount() - 1 def checkUuidConsistency(self, selected): """ Check the selection for uuid inconsistencies derived from object cloning or copying. """ # look for duplicates if self.rawselected == selected: return oldselected = self.selected newselected = {} isobjcopy = True for obj in selected: obj_uuid = obj.opensim.uuid if obj.type == 'MESH' and obj_uuid: mesh_uuid = obj.data.opensim.uuid if obj.opensim.uuid in oldselected: prevstate = oldselected[obj_uuid] if prevstate.pointer == obj.as_pointer(): newselected[obj_uuid] = oldselected[obj_uuid] if mesh_uuid: newselected[mesh_uuid] = oldselected[mesh_uuid] else: # check for copy or clone # copy or clone if mesh_uuid in oldselected and not oldselected[ mesh_uuid].pointer == obj.data.as_pointer(): # copy ismeshcopy = True obj.data.opensim.uuid = "" else: # clone if mesh_uuid in oldselected: newselected[mesh_uuid] = oldselected[mesh_uuid] obj.opensim.uuid = "" obj.opensim.state = "OFFLINE" else: newselected[obj_uuid] = ObjectState(obj) newselected[mesh_uuid] = ObjectState(obj.data) self.selected = newselected self.rawselected = selected def processView(self): """ Process any needed updates from the view. """ self.stats[9] += 1 selected = set(editor.getSelected()) all_selected = set() # changes in our own avatar agent = self.findWithUUID(self.agent_id) if agent: self.processUpdate(agent) # look for changes in objects for obj in selected: obj_id = self.get_uuid(obj) if obj_id in self.selected and obj.as_pointer( ) == self.selected[obj_id].pointer: self.processUpdate(obj) all_selected.add(obj_id) # update selection if not all_selected == self.sim_selection: self.simrt.Select(*all_selected) self.sim_selection = all_selected def getSelected(self): """ Get the selected objects, possibly proxyed into editor objects. """ return editor.getSelected() # Callbacks to override in specific applications def addStatus(self, text, priority=0): """ Add a status line to the editor. """ pass def initGui(self, title): """ Init the gui. """ pass def go(self): """ Start the ogre interface system """ self.screen.activate() def addRegionsPanel(self, regions, griddata): """ Add the regions panel after getting the region list from /admin/regions """ pass def queueRedraw(self, pars=None, immediate=False): """ queue a redraw for the application. """ pass def applyObjectProperties(self, obj, pars): """ Apply object properties into the editor backend. """ pass def addRtCheckBox(self): """ Add a checkbox to enable or disable the real time agent. """ pass