#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
"""
**Project Name:** MakeHuman
**Product Home Page:** http://www.makehuman.org/
**Code Home Page:** https://bitbucket.org/MakeHuman/makehuman/
**Authors:** Glynn Clements, Jonas Hauquier
**Copyright(c):** MakeHuman Team 2001-2015
**Licensing:** AGPL3 (http://www.makehuman.org/doc/node/the_makehuman_application.html)
This file is part of MakeHuman (www.makehuman.org).
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**Coding Standards:** See http://www.makehuman.org/node/165
Abstract
--------
Main application GUI component.
"""
import sys
import os
import glob
import imp
from core import G
import mh
from progress import Progress
import files3d
import gui3d
import geometry3d
import animation3d
import human
import skeleton
import guifiles
import managed_file
import algos3d
import gui
import language
import log
import contextlib
@contextlib.contextmanager
[docs]def outFile(path):
from codecs import open
path = mh.getPath(path)
tmppath = path + '.tmp'
try:
with open(tmppath, 'w', encoding="utf-8") as f:
yield f
if os.path.exists(path):
os.remove(path)
os.rename(tmppath, path)
except:
if os.path.exists(tmppath):
os.remove(tmppath)
log.error('unable to save file %s', path, exc_info=True)
@contextlib.contextmanager
[docs]def inFile(path):
from codecs import open
try:
path = mh.getPath(path)
if not os.path.isfile(path):
yield []
return
with open(path, 'rU', encoding="utf-8") as f:
yield f
except:
log.error('Failed to load file %s', path, exc_info=True)
[docs]class PluginCheckBox(gui.CheckBox):
def __init__(self, module):
super(PluginCheckBox, self).__init__(module, module not in gui3d.app.getSetting('excludePlugins'))
self.module = module
[docs] def onClicked(self, event):
if self.selected:
excludes = gui3d.app.getSetting('excludePlugins')
excludes.remove(self.module)
gui3d.app.setSetting('excludePlugins', excludes)
else:
excludes = gui3d.app.getSetting('excludePlugins')
excludes.append(self.module)
gui3d.app.setSetting('excludePlugins', excludes)
gui3d.app.saveSettings()
[docs]class PluginsTaskView(gui3d.TaskView):
def __init__(self, category):
gui3d.TaskView.__init__(self, category, 'Plugins')
self.scroll = self.addTopWidget(gui.VScrollArea())
self.pluginsBox = gui.GroupBox('Plugins')
self.pluginsBox.setSizePolicy(
gui.SizePolicy.MinimumExpanding,
gui.SizePolicy.MinimumExpanding)
self.scroll.setWidget(self.pluginsBox)
for module in sorted(gui3d.app.modules):
self.pluginsBox.addWidget(PluginCheckBox(module))
[docs]class SymmetryAction(gui3d.Action):
def __init__(self, human, direction):
super(SymmetryAction, self).__init__('Apply symmetry %s' % ("left" if direction == 'l' else "right"))
self.human = human
self.direction = direction
self.before = [(m.fullName, m.getValue()) for m in human.modifiers]
[docs] def do(self):
if self.direction == 'r':
self.human.applySymmetryRight()
else:
self.human.applySymmetryLeft()
self.human.applyAllTargets()
mh.redraw()
return True
[docs] def undo(self):
for (modifierName, value) in self.before:
self.human.getModifier(modifierName).setValue(value)
self.human.applyAllTargets()
mh.redraw()
return True
[docs]class MHApplication(gui3d.Application, mh.Application):
def __init__(self):
if G.app is not None:
raise RuntimeError('MHApplication is a singleton')
G.app = self
gui3d.Application.__init__(self)
mh.Application.__init__(self)
self.shortcuts = {
# Actions
'undo': (mh.Modifiers.CTRL, mh.Keys.z),
'redo': (mh.Modifiers.CTRL, mh.Keys.y),
'modelling': (mh.Modifiers.CTRL, mh.Keys.m),
'save': (mh.Modifiers.CTRL, mh.Keys.s),
'load': (mh.Modifiers.CTRL, mh.Keys.l),
'export': (mh.Modifiers.CTRL, mh.Keys.e),
'rendering': (mh.Modifiers.CTRL, mh.Keys.r),
'help': (mh.Modifiers.CTRL, mh.Keys.h),
'exit': (mh.Modifiers.CTRL, mh.Keys.q),
'stereo': (mh.Modifiers.CTRL, mh.Keys.w),
'wireframe': (mh.Modifiers.CTRL, mh.Keys.f),
'savetgt': (mh.Modifiers.ALT, mh.Keys.t),
'qexport': (mh.Modifiers.ALT, mh.Keys.e),
'smooth': (mh.Modifiers.ALT, mh.Keys.s),
'grab': (mh.Modifiers.ALT, mh.Keys.g),
'profiling': (mh.Modifiers.ALT, mh.Keys.p),
# Camera navigation
'rotateD': (0, mh.Keys.N2),
'rotateL': (0, mh.Keys.N4),
'rotateR': (0, mh.Keys.N6),
'rotateU': (0, mh.Keys.N8),
'panU': (0, mh.Keys.UP),
'panD': (0, mh.Keys.DOWN),
'panR': (0, mh.Keys.RIGHT),
'panL': (0, mh.Keys.LEFT),
'zoomIn': (0, mh.Keys.PLUS),
'zoomOut': (0, mh.Keys.MINUS),
'front': (0, mh.Keys.N1),
'right': (0, mh.Keys.N3),
'top': (0, mh.Keys.N7),
'back': (mh.Modifiers.CTRL, mh.Keys.N1),
'left': (mh.Modifiers.CTRL, mh.Keys.N3),
'bottom': (mh.Modifiers.CTRL, mh.Keys.N7),
'resetCam': (0, mh.Keys.PERIOD),
# Version check
'_versionSentinel': (0, 0x87654321)
}
self.mouseActions = {
(0, mh.Buttons.LEFT_MASK): self.mouseRotate,
(0, mh.Buttons.RIGHT_MASK): self.mouseZoom,
(0, mh.Buttons.MIDDLE_MASK): self.mouseTranslate,
(mh.Modifiers.CTRL, mh.Buttons.RIGHT_MASK): self.mouseFocus
}
self._undeclared_settings = dict()
if mh.isRelease():
self._default_settings = {
'realtimeUpdates': True,
'realtimeFitting': True,
'sliderImages': True,
'excludePlugins': [
"7_data",
"7_example",
"7_material_editor",
"7_profile",
"7_scene_editor",
"7_scripting",
"7_shell",
"7_targets",
],
'rtl': False,
'invertMouseWheel': False,
'lowspeed': 1,
'preloadTargets': True,
'cameraAutoZoom': False,
'language': 'english',
'highspeed': 5,
'realtimeNormalUpdates': False,
'units': 'metric',
'guiTheme': 'makehuman',
'restoreWindowSize': True,
'windowGeometry': ''
}
else:
self._default_settings = {
'realtimeUpdates': True,
'realtimeFitting': True,
'realtimeNormalUpdates': False,
'cameraAutoZoom': False,
'lowspeed': 1,
'highspeed': 5,
'units':'metric',
'invertMouseWheel':False,
'language':'english',
'excludePlugins':[],
'rtl': False,
'sliderImages': True,
'guiTheme': 'makehuman',
'preloadTargets': False,
'restoreWindowSize': True,
'windowGeometry': ''
}
self._settings = dict(self._default_settings)
self.loadHandlers = {}
self.saveHandlers = []
self.dialog = None
self.helpIds = set()
self.tool = None
self.selectedGroup = None
self.undoStack = []
self.redoStack = []
self.actions = None
self.clearColor = [0.5, 0.5, 0.5]
self.gridColor = [1.0, 1.0, 1.0]
self.gridSubColor = [0.7, 0.7, 0.7]
self.modules = {}
self.selectedHuman = None
self.currentFile = managed_file.File()
self._scene = None
self.backplaneGrid = None
self.groundplaneGrid = None
self.backgroundGradient = None
self.theme = None
@self.currentFile.mhEvent
def onModified(event):
self.updateFilenameCaption()
#self.modelCamera = mh.Camera()
#self.modelCamera.switchToOrtho()
self.modelCamera = mh.OrbitalCamera()
#self.modelCamera.debug = True
@self.modelCamera.mhEvent
def onChanged(event):
self.callEventHandlers('onCameraChanged', event)
mh.cameras.append(self.modelCamera)
#self.guiCamera = mh.Camera()
#self.guiCamera._fovAngle = 45
#self.guiCamera._eyeZ = 10
#self.guiCamera._projection = 0
# TODO use simpler camera for gui
self.guiCamera = mh.OrbitalCamera()
mh.cameras.append(self.guiCamera)
@property
[docs] def settings(self):
"""READ-ONLY dict of the settings of this application. Changing this
dict has NO impact."""
return dict(self._settings)
[docs] def addSetting(self, setting_name, default_value, value=None):
"""Declare a new setting for this application. Only has an impact the
first time it's called for a unique setting_name. It's impossible to
re-declare defaults for settings.
"""
if setting_name == 'version':
raise KeyError('The keyword "version" is protected for settings')
if setting_name in self._default_settings:
log.notice("Setting %s is already declared. Adding it again has no effect." % setting_name)
return
self._default_settings[setting_name] = default_value
if value is None:
if setting_name in self._undeclared_settings:
# Deferred set of setting value
log.debug("Assigning setting %s value %s that was loaded before the setting was declared." % (setting_name, self._undeclared_settings[setting_name]))
self._settings[setting_name] = self._undeclared_settings[setting_name]
del self._undeclared_settings[setting_name]
else:
self._settings[setting_name] = default_value
else:
self._settings[setting_name] = value
[docs] def getSetting(self, setting_name):
"""Retrieve the value of a setting.
"""
if setting_name not in self._default_settings:
raise KeyError("Setting %s is unknown, make sure to declare it first with addSetting()" % setting_name)
return self._settings.get(setting_name, self.getSettingDefault(setting_name))
[docs] def getSettingDefault(self, setting_name):
"""Retrieve the default value declared for a setting."""
return self._default_settings[setting_name]
[docs] def setSetting(self, setting_name, value):
"""Change the value of a setting. If value == None, the default value
for that setting is restored."""
if setting_name not in self._default_settings:
raise KeyError("Setting %s is not declared" % setting_name)
if value is None:
self._settings[setting_name] = self.getSettingDefault(setting_name)
else:
self._settings[setting_name] = value
[docs] def resetSettings(self):
"""Restore all settings to their defaults
"""
self._settings = dict(self._default_settings)
def _versionSentinel(self):
# dummy method used for checking the shortcuts.ini version
pass
@property
[docs] def args(self):
return G.args
[docs] def loadHumanMHM(self, filename):
self.selectedHuman.load(filename, True)
self.clearUndoRedo()
# Reset mesh is never forced to wireframe
self.actions.wireframe.setChecked(False)
# TO THINK: Maybe move guisave's saveMHM here as saveHumanMHM?
[docs] def loadHuman(self):
# Set a lower than default MAX_FACES value because we know the human has a good topology (will make it a little faster)
# (we do not lower the global limit because that would limit the selection of meshes that MH would accept too much)
self.selectedHuman = self.addObject(human.Human(files3d.loadMesh(mh.getSysDataPath("3dobjs/base.obj"), maxFaces = 5)))
# Set the base skeleton
base_skel = skeleton.load(mh.getSysDataPath('rigs/default.mhskel'), self.selectedHuman.meshData)
self.selectedHuman.setBaseSkeleton(base_skel)
[docs] def loadScene(self):
userSceneDir = mh.getDataPath("scenes")
if not os.path.exists(userSceneDir):
os.makedirs(userSceneDir)
from scene import Scene
from getpath import findFile
self.setScene( Scene(findFile("scenes/default.mhscene")) )
[docs] def loadMainGui(self):
@self.selectedHuman.mhEvent
def onMouseDown(event):
if self.tool:
self.selectedGroup = self.getSelectedFaceGroup()
self.tool.callEvent("onMouseDown", event)
else:
self.currentTask.callEvent("onMouseDown", event)
@self.selectedHuman.mhEvent
def onMouseMoved(event):
if self.tool:
self.tool.callEvent("onMouseMoved", event)
else:
self.currentTask.callEvent("onMouseMoved", event)
@self.selectedHuman.mhEvent
def onMouseDragged(event):
if self.tool:
self.tool.callEvent("onMouseDragged", event)
else:
self.currentTask.callEvent("onMouseDragged", event)
@self.selectedHuman.mhEvent
def onMouseUp(event):
if self.tool:
self.tool.callEvent("onMouseUp", event)
else:
self.currentTask.callEvent("onMouseUp", event)
@self.selectedHuman.mhEvent
def onMouseEntered(event):
if self.tool:
self.tool.callEvent("onMouseEntered", event)
else:
self.currentTask.callEvent("onMouseEntered", event)
@self.selectedHuman.mhEvent
def onMouseExited(event):
if self.tool:
self.tool.callEvent("onMouseExited", event)
else:
self.currentTask.callEvent("onMouseExited", event)
@self.selectedHuman.mhEvent
def onMouseWheel(event):
if self.tool:
self.tool.callEvent("onMouseWheel", event)
else:
self.currentTask.callEvent("onMouseWheel", event)
@self.selectedHuman.mhEvent
def onChanging(event):
self.callEventHandlers('onHumanChanging', event)
@self.selectedHuman.mhEvent
def onChanged(event):
self.actions.pose.setEnabled(self.selectedHuman.isPoseable())
if event.change == 'smooth':
# Update smooth action state (without triggering it)
self.actions.smooth.setChecked(self.selectedHuman.isSubdivided())
elif event.change in ['poseState', 'poseRefresh']:
self.actions.pose.setChecked(self.selectedHuman.isPosed())
elif event.change == 'load':
self.currentFile.loaded(event.path)
elif event.change == 'save':
self.currentFile.saved(event.path)
elif event.change == 'reset':
self.currentFile.closed()
self.callEventHandlers('onHumanChanged', event)
@self.selectedHuman.mhEvent
def onTranslated(event):
self.callEventHandlers('onHumanTranslated', event)
@self.selectedHuman.mhEvent
def onRotated(event):
self.callEventHandlers('onHumanRotated', event)
@self.selectedHuman.mhEvent
def onShown(event):
self.callEventHandlers('onHumanShown', event)
@self.selectedHuman.mhEvent
def onHidden(event):
self.callEventHandlers('onHumanHidden', event)
@self.modelCamera.mhEvent
def onRotated(event):
self.callEventHandlers('onCameraRotated', event)
# Set up categories and tasks
self.files = guifiles.FilesCategory(self)
self.getCategory("Modelling")
self.getCategory("Geometries")
self.getCategory("Materials")
self.getCategory("Pose/Animate")
self.getCategory("Rendering")
[docs] def loadPlugins(self):
# Load plugins not starting with _
pluginsToLoad = glob.glob(mh.getSysPath(os.path.join("plugins/",'[!_]*.py')))
# Load plugin packages (folders with a file called __init__.py)
for fname in os.listdir(mh.getSysPath("plugins/")):
if fname[0] != "_":
folder = os.path.join("plugins", fname)
if os.path.isdir(folder) and ("__init__.py" in os.listdir(folder)):
pluginsToLoad.append(folder)
pluginsToLoad.sort()
fprog = Progress(len(pluginsToLoad))
for path in pluginsToLoad:
self.loadPlugin(path)
fprog.step()
[docs] def loadPlugin(self, path):
try:
name, ext = os.path.splitext(os.path.basename(path))
if name not in self.getSetting('excludePlugins'):
log.message('Importing plugin %s', name)
#module = imp.load_source(name, path)
module = None
fp, pathname, description = imp.find_module(name, ["plugins/"])
try:
module = imp.load_module(name, fp, pathname, description)
finally:
if fp:
fp.close()
if module is None:
log.message("Could not import plugin %s", name)
return
self.modules[name] = module
log.message('Imported plugin %s', name)
log.message('Loading plugin %s', name)
module.load(self)
log.message('Loaded plugin %s', name)
# Process all non-user-input events in the queue to make sure
# any callAsync events are run.
self.processEvents()
else:
self.modules[name] = None
except Exception, _:
log.warning('Could not load %s', name, exc_info=True)
[docs] def unloadPlugins(self):
for name, module in self.modules.iteritems():
if module is None:
continue
try:
log.message('Unloading plugin %s', name)
module.unload(self)
log.message('Unloaded plugin %s', name)
except Exception, _:
log.warning('Could not unload %s', name, exc_info=True)
[docs] def getLoadedPlugins(self):
"""
Get the names of loaded plugins.
"""
return self.modules.keys()
[docs] def getPlugin(self, name):
"""
Get the (python) module of the plugin with specified name.
"""
return self.modules[name]
[docs] def loadGui(self):
progress = Progress(5)
category = self.getCategory('Settings')
category.addTask(PluginsTaskView(category))
progress.step()
mh.refreshLayout()
progress.step()
self.switchCategory("Modelling")
progress.step()
# Create viewport grid
self.loadGrid()
progress.step()
# Create background gradient
self.loadBackgroundGradient()
progress.step()
# self.progressBar.hide()
[docs] def loadGrid(self):
if self.backplaneGrid:
self.removeObject(self.backplaneGrid)
if self.groundplaneGrid:
self.removeObject(self.groundplaneGrid)
offset = self.selectedHuman.getJointPosition('ground')[1]
spacing = 1 if self.getSetting('units') == 'metric' else 3.048
# Background grid
gridSize = int(200/spacing)
if gridSize % 2 != 0:
gridSize += 1
if self.getSetting('units') == 'metric':
subgrids = 10
else:
subgrids = 12
backGridMesh = geometry3d.GridMesh(gridSize, gridSize, spacing, offset = -10, plane = 0, subgrids = subgrids)
backGridMesh.setMainColor(self.gridColor)
backGridMesh.setSubColor(self.gridSubColor)
backGridMesh.restrictVisibleToCamera = True
backGridMesh.minSubgridZoom = (1.0/spacing) * float(subgrids)/5
self.backplaneGrid = gui3d.Object(backGridMesh)
self.backplaneGrid.excludeFromProduction = True
self.backplaneGrid.placeAtFeet = True
self.backplaneGrid.lockRotation = True
self.backplaneGrid.setShadeless(1)
#self.backplaneGrid.setPosition([0,offset,0])
self.addObject(self.backplaneGrid)
# Ground grid
gridSize = int(20/spacing)
if gridSize % 2 != 0:
gridSize += 1
groundGridMesh = geometry3d.GridMesh(gridSize, gridSize, spacing, offset = 0, plane = 1, subgrids = subgrids)
groundGridMesh.setMainColor(self.gridColor)
groundGridMesh.setSubColor(self.gridSubColor)
groundGridMesh.minSubgridZoom = (1.0/spacing) * float(subgrids)/5
self.groundplaneGrid = gui3d.Object(groundGridMesh)
self.groundplaneGrid.excludeFromProduction = True
self.groundplaneGrid.placeAtFeet = True
self.groundplaneGrid.setShadeless(1)
#self.groundplaneGrid.setPosition([0,offset,0])
groundGridMesh.restrictVisibleAboveGround = True
self.addObject(self.groundplaneGrid)
self.actions.grid.setChecked(True)
[docs] def loadBackgroundGradient(self):
import numpy as np
if self.backgroundGradient:
self.removeObject(self.backgroundGradient)
mesh = geometry3d.RectangleMesh(10, 10, centered=True)
mesh.setColors(self.bgBottomLeftColor, self.bgBottomRightColor,
self.bgTopRightColor, self.bgTopLeftColor)
self.backgroundGradient = gui3d.Object(mesh)
self.backgroundGradient.priority = -200
self.backgroundGradient.excludeFromProduction = True
self.backgroundGradient.setShadeless(1)
self.backgroundGradient.material.configureShading(vertexColors=True)
self.addObject(self.backgroundGradient)
self._updateBackgroundDimensions()
[docs] def onResizedCallback(self, event):
gui3d.Application.onResizedCallback(self, event)
self._updateBackgroundDimensions()
def _updateBackgroundDimensions(self, width=G.windowWidth, height=G.windowHeight):
if self.backgroundGradient is None:
return
cam = self.backgroundGradient.mesh.getCamera()
#minX,minY,_ = cam.convertToWorld2D(0,0)
#maxX,maxY,_ = cam.convertToWorld2D(width, height)
#self.backgroundGradient.mesh.resize(abs(maxX - minX), abs(maxY - minY))
# TODO hack for orbital camera, properly clean this up some day
height = cam.getScale()
aspect = cam.getAspect()
width = height * aspect
self.backgroundGradient.mesh.resize(2.1*width, 2.1*height)
self.backgroundGradient.setPosition([0, 0, -0.85*cam.farPlane])
[docs] def loadMacroTargets(self):
"""
Preload all target files belonging to group macrodetails and its child
groups.
"""
import targets
#import getpath
for target in targets.getTargets().findTargets('macrodetails'):
#log.debug('Preloading target %s', getpath.getRelativePath(target.path))
algos3d.getTarget(self.selectedHuman.meshData, target.path)
[docs] def loadFinish(self):
self.selectedHuman.updateMacroModifiers()
self.selectedHuman.applyAllTargets()
self.currentFile.modified = False
#printtree(self)
mh.changeCategory("Modelling")
self.redraw()
[docs] def startupSequence(self):
self._processCommandlineArgs(beforeLoaded = True)
mainwinGeometry = self.mainwin.storeGeometry()
mainwinBorder = (self.mainwin.frameGeometry().width() - self.mainwin.width(),
self.mainwin.frameGeometry().height() - self.mainwin.height())
# Move main window completely behind splash screen
self.mainwin.resize(self.splash.width() - mainwinBorder[0], self.splash.height() - mainwinBorder[1])
self.mainwin.move(self.splash.pos())
#self.splash.setFormat('<br><br><b><font size="10" color="#ffffff">%s</font></b>')
progress = Progress([36, 6, 15, 333, 40, 154, 257, 5], messaging=True)
progress.firststep('Loading human')
self.loadHuman()
progress.step('Loading scene')
self.loadScene()
progress.step('Loading main GUI')
self.loadMainGui()
progress.step('Loading plugins')
self.loadPlugins()
progress.step('Loading GUI')
self.loadGui()
progress.step('Loading theme')
try:
self.setTheme(self.getSetting('guiTheme'))
except:
self.setTheme("default")
progress.step('Applying targets')
self.loadFinish()
progress.step('Loading macro targets')
if self.getSetting('preloadTargets'):
self.loadMacroTargets()
progress.step('Loading done')
log.message('') # Empty status indicator
if sys.platform.startswith("darwin"):
self.splash.resize(0,0) # work-around for mac splash-screen closing bug
self.mainwin.show()
self.splash.hide()
# self.splash.finish(self.mainwin)
self.splash.close()
self.splash = None
self.prompt('Warning', 'MakeHuman is a character creation suite. It is designed for making anatomically correct humans.\nParts of this program may contain nudity.\nDo you want to proceed?', 'Yes', 'No', None, self.stop, 'nudityWarning')
if not self.args.get('noshaders', False) and \
( not mh.Shader.supported() or mh.Shader.glslVersion() < (1,20) ):
self.prompt('Warning', 'Your system does not support OpenGL shaders (GLSL v1.20 required).\nOnly simple shading will be available.', 'Ok', None, None, None, 'glslWarning')
# Restore main window size and position
geometry = self.getSetting('windowGeometry')
if self.getSetting('restoreWindowSize') and geometry:
self.mainwin.restoreGeometry(geometry)
else:
self.mainwin.restoreGeometry(mainwinGeometry)
self._processCommandlineArgs(beforeLoaded = False)
def _processCommandlineArgs(self, beforeLoaded):
if beforeLoaded:
if self.args.get('noshaders', False):
log.message("Force shaders disabled")
else: # After application is loaded
if self.args.get('mhmFile', None):
import getpath
mhmFile = getpath.pathToUnicode( self.args.get('mhmFile') )
log.message("Loading MHM file %s (as specified by commandline argument)", mhmFile)
if not os.path.isfile(mhmFile):
mhmFile = getpath.findFile(mhmFile, mh.getPath("models"))
if os.path.isfile(mhmFile):
self.loadHumanMHM(mhmFile)
else:
log.error("Failed to load MHM file. The MHM file specified as argument (%s) does not exist!", mhmFile)
if self.args.get('runtests', False):
log.message("Running test suite")
import testsuite
testsuite.runAll()
# Events
[docs] def onStart(self, event):
self.startupSequence()
[docs] def onStop(self, event):
if self.getSetting('restoreWindowSize'):
self.setSetting('windowGeometry', self.mainwin.storeGeometry())
self.saveSettings(True)
self.unloadPlugins()
self.dumpMissingStrings()
[docs] def onQuit(self, event):
self.promptAndExit()
[docs] def onMouseDown(self, event):
if self.selectedHuman.isVisible():
# Normalize modifiers
modifiers = mh.getKeyModifiers() & (mh.Modifiers.CTRL | mh.Modifiers.ALT | mh.Modifiers.SHIFT)
if (modifiers, event.button) in self.mouseActions:
action = self.mouseActions[(modifiers, event.button)]
if action == self.mouseFocus:
self.modelCamera.mousePickHumanFocus(event.x, event.y)
elif action == self.mouseZoom:
self.modelCamera.mousePickHumanCenter(event.x, event.y)
[docs] def onMouseDragged(self, event):
if self.selectedHuman.isVisible():
# Normalize modifiers
modifiers = mh.getKeyModifiers() & (mh.Modifiers.CTRL | mh.Modifiers.ALT | mh.Modifiers.SHIFT)
if (modifiers, event.button) in self.mouseActions:
self.mouseActions[(modifiers, event.button)](event)
[docs] def onMouseWheel(self, event):
if self.selectedHuman.isVisible():
zoomOut = event.wheelDelta > 0
if self.getSetting('invertMouseWheel'):
zoomOut = not zoomOut
if event.x is not None:
self.modelCamera.mousePickHumanCenter(event.x, event.y)
if zoomOut:
self.zoomOut()
else:
self.zoomIn()
# Undo-redo
[docs] def do(self, action):
if action.do():
self.undoStack.append(action)
del self.redoStack[:]
self.currentFile.changed()
log.message('do %s', action.name)
self.syncUndoRedo()
[docs] def did(self, action):
self.undoStack.append(action)
self.currentFile.changed()
del self.redoStack[:]
log.message('did %s', action.name)
self.syncUndoRedo()
[docs] def undo(self):
if self.undoStack:
action = self.undoStack.pop()
log.message('undo %s', action.name)
action.undo()
self.redoStack.append(action)
self.currentFile.changed()
self.syncUndoRedo()
[docs] def redo(self):
if self.redoStack:
action = self.redoStack.pop()
log.message('redo %s', action.name)
action.do()
self.undoStack.append(action)
self.currentFile.changed()
self.syncUndoRedo()
[docs] def syncUndoRedo(self):
self.actions.undo.setEnabled(bool(self.undoStack))
self.actions.redo.setEnabled(bool(self.redoStack))
self.redraw()
[docs] def clearUndoRedo(self):
self.undoStack = []
self.redoStack = []
self.syncUndoRedo()
# Settings
[docs] def loadSettings(self):
with inFile("settings.ini") as f:
if f:
settings = mh.parseINI(f.read())
if 'version' in settings and settings['version'] == mh.getVersionDigitsStr():
# Only load settings for this specific version
del settings['version']
for setting_name, value in settings.items():
try:
self.setSetting(setting_name, value)
except:
# Store the values of (yet) undeclared settings and defer until plugins are loaded
self._undeclared_settings[setting_name] = value
else:
log.warning("Incompatible MakeHuman settings (version %s) detected (expected %s). Loading default settings." % (settings.get('version','undefined'), mh.getVersionDigitsStr()))
else:
log.warning("No settings file found, starting with default settings.")
if 'language' in self.settings:
self.setLanguage(self.settings['language'])
gui.Slider.showImages(self.settings['sliderImages'])
with inFile("shortcuts.ini") as f:
shortcuts = {}
for line in f:
modifier, key, action = line.strip().split(' ')
shortcuts[action] = (int(modifier), int(key))
if shortcuts.get('_versionSentinel') != (0, 0x87654321):
log.warning('shortcuts.ini out of date; ignoring')
else:
self.shortcuts.update(shortcuts)
with inFile("mouse.ini") as f:
mouseActions = dict([(method.__name__, shortcut)
for shortcut, method in self.mouseActions.iteritems()])
for line in f:
modifier, button, method = line.strip().split(' ')
if hasattr(self, method):
mouseActions[method] = (int(modifier), int(button))
self.mouseActions = dict([(shortcut, getattr(self, method))
for method, shortcut in mouseActions.iteritems()])
with inFile("help.ini") as f:
helpIds = set()
for line in f:
helpIds.add(line.strip())
if self.dialog is not None:
self.dialog.helpIds.update(self.helpIds)
self.helpIds = helpIds
[docs] def saveSettings(self, promptOnFail=False):
try:
if not os.path.exists(mh.getPath()):
os.makedirs(mh.getPath())
with outFile("settings.ini") as f:
settings = self.settings
settings['version'] = mh.getVersionDigitsStr()
f.write(mh.formatINI(settings))
with outFile("shortcuts.ini") as f:
for action, shortcut in self.shortcuts.iteritems():
f.write('%d %d %s\n' % (shortcut[0], shortcut[1], action))
with outFile("mouse.ini") as f:
for mouseAction, method in self.mouseActions.iteritems():
f.write('%d %d %s\n' % (mouseAction[0], mouseAction[1], method.__name__))
if self.dialog is not None:
self.helpIds.update(self.dialog.helpIds)
with outFile("help.ini") as f:
for helpId in self.helpIds:
f.write('%s\n' % helpId)
except:
log.error('Failed to save settings file', exc_info=True)
if promptOnFail:
self.prompt('Error', 'Could not save settings file.', 'OK')
# Themes
[docs] def setTheme(self, theme):
# Disabling this check allows faster testing of a skin by reloading it.
#if self.theme == theme:
# return
# Set defaults
self.clearColor = [0.5, 0.5, 0.5]
self.gridColor = [1.0, 1.0, 1.0]
self.gridSubColor = [0.7, 0.7, 0.7]
log._logLevelColors[log.DEBUG] = 'grey'
log._logLevelColors[log.NOTICE] = 'blue'
log._logLevelColors[log.WARNING] = 'darkorange'
log._logLevelColors[log.ERROR] = 'red'
log._logLevelColors[log.CRITICAL] = 'red'
self.bgBottomLeftColor = [0.101, 0.101, 0.101]
self.bgBottomRightColor = [0.101, 0.101, 0.101]
self.bgTopLeftColor = [0.312, 0.312, 0.312]
self.bgTopRightColor = [0.312, 0.312, 0.312]
f = open(os.path.join(mh.getSysDataPath("themes/"), theme + ".mht"), 'rU')
update_log = False
for data in f.readlines():
lineData = data.split()
if len(lineData) > 0:
if lineData[0] == "version":
log.message('Theme %s version %s', theme, lineData[1])
elif lineData[0] == "color":
if lineData[1] == "clear":
self.clearColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "grid":
self.gridColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "subgrid":
self.gridSubColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "bgbottomleft":
self.bgBottomLeftColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "bgbottomright":
self.bgBottomRightColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "bgtopleft":
self.bgTopLeftColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[1] == "bgtopright":
self.bgTopRightColor[:] = [float(val) for val in lineData[2:5]]
elif lineData[0] == "logwindow_color":
logLevel = lineData[1]
if hasattr(log, logLevel) and isinstance(getattr(log, logLevel), int):
update_log = True
logLevel = int(getattr(log, logLevel))
log._logLevelColors[logLevel] = lineData[2]
if self.groundplaneGrid:
self.groundplaneGrid.mesh.setMainColor(self.gridColor)
self.groundplaneGrid.mesh.setSubColor(self.gridSubColor)
if self.backplaneGrid:
self.backplaneGrid.mesh.setMainColor(self.gridColor)
self.backplaneGrid.mesh.setSubColor(self.gridSubColor)
if self.backgroundGradient:
self.backgroundGradient.mesh.setColors(self.bgBottomLeftColor,
self.bgBottomRightColor,
self.bgTopRightColor,
self.bgTopLeftColor)
mh.setClearColor(self.clearColor[0], self.clearColor[1], self.clearColor[2], 1.0)
if update_log:
self.log_window.updateView()
log.debug("Loaded theme %s", mh.getSysDataPath('themes/'+theme+'.mht'))
try:
f = open(mh.getSysDataPath('themes/%s.qss' % theme), 'r')
qStyle = "\n".join(f.readlines())
self.setStyleSheet(qStyle)
# Also set stylesheet on custom slider style
for widget in self.allWidgets():
if isinstance(widget, gui.Slider):
widget.setStyleSheet(qStyle)
log.debug("Loaded Qt style %s", mh.getSysDataPath('themes/'+theme+'.qss'))
except:
self.setStyleSheet("")
# Also set stylesheet on custom slider style
for widget in self.allWidgets():
if isinstance(widget, gui.Slider):
widget.setStyleSheet("")
'''
if theme != "default":
log.warning('Could not open Qt style file %s.', mh.getSysDataPath('themes/'+theme+'.qss'))
'''
self.theme = theme
self.reloadIcons()
self.redraw()
[docs] def reloadIcons(self):
if not self.actions:
return
for action in self.actions:
action.setIcon(gui.Action.getIcon(action.name))
[docs] def getLookAndFeelStyles(self):
return [ str(style) for style in gui.QtGui.QStyleFactory.keys() ]
[docs] def setLookAndFeel(self, platform):
style = gui.QtGui.QStyleFactory.create(platform)
self.setStyle(style)
[docs] def getLookAndFeel(self):
return str(self.style().objectName())
[docs] def getThemeResource(self, folder, id):
if '/' in id:
return id
path = os.path.join(mh.getSysDataPath("themes/"), self.theme, folder, id)
if os.path.exists(path):
return path
else:
return os.path.join(mh.getSysDataPath("themes/default/"), folder, id)
[docs] def setLanguage(self, lang):
log.debug("Setting language to %s", lang)
language.language.setLanguage(lang)
self.setSetting('rtl', language.language.rtl)
[docs] def getLanguages(self):
"""
The languages available on this MH installation, by listing all .json
files in the languages folder in user and system data path.
"""
return language.getLanguages()
[docs] def getLanguageString(self, string, appendData=None, appendFormat=None):
return language.language.getLanguageString(string,appendData,appendFormat)
[docs] def dumpMissingStrings(self):
language.language.dumpMissingStrings()
# Caption
[docs] def setCaption(self, caption):
"""Set the main window caption."""
mh.setCaption(caption)
[docs] def updateFilenameCaption(self):
"""Calculate and set the window title according to the
name of the current open file and the version of MH."""
filename = self.currentFile.name
if filename is None:
filename = "Untitled"
if mh.isRelease():
from getpath import pathToUnicode
self.setCaption(
"MakeHuman %s - [%s][*]" %
(mh.getVersionStr(), pathToUnicode(filename)))
else:
from getpath import pathToUnicode
self.setCaption(
"MakeHuman r%s (%s) - [%s][*]" %
(os.environ['HGREVISION'], os.environ['HGNODEID'],
pathToUnicode(filename)))
self.mainwin.setWindowModified(self.currentFile.modified)
# Global status bar
[docs] def status(self, text, *args):
if self.statusBar is None:
return
self.statusBar.showMessage(text, *args)
[docs] def statusPersist(self, text, *args):
if self.statusBar is None:
return
self.statusBar.setMessage(text, *args)
# Global progress bar
[docs] def progress(self, value, text=None, *args):
if text is not None:
self.status(text, *args)
if self.splash:
self.splash.setProgress(value)
self.splash.raise_()
if self.progressBar is None:
return
if value >= 1.0:
self.progressBar.reset()
else:
self.progressBar.setProgress(value)
self.mainwin.canvas.blockRedraw = True
# Process all non-user-input events in the queue to run callAsync tasks.
# This is invoked here so events are processed in every step during the
# onStart() init sequence.
self.processEvents()
self.mainwin.canvas.blockRedraw = False
# Global dialog
[docs] def prompt(self, title, text, button1Label, button2Label=None, button1Action=None, button2Action=None, helpId=None, fmtArgs = None):
if fmtArgs is None:
fmtArgs = []
elif isinstance(fmtArgs, basestring):
fmtArgs = [fmtArgs]
if self.dialog is None:
self.dialog = gui.Dialog(self.mainwin)
self.dialog.helpIds.update(self.helpIds)
return self.dialog.prompt(title, text, button1Label, button2Label, button1Action, button2Action, helpId, fmtArgs)
[docs] def about(self):
"""
Show about dialog
"""
#gui.QtGui.QMessageBox.about(self.mainwin, 'About MakeHuman', mh.getCopyrightMessage())
#aboutbox = gui.AboutBox(self.mainwin, 'About MakeHuman', mh.getCopyrightMessage())
abouttext = '<h1>MakeHuman license</h1>' + mh.getCopyrightMessage() + "\n" + mh.getCredits(richtext=True) + "\n\n" + mh.getSoftwareLicense(richtext=True) + "\n\n\n" + mh.getThirdPartyLicenses(richtext=True)
aboutbox = gui.AboutBoxScrollbars(self.mainwin, 'About MakeHuman', abouttext, "MakeHuman v"+mh.getVersionStr(verbose=False, full=True))
aboutbox.show()
aboutbox.exec_()
[docs] def setGlobalCamera(self):
human = self.selectedHuman
tl = animation3d.Timeline(0.20)
tl.append(animation3d.PathAction(self.modelCamera, [self.modelCamera.getPosition(), [0.0, 0.0, 0.0]]))
tl.append(animation3d.RotateAction(self.modelCamera, self.modelCamera.getRotation(), [0.0, 0.0, 0.0]))
tl.append(animation3d.ZoomAction(self.modelCamera, self.modelCamera.zoomFactor, 1.0))
tl.append(animation3d.UpdateAction(self))
tl.start()
[docs] def setTargetCamera(self, vIdx, zoomFactor = 1.0, animate = True):
if isinstance(vIdx, (tuple, list)):
return
human = self.selectedHuman
coord = human.meshData.coord[vIdx]
direction = human.meshData.vnorm[vIdx].copy()
self.modelCamera.focusOn(coord, direction, zoomFactor, animate)
if not animate:
self.redraw()
[docs] def setFaceCamera(self):
self.setTargetCamera(132, 8.7)
[docs] def setLeftHandFrontCamera(self):
self.setTargetCamera(9828, 10)
[docs] def setLeftHandTopCamera(self):
self.setTargetCamera(9833, 10)
[docs] def setRightHandFrontCamera(self):
self.setTargetCamera(3160, 10)
[docs] def setRightHandTopCamera(self):
self.setTargetCamera(3165, 10)
[docs] def setLeftArmFrontCamera(self):
self.setTargetCamera(9981, 4.2)
[docs] def setLeftArmTopCamera(self):
self.setTargetCamera(9996, 2.9)
[docs] def setRightArmFrontCamera(self):
self.setTargetCamera(3330, 4.2)
[docs] def setRightArmTopCamera(self):
self.setTargetCamera(3413, 2.9)
[docs] def setLeftLegFrontCamera(self):
self.setTargetCamera(11325, 2.7)
[docs] def setLeftLegLeftCamera(self):
self.setTargetCamera(11381, 2.3)
[docs] def setRightLegFrontCamera(self):
self.setTargetCamera(4707, 2.7)
[docs] def setRightLegRightCamera(self):
self.setTargetCamera(4744, 2.3)
[docs] def getScene(self):
"""
The scene used for rendering the viewport.
"""
return self._scene
[docs] def setScene(self, scene):
"""
Set the scene used for rendering the viewport,
and connect its events with appropriate handler methods.
"""
setSceneEvent = managed_file.FileModifiedEvent.fromObjectAssignment(
scene.file if scene else None,
self._scene.file if self._scene else None)
self._scene = scene
if self._scene is None:
return
@self._scene.file.mhEvent
def onModified(event):
self._sceneChanged(event)
self._sceneChanged(setSceneEvent)
scene = property(getScene, setScene)
def _sceneChanged(self, event):
"""
Method to be called internally when the scene is modified,
that updates the view according to the modified scene,
and emits the onSceneChanged event application - wide.
"""
if event.file != self.scene.file:
return
if event.objectWasChanged:
from glmodule import setSceneLighting
setSceneLighting(self.scene)
for category in self.categories.itervalues():
self.callEventHandlers('onSceneChanged', event)
# Shortcuts
[docs] def setShortcut(self, modifier, key, action):
shortcut = (modifier, key)
if shortcut in self.shortcuts.values():
self.prompt('Warning', 'This combination is already in use.', 'OK', helpId='shortcutWarning')
return False
self.shortcuts[action.name] = shortcut
mh.setShortcut(modifier, key, action)
return True
[docs] def getShortcut(self, action):
return self.shortcuts.get(action.name)
# Mouse actions
[docs] def setMouseAction(self, modifier, key, method):
mouseAction = (modifier, key)
if mouseAction in self.mouseActions:
self.prompt('Warning', 'This combination is already in use.', 'OK', helpId='mouseActionWarning')
return False
# Remove old entry
for s, m in self.mouseActions.iteritems():
if m == method:
del self.mouseActions[s]
break
self.mouseActions[mouseAction] = method
#for mouseAction, m in self.mouseActions.iteritems():
# print mouseAction, m
return True
[docs] def getMouseAction(self, method):
for mouseAction, m in self.mouseActions.iteritems():
if m == method:
return mouseAction
# Load handlers
[docs] def addLoadHandler(self, keyword, handler):
"""Register a handler for handling the loading of the specified
keyword from MHM file."""
self.loadHandlers[keyword] = handler
[docs] def getLoadHandler(self, keyword):
"""Retrieve the plugin or handler that handles the loading of the
specified keyword from MHM file.
"""
self.loadHandlers.get(keyword, None)
# Save handlers
[docs] def addSaveHandler(self, handler, priority = None):
"""
Register a handler to trigger when a save action happens, when called
the handler gets the chance to write property lines to the MHM file.
If priority is specified, should be an integer number > 0.
0 is highest priority.
"""
if priority is None:
self.saveHandlers.append(handler)
else:
# TODO more robust solution for specifying priority weights
self.saveHandlers.insert(priority, handler)
# Shortcut methods
[docs] def goToModelling(self):
mh.changeCategory("Modelling")
self.redraw()
[docs] def doSave(self):
if self.currentFile.path:
from guisave import saveMHM
self.currentTask.hide()
saveMHM(self.currentFile.path)
self.currentTask.show()
self.redraw()
else:
self.goToSave()
[docs] def goToSave(self):
mh.changeTask("Files", "Save")
self.redraw()
[docs] def goToLoad(self):
mh.changeTask("Files", "Load")
self.redraw()
[docs] def goToExport(self):
mh.changeTask("Files", "Export")
self.redraw()
[docs] def goToRendering(self):
mh.changeCategory("Rendering")
self.redraw()
[docs] def goToHelp(self):
mh.changeCategory("Help")
[docs] def toggleSolid(self):
self.selectedHuman.setSolid(not self.actions.wireframe.isChecked())
self.redraw()
[docs] def toggleSubdivision(self):
self.selectedHuman.setSubdivided(self.actions.smooth.isChecked(), True)
self.redraw()
[docs] def togglePose(self):
self.selectedHuman.setPosed(self.actions.pose.isChecked())
self.redraw()
[docs] def toggleGrid(self):
if self.backplaneGrid and self.groundplaneGrid:
self.backplaneGrid.setVisibility( self.actions.grid.isChecked() )
self.groundplaneGrid.setVisibility( self.actions.grid.isChecked() )
self.redraw()
[docs] def symmetryRight(self):
human = self.selectedHuman
self.do( SymmetryAction(human, 'r') )
[docs] def symmetryLeft(self):
human = self.selectedHuman
self.do( SymmetryAction(human, 'l') )
[docs] def symmetry(self):
human = self.selectedHuman
human.symmetryModeEnabled = self.actions.symmetry.isChecked()
[docs] def saveTarget(self, path=None):
"""
Export the current modifications to the human as one single target,
relative to the basemesh.
"""
if path is None:
path = mh.getPath("full_target.target")
if os.path.splitext(path)[1] != '.target':
raise RuntimeError("Cannot save target to file %s, expected a path to a .target file." % path)
human = self.selectedHuman
algos3d.saveTranslationTarget(human.meshData, path)
log.message("Full target exported to %s", path)
[docs] def grabScreen(self):
import datetime
grabPath = mh.getPath('grab')
if not os.path.exists(grabPath):
os.makedirs(grabPath)
grabName = datetime.datetime.now().strftime('grab_%Y-%m-%d_%H.%M.%S.png')
filename = os.path.join(grabPath, grabName)
mh.grabScreen(0, 0, G.windowWidth, G.windowHeight, filename)
self.status("Screengrab saved to %s", filename)
[docs] def resetHuman(self):
if self.currentFile.modified:
self.prompt('Reset', 'By resetting the human you will lose all your changes, are you sure?', 'Yes', 'No', self._resetHuman)
else:
self._resetHuman()
def _resetHuman(self):
self.selectedHuman.resetMeshValues()
self.selectedHuman.applyAllTargets()
self.clearUndoRedo()
# Reset mesh is never forced to wireframe
self.actions.wireframe.setChecked(False)
# Camera navigation
[docs] def rotateCamera(self, axis, amount):
self.modelCamera.addRotation(axis, amount)
if axis == 1 and self.modelCamera.getRotation()[1] in [0, 90, 180, 270]:
# Make sure that while rotating the grid never appears
self.modelCamera.addRotation(1, 0.001)
self.redraw()
[docs] def panCamera(self, axis, amount):
self.modelCamera.addTranslation(axis, amount)
self.redraw()
[docs] def cameraSpeed(self):
if mh.getKeyModifiers() & mh.Modifiers.SHIFT:
return self.getSetting('highspeed')
else:
return self.getSetting('lowspeed')
[docs] def zoomCamera(self, amount):
self.modelCamera.addZoom(amount * self.cameraSpeed())
self.redraw()
[docs] def rotateAction(self, axis):
return animation3d.RotateAction(self.modelCamera, self.modelCamera.getRotation(), axis)
[docs] def axisView(self, axis):
tmp = self.modelCamera.limitInclination
self.modelCamera.limitInclination = False
animation3d.animate(self, 0.20, [self.rotateAction(axis)])
self.modelCamera.limitInclination = tmp
[docs] def rotateDown(self):
self.rotateCamera(0, 5.0)
[docs] def rotateUp(self):
self.rotateCamera(0, -5.0)
[docs] def rotateLeft(self):
self.rotateCamera(1, -5.0)
[docs] def rotateRight(self):
self.rotateCamera(1, 5.0)
[docs] def panUp(self):
self.panCamera(1, 0.05)
[docs] def panDown(self):
self.panCamera(1, -0.05)
[docs] def panRight(self):
self.panCamera(0, 0.05)
[docs] def panLeft(self):
self.panCamera(0, -0.05)
[docs] def zoomOut(self):
self.zoomCamera(0.65)
[docs] def zoomIn(self):
self.zoomCamera(-0.65)
[docs] def frontView(self):
self.axisView([0.0, 0.0, 0.0])
[docs] def rightView(self):
self.axisView([0.0, 90.0, 0.0])
[docs] def topView(self):
self.axisView([90.0, 0.0, 0.0])
[docs] def backView(self):
self.axisView([0.0, 180.0, 0.0])
[docs] def leftView(self):
self.axisView([0.0, -90.0, 0.0])
[docs] def bottomView(self):
self.axisView([-90.0, 0.0, 0.0])
[docs] def resetView(self):
cam = self.modelCamera
animation3d.animate(self, 0.20, [
self.rotateAction([0.0, 0.0, 0.0]),
animation3d.PathAction(self.modelCamera, [self.modelCamera.getPosition(), [0.0, 0.0, 0.0]]),
animation3d.ZoomAction(self.modelCamera, self.modelCamera.zoomFactor, 1.0) ])
# Mouse actions
[docs] def mouseTranslate(self, event):
speed = self.cameraSpeed()
self.modelCamera.addXYTranslation(event.dx * speed, event.dy * speed)
[docs] def mouseRotate(self, event):
speed = self.cameraSpeed()
rotX = 0.5 * event.dy * speed
rotY = 0.5 * event.dx * speed
self.modelCamera.addRotation(0, rotX)
self.modelCamera.addRotation(1, rotY)
[docs] def mouseZoom(self, event):
speed = self.cameraSpeed()
if self.getSetting('invertMouseWheel'):
speed *= -1
self.modelCamera.addZoom( -0.05 * event.dy * speed )
[docs] def mouseFocus(self, ev):
pass
[docs] def promptAndExit(self):
if self.currentFile.modified:
self.prompt('Exit', 'You have unsaved changes. Are you sure you want to exit the application?', 'Yes', 'No', self.stop)
else:
self.stop()
[docs] def toggleProfiling(self):
import profiler
if self.actions.profiling.isChecked():
profiler.start()
log.notice('profiling started')
else:
profiler.stop()
log.notice('profiling stopped')
mh.changeTask('Utilities', 'Profile')
[docs] def createActions(self):
"""
Creates the actions toolbar with icon buttons.
"""
self.actions = gui.Actions()
def action(*args, **kwargs):
action = gui.Action(*args, **kwargs)
self.mainwin.addAction(action)
if toolbar is not None:
toolbar.addAction(action)
return action
# Global actions (eg. keyboard shortcuts)
toolbar = None
self.actions.rendering = action('rendering', self.getLanguageString('Rendering'), self.goToRendering)
self.actions.modelling = action('modelling', self.getLanguageString('Modelling'), self.goToModelling)
self.actions.exit = action('exit' , self.getLanguageString('Exit'), self.promptAndExit)
self.actions.rotateU = action('rotateU', self.getLanguageString('Rotate Up'), self.rotateUp)
self.actions.rotateD = action('rotateD', self.getLanguageString('Rotate Down'), self.rotateDown)
self.actions.rotateR = action('rotateR', self.getLanguageString('Rotate Right'), self.rotateRight)
self.actions.rotateL = action('rotateL', self.getLanguageString('Rotate Left'), self.rotateLeft)
self.actions.panU = action('panU', self.getLanguageString('Pan Up'), self.panUp)
self.actions.panD = action('panD', self.getLanguageString('Pan Down'), self.panDown)
self.actions.panR = action('panR', self.getLanguageString('Pan Right'), self.panRight)
self.actions.panL = action('panL', self.getLanguageString('Pan Left'), self.panLeft)
self.actions.zoomIn = action('zoomIn', self.getLanguageString('Zoom In'), self.zoomIn)
self.actions.zoomOut = action('zoomOut', self.getLanguageString('Zoom Out'), self.zoomOut)
self.actions.profiling = action('profiling', self.getLanguageString('Profiling'), self.toggleProfiling, toggle=True)
# 1 - File toolbar
toolbar = self.file_toolbar = mh.addToolBar("File")
self.actions.load = action('load', self.getLanguageString('Load'), self.goToLoad)
self.actions.save = action('save', self.getLanguageString('Save'), self.doSave)
self.actions.export = action('export', self.getLanguageString('Export'), self.goToExport)
# 2 - Edit toolbar
toolbar = self.edit_toolbar = mh.addToolBar("Edit")
self.actions.undo = action('undo', self.getLanguageString('Undo'), self.undo)
self.actions.redo = action('redo', self.getLanguageString('Redo'), self.redo)
self.actions.reset = action('reset', self.getLanguageString('Reset'), self.resetHuman)
# 3 - View toolbar
toolbar = self.view_toolbar = mh.addToolBar("View")
self.actions.smooth = action('smooth', self.getLanguageString('Smooth'), self.toggleSubdivision, toggle=True)
self.actions.wireframe = action('wireframe', self.getLanguageString('Wireframe'), self.toggleSolid, toggle=True)
self.actions.pose = action('pose', self.getLanguageString('Pose'), self.togglePose, toggle=True)
self.actions.grid = action('grid', self.getLanguageString('Grid'), self.toggleGrid, toggle=True)
# 4 - Symmetry toolbar
toolbar = self.sym_toolbar = mh.addToolBar("Symmetry")
self.actions.symmetryR = action('symm1', self.getLanguageString('Symmmetry R>L'), self.symmetryLeft)
self.actions.symmetryL = action('symm2', self.getLanguageString('Symmmetry L>R'), self.symmetryRight)
self.actions.symmetry = action('symm', self.getLanguageString('Symmmetry'), self.symmetry, toggle=True)
# 5 - Camera toolbar
toolbar = self.camera_toolbar = mh.addToolBar("Camera")
self.actions.front = action('front', self.getLanguageString('Front view'), self.frontView)
self.actions.back = action('back', self.getLanguageString('Back view'), self.backView)
self.actions.right = action('right', self.getLanguageString('Right view'), self.rightView)
self.actions.left = action('left', self.getLanguageString('Left view'), self.leftView)
self.actions.top = action('top', self.getLanguageString('Top view'), self.topView)
self.actions.bottom = action('bottom', self.getLanguageString('Bottom view'), self.bottomView)
self.actions.resetCam = action('resetCam', self.getLanguageString('Reset camera'), self.resetView)
# 6 - Other toolbar
toolbar = self.other_toolbar = mh.addToolBar("Other")
self.actions.grab = action('grab', self.getLanguageString('Grab screen'), self.grabScreen)
self.actions.help = action('help', self.getLanguageString('Help'), self.goToHelp)
[docs] def createShortcuts(self):
for action, (modifier, key) in self.shortcuts.iteritems():
action = getattr(self.actions, action, None)
if action is not None:
mh.setShortcut(modifier, key, action)
[docs] def OnInit(self):
mh.Application.OnInit(self)
#[BAL 07/14/2013] work around focus bug in PyQt on OS X
if sys.platform == 'darwin':
G.app.mainwin.raise_()
self.setLanguage("english")
self.loadSettings()
# Necessary because otherwise setting back to default theme causes crash
log.message("Initializing default theme first.")
self.setTheme("default")
log.debug("Using Qt system style %s", self.getLookAndFeel())
self.createActions()
self.syncUndoRedo()
self.createShortcuts()
self.splash = gui.SplashScreen(self.getThemeResource('images', 'splash.png'), mh.getVersionDigitsStr())
self.splash.show()
if sys.platform != 'darwin':
self.mainwin.hide() # Fix for OSX crash thanks to Francois (issue #593)
self.tabs = self.mainwin.tabs
@self.tabs.mhEvent
def onTabSelected(tab):
self.switchCategory(tab.name)
[docs] def run(self):
self.start()
[docs] def addExporter(self, exporter):
self.getCategory('Files').getTaskByName('Export').addExporter(exporter)