"""Functions for visual editing. Used by the klampt.io.resource module
in ``resource.get(...)`` and ``resource.edit(...)``.
A couple editors, SelectionEditor and WorldEditor, cannot be launched from
the ``resource`` module. To use these, call::
from klampt.vis import editors
ed = editors.SelectionEditor("Some links",[],"Select the links that you want to modify",world))
indices = editors.run(ed)
"""
from . import glcommon
from . import glinit
from . import gldraw
from . import visualization
import time
from ..math import vectorops,so3,se3
from ..robotsim import WidgetSet,RobotPoser,ObjectPoser,TransformPoser,PointPoser,AABBPoser,BoxPoser,SpherePoser,WorldModel,RobotModelLink,RigidObjectModel,TerrainModel,IKObjective,Appearance,Geometry3D
from ..model.subrobot import SubRobotModel
from ..model import trajectory
from ..model import collide
from OpenGL.GL import *
[docs]class VisualEditorBase(glcommon.GLWidgetPlugin):
"""A base class for editing resources. Used in conjunction with
:func:`run`.
"""
def __init__(self,name,value,description,world):
glcommon.GLWidgetPlugin.__init__(self)
self.name = name
self.value = value
self.description = description
self.world = world
[docs] def instructions(self):
return None
[docs] def loadable(self):
"""Whether Load... should be shown"""
return True
[docs] def savable(self):
"""Whether Save... should be shown"""
return True
[docs] def display(self):
if self.world: self.world.drawGL()
self.klamptwidgetmaster.drawGL(self.viewport())
return True
[docs] def updateGuiFromValue(self):
"""Called when the value is externally changed (from Load...)"""
return
[docs] def updateValueFromGui(self):
"""Called when the value is requested (from Save... and OK)"""
return
[docs] def addDialogItems(self,parent,ui='qt'):
return
[docs] def display_screen(self):
pass
"""
#for GLUT?
glDisable(GL_LIGHTING)
glColor3f(0,0,0)
h = 30
if self.instructions() is not None:
self.window.draw_text((20,h),"Instructions: "+self.instructions())
h += 20
if self.description is not None:
self.window.draw_text((20,h),"Description: "+self.description)
h += 20
glRasterPos(20,h)
self.window.draw_text((20,h),"Press 'x' to exit without saving, 'q' to save+exit")
"""
return True
[docs]class ConfigEditor(VisualEditorBase):
"""Edits a Config object.
Either a world or a :class:`~klampt.RobotModel` or
:class:`~klampt.SubRobotModel` must be provided.
"""
def __init__(self,name,value,description,world,robot=None):
VisualEditorBase.__init__(self,name,value,description,world)
if robot is None:
robot = world.robot(0)
robot.setConfig(value)
self.robot = robot
if isinstance(robot,SubRobotModel):
self.robotposer = RobotPoser(robot._robot)
self.robotposer.setActiveDofs(robot._links)
else:
self.robotposer = RobotPoser(robot)
self.addWidget(self.robotposer)
[docs] def instructions(self):
return 'Right-click and drag on the robot links to pose the robot'
[docs] def mousefunc(self,button,state,x,y):
if self.robotposer.hasFocus():
self.value = self.robotposer.get()
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def display(self):
#Override display handler since the widget draws the robot
#the next few lines draw everything but the robot
if self.world:
for i in range(self.world.numTerrains()):
self.world.terrain(i).drawGL()
for i in range(self.world.numRigidObjects()):
self.world.rigidObject(i).drawGL()
for i in range(self.world.numRobots()):
if i != self.robot.index:
self.world.robot(i).drawGL()
#this line will draw the robot
self.klamptwidgetmaster.drawGL(self.viewport())
return False
[docs] def updateGuiFromValue(self):
self.robotPoser.set(self.value)
self.refresh()
[docs]class ConfigsEditor(VisualEditorBase):
"""Edits a Configs object. The GUI lets the user select which index
to edit, and then can click and drag on the robot widget to pose the
selected configuration.
Either a world or a :class:`~klampt.RobotModel` or
:class:`~klampt.SubRobotModel` must be provided.
"""
def __init__(self,name,value,description,world,robot=None):
VisualEditorBase.__init__(self,name,value,description,world)
if robot is None:
robot = world.robot(0)
if len(value) > 0:
robot.setConfig(value[0])
self.robot = robot
self.editingIndex = len(value)-1
if isinstance(robot,SubRobotModel):
self.robotposer = RobotPoser(robot._robot)
self.robotposer.setActiveDofs(robot._links)
else:
self.robotposer = RobotPoser(robot)
self.addWidget(self.robotposer)
[docs] def instructions(self):
return 'Right-click and drag on the robot links to pose the robot.\nKeyboard i: insert, d: delete, < to select previous, > to select next'
[docs] def addDialogItems(self,parent,ui='qt'):
self.indexSpinBox = QSpinBox()
self.indexSpinBox.setRange(0,len(self.value)-1)
layout = QHBoxLayout(parent)
label = QLabel("Index")
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
layout.addWidget(self.indexSpinBox)
self.insertButton = QPushButton("Insert")
self.deleteButton = QPushButton("Delete")
layout.addWidget(self.insertButton)
layout.addWidget(self.deleteButton)
self.insertButton.clicked.connect(self.insert)
self.deleteButton.clicked.connect(self.delete)
self.indexSpinBox.valueChanged.connect(self.indexChanged)
[docs] def insert(self):
if self.editingIndex < 0:
self.value.append(self.robotposer.get())
self.editingIndex = len(self.value)-1
else:
self.value.insert(self.editingIndex+1,self.robotposer.get())
self.editingIndex += 1
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setRange(0,len(self.value)-1)
self.indexSpinBox.setValue(self.editingIndex)
self.refresh()
[docs] def delete(self):
if self.editingIndex >= 0:
del self.value[self.editingIndex]
if self.editingIndex >= len(self.value):
self.editingIndex = len(self.value)-1
if self.editingIndex >= 0:
if isinstance(self.robot,SubRobotModel):
self.robot.setConfig(self.value[self.editingIndex])
self.robotposer.set(self.robot._robot.getConfig())
else:
self.robotposer.set(self.value[self.editingIndex])
#print("Now has",len(self.value),"configs, editing index",self.editingIndex)
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setRange(0,len(self.value)-1)
self.indexSpinBox.setValue(self.editingIndex)
self.refresh()
[docs] def indexChanged(self,index):
self.editingIndex = index
if index >= 0 and index < len(self.value):
if isinstance(self.robot,SubRobotModel):
self.robot.setConfig(self.value[self.editingIndex])
self.robotposer.set(self.robot._robot.getConfig())
else:
self.robotposer.set(self.value[self.editingIndex])
self.refresh()
[docs] def mousefunc(self,button,state,x,y):
if self.editingIndex >= 0 and self.robotposer.hasFocus():
#mouse release
if isinstance(self.robot,SubRobotModel):
self.robot._robot.setConfig(self.robotposer.get())
self.value[self.editingIndex] = self.robot.getConfig()
else:
self.value[self.editingIndex] = self.robotposer.get()
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def keyboardfunc(self,c,x,y):
if c=='i':
self.insert()
return True
elif c=='d':
self.delete()
return True
elif c==',' or c=='<':
self.editingIndex -= 1
if self.editingIndex < 0:
self.editingIndex = min(len(self.durations)-1,0)
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
return True
elif c=='.' or c=='>':
self.editingIndex += 1
self.editingIndex = min(len(self.durations)-1,self.editingIndex)
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
return True
[docs] def display(self):
#Override display handler since the widget draws the robot
#the next few lines draw everything but the robot
if self.world != None:
for i in range(self.world.numTerrains()):
self.world.terrain(i).drawGL()
for i in range(self.world.numRigidObjects()):
self.world.rigidObject(i).drawGL()
for i in range(self.world.numRobots()):
if i != self.robot.index:
self.world.robot(i).drawGL()
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
#draw most opaque first
order = []
if self.editingIndex < 0:
order = list(range(len(self.value)))
else:
order = [self.editingIndex]
n = max(self.editingIndex,len(self.value)-self.editingIndex)
for i in range(1,n+1):
if self.editingIndex + i < len(self.value): order.append(self.editingIndex +i)
if self.editingIndex - i >= 0: order.append(self.editingIndex -i)
oldAppearances = [self.robot.link(j).appearance().clone() for j in range(self.robot.numLinks())]
for i in order:
#draw transparent
opacity = pow(0.5,abs(i-self.editingIndex))
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().setColor(0.5,0.5,0.5,opacity)
if opacity == 1:
self.robot.link(j).appearance().setSilhouette(0.004)
else:
self.robot.link(j).appearance().setSilhouette(0)
if i == self.editingIndex:
#this line will draw the robot at the current editing config
self.klamptwidgetmaster.drawGL(self.viewport())
else:
self.robot.setConfig(self.value[i])
self.robot.drawGL()
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().set(oldAppearances[j])
glDisable(GL_BLEND)
[docs] def updateGuiFromValue(self):
if self.editingIndex >= len(self.value):
self.editingIndex = len(self.value)-1
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
[docs]class TrajectoryEditor(VisualEditorBase):
"""Edits a :class:`Trajectory`, :class:`RobotTrajectory`,
or :class:`SE3Trajectory` object.
If a Trajectory is given, then it must either be attached to a robot or
a 1D, 2D, or 3D trajectory.
"""
def __init__(self,name,value,description,world,robot=None):
VisualEditorBase.__init__(self,name,value,description,world)
if robot is None:
if isinstance(value,trajectory.RobotTrajectory):
robot = value.robot
elif isinstance(value,trajectory.SE3Trajectory):
pass
elif world.numRobots() > 0 and len(value.milestones) > 0 and len(value.milestones[0]) == world.robot(0).numLinks():
robot = world.robot(0)
self.robot = robot
self.attachedObjects = []
self.attachedRelativePoses = []
self.attachedAppearances = []
self.editingIndex = len(value.milestones)-1
self.durations = []
if len(value.times) > 0:
self.durations.append(value.times[0])
for i in range(len(value.times)-1):
self.durations.append(value.times[i+1]-value.times[i])
self.animTrajectory = None
self.animTrajectoryTime = 0.0
self.animating = False
self.animSelectorValue = 0
self.lastAnimTrajectoryTime = None
if robot is not None:
if len(value.milestones) > 0:
qold = robot.getConfig()
robot.setConfig(value.milestones[self.editingIndex])
if isinstance(robot,SubRobotModel):
self.milestoneposer = RobotPoser(robot._robot)
self.milestoneposer.setActiveDofs(robot._links)
else:
self.milestoneposer = RobotPoser(robot)
if len(value.milestones) > 0:
robot.setConfig(qold)
elif isinstance(value,trajectory.SE3Trajectory):
self.milestoneposer = TransformPoser()
if len(value.milestones) > 0:
self.milestone_to_poser(value.milestones[self.editingIndex])
elif len(value.milestones) > 0 and len(value.milestones[0])<=3:
self.ndims = len(value.milestones[0])
self.milestoneposer = PointPoser()
if self.ndims == 2:
self.milestoneposer.enableAxes(True,True,False)
elif self.ndims == 1:
self.milestoneposer.enableAxes(True,False,False)
else:
assert len(value.milestones[0])>0,"Need to have dimension >= 1"
self.milestone_to_poser(value.milestones[self.editingIndex])
else:
raise NotImplementedError("Can't edit trajectories except for robot trajectories, R^2, R^3, or SE(3) trajectories yet")
self.addWidget(self.milestoneposer)
self.updateAnimTrajectory()
[docs] def attach(self,object,relativePose=None):
"""For an SE3 trajectory, shows the given object relative to the edited transform trajectory"""
assert isinstance(object,(Geometry3D,RigidObjectModel,RobotModelLink,TerrainModel))
assert self.robot is None,"Can't attach items to an editor for a RobotTrajectory"
self.attachedObjects.append(object)
if relativePose is None:
self.attachedRelativePoses.append(se3.identity())
else:
self.attachedRelativePoses.append(relativePose)
if hasattr(object,'appearance'):
self.attachedAppearances.append(object.appearance())
else:
self.attachedAppearances.append(Appearance())
[docs] def milestone_to_poser(self,m):
if self.robot is not None:
if isinstance(self.robot,SubRobotModel):
self.robot.setConfig(m)
self.milestoneposer.set(self.robot._robot.getConfig())
else:
self.milestoneposer.set(m)
elif isinstance(self.value,trajectory.SE3Trajectory):
self.milestoneposer.set(*self.value.to_se3(m))
else:
assert len(m) == self.ndims
self.milestoneposer.set(m + [0]*(3-self.ndims))
[docs] def poser_to_milestone(self):
q = self.milestoneposer.get()
if self.robot is not None:
if isinstance(self.robot,SubRobotModel):
self.robot._robot.setConfig(q)
return self.robot.getConfig()
else:
return q
elif isinstance(self.value,trajectory.SE3Trajectory):
return q[0] + q[1]
else:
return q[:self.ndims]
[docs] def instructions(self):
if self.robot is not None:
return 'Right-click and drag on the robot links to pose keyframes.\nKeyboard i: insert, d: delete, < to select previous, > to select next'
else:
return 'Right-click and drag on the poser to set keyframes.\nKeyboard i: insert, d: delete, < to select previous, > to select next'
[docs] def addDialogItems(self,parent,ui='qt'):
vlayout = QVBoxLayout(parent)
#adding and editing keyframes
self.indexSpinBox = QSpinBox()
self.indexSpinBox.setRange(0,len(self.durations)-1)
self.indexSpinBox.setValue(self.editingIndex)
self.durationSpinBox = QDoubleSpinBox()
self.durationSpinBox.setRange(0,10.0)
self.durationSpinBox.setSingleStep(0.01)
self.durationSpinBox.setDecimals(4)
self.insertButton = QPushButton("Insert")
self.deleteButton = QPushButton("Delete")
self.indexSpinBox.valueChanged.connect(self.indexChanged)
self.durationSpinBox.valueChanged.connect(self.durationChanged)
self.insertButton.clicked.connect(self.insert)
self.deleteButton.clicked.connect(self.delete)
layout = QHBoxLayout()
vlayout.addLayout(layout)
label = QLabel("Index")
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
layout.addWidget(self.indexSpinBox)
label = QLabel("Duration")
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
layout.addWidget(self.durationSpinBox)
layout.addWidget(self.insertButton)
layout.addWidget(self.deleteButton)
#playback
self.timeDriver = QSlider()
self.timeDriver.setOrientation(Qt.Horizontal)
self.timeDriver.setRange(0,1000)
self.timeDriver.valueChanged.connect(self.timeDriverChanged)
self.playButton = QPushButton("Play")
self.playButton.setCheckable(True)
self.playButton.toggled.connect(self.togglePlay)
layout = QHBoxLayout()
vlayout.addLayout(layout)
self.animSelector = QComboBox()
self.animSelector.addItem("Linear")
self.animSelector.addItem("Spline")
if self.robot is not None and not isinstance(self.value,trajectory.RobotTrajectory):
self.animSelector.addItem("Linear (RobotTrajectory)")
#self.animSelector.addItem("Retimed")
#self.animSelector.addItem("Retimed-spline")
self.animSelector.currentIndexChanged.connect(self.animSelectorChanged)
label = QLabel("Time")
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
layout.addWidget(self.timeDriver)
layout.addWidget(self.playButton)
label = QLabel("Interp.")
label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(label)
layout.addWidget(self.animSelector)
self.indexChanged(self.editingIndex)
[docs] def insert(self):
if self.editingIndex < 0:
self.value.times.append(0.0)
self.value.milestones.append(self.poser_to_milestone())
self.editingIndex = len(self.durations)-1
else:
newdur = 1.0
if self.editingIndex+1 == len(self.durations):
#extrapolate from previous
if self.editingIndex > 0:
newdur = self.value.times[self.editingIndex] - self.value.times[self.editingIndex-1]
elif self.editingIndex == 0:
#shift everything else
if len(self.durations) > 1:
newdur = self.value.times[1]-self.value.times[0]
else:
#subdivide time between milesones
newdur = self.value.times[self.editingIndex]-self.value.times[self.editingIndex-1]
self.durations.insert(self.editingIndex+1,newdur)
self.value.milestones.insert(self.editingIndex+1,self.poser_to_milestone())
self.onDurationsChanged()
self.editingIndex += 1
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setRange(0,len(self.durations)-1)
self.indexSpinBox.setValue(self.editingIndex)
self.refresh()
[docs] def delete(self):
if self.editingIndex >= 0:
del self.durations[self.editingIndex]
del self.value.milestones[self.editingIndex]
if self.editingIndex >= len(self.durations):
self.editingIndex = len(self.durations)-1
if self.editingIndex >= 0:
self.milestone_to_poser(self.value.milestones[self.editingIndex])
self.onDurationsChanged()
#print("Now has",len(self.durations),"configs, editing index",self.editingIndex)
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setRange(0,len(self.durations)-1)
self.indexSpinBox.setValue(self.editingIndex)
if self.editingIndex >= 0:
self.durationSpinBox.setValue(self.durations[self.editingIndex])
self.refresh()
[docs] def indexChanged(self,index):
self.editingIndex = index
if index >= 0 and index < len(self.durations):
self.durationSpinBox.setValue(self.durations[self.editingIndex])
self.milestone_to_poser(self.value.milestones[self.editingIndex])
if not self.animating:
self.animTrajectoryTime = self.value.times[index]
self.timeDriver.setValue(int(1000*(self.animTrajectoryTime - self.value.times[0])/self.value.duration()))
self.refresh()
[docs] def durationChanged(self,value):
if self.editingIndex >= 0 and self.editingIndex < len(self.durations):
self.durations[self.editingIndex] = max(value,0.0)
self.onDurationsChanged()
self.refresh()
[docs] def timeDriverChanged(self,value):
u = value * 0.001
self.animTrajectoryTime = self.animTrajectory.times[0] + u*self.animTrajectory.duration()
self.refresh()
[docs] def animSelectorChanged(self,value):
self.animSelectorValue = value
self.updateAnimTrajectory()
self.refresh()
[docs] def togglePlay(self,value):
self.animating = value
self.refresh()
if value:
self.idlesleep(0)
else:
#self.idlesleep(float('inf'))
self.idlesleep(0.1)
[docs] def onDurationsChanged(self):
"""Update the trajectory times"""
if len(self.durations)==0:
self.value.times = []
else:
self.value.times = [self.durations[0]]
for i in range(1,len(self.durations)):
self.value.times.append(self.value.times[-1] + self.durations[i])
self.updateAnimTrajectory()
if not self.animating:
if hasattr(self,'timeDriver'):
self.timeDriver.setValue(int(1000*(self.animTrajectoryTime - self.value.times[0])/self.value.duration()))
[docs] def updateAnimTrajectory(self):
from ..model import trajectory
if self.animSelectorValue == 1:
if isinstance(self.value,trajectory.SE3Trajectory):
traj = trajectory.SE3HermiteTrajectory()
else:
traj = trajectory.HermiteTrajectory()
traj.makeSpline(self.value)
self.animTrajectory = traj
elif self.animSelectorValue == 2:
self.animTrajectory = trajectory.RobotTrajectory(self.robot,self.value.times,self.value.milestones)
else:
#TODO: other selections
self.animTrajectory = self.value
[docs] def mousefunc(self,button,state,x,y):
if self.editingIndex >= 0 and self.milestoneposer.hasFocus():
self.value.milestones[self.editingIndex] = self.poser_to_milestone()
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def keyboardfunc(self,c,x,y):
if c=='i':
self.insert()
return True
elif c=='d':
self.delete()
return True
elif c==',' or c=='<':
self.editingIndex -= 1
if self.editingIndex < 0:
self.editingIndex = min(len(self.durations)-1,0)
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
return True
elif c=='.' or c=='>':
self.editingIndex += 1
self.editingIndex = min(len(self.durations)-1,self.editingIndex)
if hasattr(self,'indexSpinBox'):
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
return True
[docs] def display(self):
#Override display handler since the widget draws the robot
#the next few lines draw everything but the robot
if self.world is not None:
for i in range(self.world.numTerrains()):
self.world.terrain(i).drawGL()
for i in range(self.world.numRigidObjects()):
self.world.rigidObject(i).drawGL()
for i in range(self.world.numRobots()):
if self.robot is None or i != self.robot.index:
self.world.robot(i).drawGL()
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
if self.robot is None:
glDisable(GL_LIGHTING)
if self.robot is not None:
oldAppearances = [self.robot.link(j).appearance().clone() for j in range(self.robot.numLinks())]
#draw animation, if available
if self.animTrajectoryTime is not None:
if self.robot is not None:
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().setColor(1.0,1.0,0,0.5)
q = self.animTrajectory.eval(self.animTrajectoryTime,'loop')
self.robot.setConfig(q)
self.robot.drawGL()
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().set(oldAppearances[j])
elif isinstance(self.value,trajectory.SE3Trajectory):
T = self.animTrajectory.eval(self.animTrajectoryTime,'loop')
glEnable(GL_LIGHTING)
for (o,a,Trel) in zip(self.attachedObjects,self.attachedAppearances,self.attachedRelativePoses):
if hasattr(o,'setCurrentTransform'):
o.setCurrentTransform(*se3.mul(T,Trel))
a.drawWorldGL(o)
else:
o.setTransform(*se3.mul(T,Trel))
a.drawWorldGL(o.geometry())
length = 0.1
width = 0.01
fancy = True
gldraw.xform_widget(T,length,width,fancy)
glDisable(GL_LIGHTING)
else:
x = self.animTrajectory.eval(self.animTrajectoryTime,'loop')
pt = x + [0]*(3-self.ndims)
if len(self.attachedObjects) > 0:
glEnable(GL_LIGHTING)
for (o,a,Trel) in zip(self.attachedObjects,self.attachedAppearances,self.attachedRelativePoses):
if hasattr(o,'setCurrentTransform'):
o.setCurrentTransform(Trel[0],vectorops.add(pt,Trel[1]))
a.drawWorldGL(o)
else:
o.setTransform(Trel[0],vectorops.add(pt,Trel[1]))
a.drawWorldGL(o.geometry())
glDisable(GL_LIGHTING)
glPointSize(7.0)
glColor3f(1,0.5,0)
gldraw.point(pt)
#draw most opaque first
order = []
if self.editingIndex < 0:
order = list(range(len(self.durations)))
else:
order = [self.editingIndex]
n = max(self.editingIndex,len(self.durations)-self.editingIndex)
for i in range(1,n+1):
if self.editingIndex + i < len(self.durations): order.append(self.editingIndex +i)
if self.editingIndex - i >= 0: order.append(self.editingIndex -i)
for i in order:
#draw transparent
opacity = pow(0.5,abs(i-self.editingIndex))
if opacity < 5e-3:
continue
if self.robot is not None:
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().setColor(0.5,0.5,0.5,opacity)
if opacity == 1:
self.robot.link(j).appearance().setSilhouette(0.004)
else:
self.robot.link(j).appearance().setSilhouette(0)
if i == self.editingIndex:
#this line will draw the robot at the current editing config
self.klamptwidgetmaster.drawGL(self.viewport())
if self.robot is None:
if isinstance(self.value,trajectory.SE3Trajectory):
T = self.value.to_se3(self.value.milestones[i])
for (o,a,Trel) in zip(self.attachedObjects,self.attachedAppearances,self.attachedRelativePoses):
if hasattr(o,'setCurrentTransform'):
o.setCurrentTransform(*se3.mul(T,Trel))
a.drawWorldGL(o)
else:
o.setTransform(*se3.mul(T,Trel))
if self.world is None or self.world.index != o.world:
a.drawWorldGL(o.geometry())
else:
glEnable(GL_LIGHTING)
for (o,a,Trel) in zip(self.attachedObjects,self.attachedAppearances,self.attachedRelativePoses):
if hasattr(o,'setCurrentTransform'):
o.setCurrentTransform(Trel[0],vectorops.add(pt,Trel[1]))
a.drawWorldGL(o)
else:
o.setTransform(Trel[0],vectorops.add(pt,Trel[1]))
if self.world is None or self.world.index != o.world:
a.drawWorldGL(o.geometry())
glDisable(GL_LIGHTING)
else:
if self.robot is not None:
self.robot.setConfig(self.value.milestones[i])
self.robot.drawGL()
elif isinstance(self.value,trajectory.SE3Trajectory):
T = self.value.to_se3(self.value.milestones[i])
length = 0.1*opacity
width = 0.01*opacity
gldraw.xform_widget(T,length,width,False)
else:
pt = self.value.milestones[i] + [0]*(3-self.ndims)
glPointSize(5.0)
glColor4f(1,0.5,0,opacity)
gldraw.point(pt)
if self.robot is not None:
for j in range(self.robot.numLinks()):
self.robot.link(j).appearance().set(oldAppearances[j])
elif len(self.value.milestones) > 1:
glColor3f(1,1,0)
glBegin(GL_LINE_STRIP)
for i in range(0,len(self.value.milestones)):
a = self.value.milestones[i]
if isinstance(self.value,trajectory.SE3Trajectory):
glVertex3fv(a[9:12])
else:
glVertex3fv(a + [0]*(3-self.ndims))
glEnd()
glDisable(GL_BLEND)
glEnable(GL_LIGHTING)
[docs] def idle(self):
import time
t = time.time()
if self.animating:
self.animTrajectoryTime += t - self.lastAnimTrajectoryTime
if self.animTrajectoryTime > self.value.times[-1]:
self.animTrajectoryTime -= self.value.duration()
self.timeDriver.setValue(int(1000*(self.animTrajectoryTime - self.value.times[0])/self.value.duration()))
self.refresh()
self.lastAnimTrajectoryTime = t
return False
[docs] def updateGuiFromValue(self):
if self.editingIndex >= len(self.value.times):
self.editingIndex = len(self.value.times)-1
self.durations = []
if len(self.value.times) > 0:
self.durations.append(self.value.times[0])
for i in range(len(self.value.times)-1):
self.durations.append(self.value.times[i+1]-self.value.times[i])
self.indexSpinBox.setValue(self.editingIndex)
self.indexChanged(self.editingIndex)
self.onDurationsChanged()
[docs]class SelectionEditor(VisualEditorBase):
"""Edits a list of indices selecting some links of a robot.
"""
def __init__(self,name,value,description,world,robot=None):
VisualEditorBase.__init__(self,name,value,description,world)
self.robot = robot
self.lastClicked = -1
self.oldAppearances = {}
self.newAppearances = {}
[docs] def instructions(self):
return 'Right-click to toggle selection of robot links / objects in the world.\nKeyboard: < to deselect previous, > to select next'
[docs] def addDialogItems(self,parent,ui='qt'):
layout = QHBoxLayout(parent)
self.clearButton = QPushButton("Clear")
self.selectAllButton = QPushButton("Select all")
self.selectionList = QListWidget()
self.selectionList.setSelectionMode(QAbstractItemView.MultiSelection)
if self.robot != None:
for i in range(self.robot.numLinks()):
self.selectionList.addItem(self.robot.link(i).getName())
elif self.world != None:
for i in range(self.world.numIDs()):
self.selectionList.addItem(self.world.getName(i))
self.updateGuiFromValue()
layout.addWidget(self.clearButton)
layout.addWidget(self.selectAllButton)
layout.addWidget(self.selectionList)
self.clearButton.clicked.connect(self.clear)
self.selectAllButton.clicked.connect(self.selectAll)
self.selectionList.itemSelectionChanged.connect(self.selectionListChanged)
self.selectionListChangeFlag = False
[docs] def clear(self):
self.value = []
self.selectionList.clearSelection()
self.refresh()
[docs] def selectAll(self):
if self.robot is None:
#select all ids in the world
self.value = list(range(self.world.numIDs()))
else:
self.value = list(range(self.robot.numLinks()))
self.selectionListChangeFlag = True
for i in self.value:
self.selectionList.setCurrentItem(self.selectionList.item(i),QItemSelectionModel.Select)
self.selectionListChangeFlag = False
self.refresh()
[docs] def click_world(self,x,y):
"""Helper: returns a list of world objects sorted in order of
increasing distance."""
#get the viewport ray
(s,d) = self.click_ray(x,y)
geoms = [self.world.geometry(i) for i in range(self.world.numIDs())]
res = collide.ray_cast(geoms,s,d)
if not res:
return
id,geom = res
self.toggle_selection(id)
self.lastClicked = id
self.refresh()
[docs] def click_robot(self,x,y):
"""Helper: returns a list of robot objects sorted in order of
increasing distance."""
#get the viewport ray
(s,d) = self.click_ray(x,y)
geoms = [self.robot.link(i).geometry() for i in range(self.robot.numLinks())]
self.robot.setConfig(self.robot.getConfig())
res = collide.ray_cast(geoms,s,d)
if not res:
return
id,geom = res
self.toggle_selection(id)
self.lastClicked = id
self.refresh()
[docs] def selectionListChanged(self):
#if the GUI has changed the selection then don't update the selection list
if self.selectionListChangeFlag: return
self.value = []
for item in self.selectionList.selectedItems():
row = self.selectionList.row(item)
self.value.append(row)
self.refresh()
[docs] def add_selection(self,id):
self.selectionListChangeFlag = True
if id not in self.value:
self.selectionList.setCurrentItem(self.selectionList.item(id),QItemSelectionModel.Select)
self.value.append(id)
self.selectionListChangeFlag = False
[docs] def remove_selection(self,id):
self.selectionListChangeFlag = False
if id in self.value:
self.value.remove(id)
self.selectionList.setCurrentItem(self.selectionList.item(id),QItemSelectionModel.Deselect)
self.selectionListChangeFlag = True
[docs] def toggle_selection(self,id):
self.selectionListChangeFlag = True
if id in self.value:
self.value.remove(id)
self.selectionList.setCurrentItem(self.selectionList.item(id),QItemSelectionModel.Deselect)
else:
self.selectionList.setCurrentItem(self.selectionList.item(id),QItemSelectionModel.Select)
self.value.append(id)
self.selectionListChangeFlag = False
[docs] def mousefunc(self,button,state,x,y):
if button==2 and state==0:
if self.robot is None:
self.click_world(x,y)
else:
self.click_robot(x,y)
return True
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def keyboardfunc(self,c,x,y):
if c==',' or c=='<':
if self.lastClicked >= 0:
self.remove_selection(self.lastClicked)
if self.lastClicked >= 0:
self.lastClicked -= 1
self.refresh()
return True
elif c=='.' or c=='>':
Nmax = (self.robot.numLinks() if self.robot else self.world.numIDs())
if self.lastClicked < Nmax:
self.lastClicked += 1
if self.lastClicked < Nmax:
self.add_selection(self.lastClicked)
self.refresh()
return True
[docs] def display(self):
#Override display handler to highlight selected links
#save old appearance and set new appearance
apps = {}
if self.robot != None:
for i in range(self.robot.numLinks()):
apps[i] = self.robot.link(i).appearance()
elif self.world != None:
for i in range(self.world.numIDs()):
apps[i] = self.world.appearance(i)
changed = self.value[:]
if self.lastClicked >= 0: changed.append(self.lastClicked)
for i in changed:
if i not in self.oldAppearances:
self.oldAppearances[i] = apps[i].clone()
self.newAppearances[i] = apps[i].clone()
if i == self.lastClicked:
if i in self.value:
self.newAppearances[i].setColor(1,0.5,0)
else:
self.newAppearances[i].setColor(1,0,0)
else:
self.newAppearances[i].setColor(1,1,0)
apps[i].set(self.newAppearances[i])
#draw
self.world.drawGL()
#restore old appearance
for i in changed:
apps[i].set(self.oldAppearances[i])
glDisable(GL_BLEND)
[docs] def updateGuiFromValue(self):
self.selectionList.clearSelection()
for i in self.value:
self.selectionList.setCurrentItem(self.selectionList.item(i),QItemSelectionModel.Select)
[docs]class PointEditor(VisualEditorBase):
"""Edits a 3-D point.
If ``frame`` is given, then it is :mod:`klampt.math.se3` element, and the
input and output are measured with respect to that frame. However, the
editing is done in world coordinates.
"""
def __init__(self,name,value,description,world,frame=None):
VisualEditorBase.__init__(self,name,value,description,world)
self.frame = se3.identity() if frame is None else frame
self.pointposer = PointPoser()
self.pointposer.set(se3.apply(self.frame,value))
self.pointposer.setAxes(self.frame[0])
self.addWidget(self.pointposer)
[docs] def instructions(self):
return 'Right-click and drag on the widget to pose the point'
[docs] def mousefunc(self,button,state,x,y):
if self.pointposer.hasFocus():
self.value = se3.apply(se3.inv(self.frame),self.pointposer.get())
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def updateGuiFromValue(self):
self.pointposer.set(se3.apply(self.frame,self.value))
self.refresh()
[docs]class AABBEditor(VisualEditorBase):
"""Edits a 3D axis-aligned bounding box ``(bmin,bmax)``.
If ``frame`` is given, then it is :mod:`klampt.math.se3` element, and the
input and output are measured with respect to that frame. However, the
editing is done in world coordinates.
"""
def __init__(self,name,value,description,world,frame=None):
VisualEditorBase.__init__(self,name,value,description,world)
self.aabbposer = AABBPoser()
self.aabbposer.set(value[0],value[1])
if frame is not None:
self.aabbposer.setFrame(frame[0],frame[1])
self.addWidget(self.aabbposer)
[docs] def instructions(self):
return 'Right-click and drag on the widget to set the box'
[docs] def mousefunc(self,button,state,x,y):
if self.aabbposer.hasFocus():
self.value = self.aabbposer.get()
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def updateGuiFromValue(self):
self.aabbposer.set(self.value[0],self.value[1])
self.refresh()
[docs]class SphereEditor(VisualEditorBase):
"""Edits a sphere represented by a tuple ``(center,radius)``.
If ``frame`` is given, then it is :mod:`klampt.math.se3` element, and the
input and output are measured with respect to that frame. However, the
editing is done in world coordinates.
"""
def __init__(self,name,value,description,world,frame=None):
VisualEditorBase.__init__(self,name,value,description,world)
self.frame = se3.identity() if frame is None else frame
self.sphereposer = SpherePoser()
self.sphereposer.set(se3.apply(frame,value[0])+[value[1]])
self.addWidget(self.sphereposer)
[docs] def instructions(self):
return 'Right-click and drag on the widget to set the sphere'
[docs] def mousefunc(self,button,state,x,y):
if self.sphereposer.hasFocus():
cr = self.sphereposer.get()
self.value = (se3.apply(se3.inv(self.frame),cr[:3]),cr[3])
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def updateGuiFromValue(self):
self.sphereposer.set(se3.apply(self.frame,self.value[0])+[self.value[1]])
self.refresh()
[docs]class GeometricPrimitiveEditor(VisualEditorBase):
"""Edits a :class:`GeometricPrimitive`.
Can only handle Point, Sphere, AABB, and Box types for now.
If ``frame`` is given, then it is :mod:`klampt.math.se3` element, and the
input and output are measured with respect to that frame. However, the
editing is done in world coordinates.
"""
def __init__(self,name,value,description,world,frame=None):
VisualEditorBase.__init__(self,name,value,description,world)
self.frame = se3.identity() if frame is None else frame
if value.type == 'Point':
self.poser = PointPoser()
elif value.type == 'Sphere':
self.poser = SpherePoser()
elif value.type == 'AABB':
self.poser = AABBPoser()
elif value.type == 'Box':
self.poser = BoxPoser()
else:
raise NotImplementedError("Can't edit GeometricPrimitive of type "+value.type+" yet")
self.updateGuiFromValue()
self.addWidget(self.poser)
[docs] def instructions(self):
return 'Right-click and drag on the widget to edit the primitive'
[docs] def mousefunc(self,button,state,x,y):
if self.poser.hasFocus():
if self.value.type == 'Point':
p = self.poser.get()
self.value.setPoint(se3.apply(se3.inv(self.frame),p))
elif self.value.type == 'Sphere':
cr = self.poser.get()
self.value.setSphere(se3.apply(se3.inv(self.frame),cr[:3]),cr[3])
elif self.value.type == 'AABB':
bmin,bmax = self.poser.get()
self.value.setAABB(bmin,bmax)
elif self.value.type == 'Box':
Tw = self.poser.getTransform()
Rl,tl = se3.mul(se3.inv(self.frame),Tw)
dims = self.poser.getDims()
self.value.setBox(tl,Rl,dims)
else:
raise NotImplementedError("Can't edit GeometricPrimitive of type "+value.type+" yet")
return VisualEditorBase.mousefunc(self,button,state,x,y)
[docs] def updateGuiFromValue(self):
if self.value.type == 'Point':
c = self.value.properties[0:3]
self.poser.set(se3.apply(self.frame,c))
self.poser.setAxes(self.frame[0])
elif self.value.type == 'Sphere':
c = self.value.properties[0:3]
r = self.value.properties[3]
self.poser.set(se3.apply(self.frame,c) + [r])
elif self.value.type == 'AABB':
bmin = self.value.properties[0:3]
bmax = self.value.properties[3:6]
self.poser.set(bmin,bmax)
self.poser.setFrame(self.frame[0],self.frame[1])
elif self.value.type == 'Box':
t = self.value.properties[0:3]
R = self.value.properties[3:12]
dims = self.value.properties[12:15]
Rw,tw = se3.mul(self.frame,(R,t))
self.poser.set(Rw,tw,dims)
else:
raise NotImplementedError("Can't edit GeometricPrimitive of type "+value.type+" yet")
self.refresh()
[docs]class WorldEditor(VisualEditorBase):
"""Edits poses of robots, rigid objects, and terrains in a world.
.. note::
Edits to robot and rigid object poses are done immediately, but
since terrains do not have poses, their actual geometry needs to
be updated. To apply the edited poses to the terrain geometries,
call :meth:`updateValueFromGui` after the editor has been run.
"""
def __init__(self,name,value,description):
VisualEditorBase.__init__(self,name,value,description,value)
world = value
self.world = value
self.robotPosers = [RobotPoser(world.robot(i)) for i in range(world.numRobots())]
self.objectPosers = [ObjectPoser(world.rigidObject(i)) for i in range(world.numRigidObjects())]
self.terrainPosers = [TransformPoser() for i in range(world.numTerrains())]
self.terrainGeometryCenters = []
for i in range(world.numTerrains()):
bmin,bmax=world.terrain(i).geometry().getBB()
gc = vectorops.interpolate(bmin,bmax,0.5)
T0 = world.terrain(i).geometry().getCurrentTransform()
self.terrainGeometryCenters.append(se3.apply(se3.inv(T0),gc))
#Tw.R = Tg.R
#Tw.t = gc = Tg.R*gcloc + Tg.t
self.terrainPosers[i].set(T0[0],gc)
for r in self.robotPosers:
self.addWidget(r)
for r in self.objectPosers:
self.addWidget(r)
for r in self.terrainPosers:
self.addWidget(r)
[docs] def loadable(self):
return False
[docs] def savable(self):
return True
[docs] def display(self):
#Override display handler since the widget draws the rigid objects and transforms
for i in range(self.world.numTerrains()):
#self.world.terrain(i).geometry().getCurrentTransform()
self.world.terrain(i).appearance().drawWorldGL(self.world.terrain(i).geometry())
self.klamptwidgetmaster.drawGL(self.viewport())
return False
[docs] def updateValueFromGui(self):
"""Applies the transforms to all the terrain geometries."""
for i in range(self.world.numTerrains()):
T0 = self.world.terrain(i).geometry().getCurrentTransform()
self.world.terrain(i).geometry().setCurrentTransform(*se3.identity())
self.world.terrain(i).geometry().transform(*T0)
[docs] def instructions(self):
return 'Right-click and drag on the widgets to pose the world objects'
[docs] def motionfunc(self,button,state,x,y):
for i,r in enumerate(self.terrainPosers):
if r.hasFocus():
Tw = r.get()
gcloc = self.terrainGeometryCenters[i]
#Tw.t = Tw.R*gc + Tg.t
self.world.terrain(i).geometry().setCurrentTransform(Tw[0],vectorops.sub(Tw[1],so3.apply(Tw[0],gcloc)))
return VisualEditorBase.motionfunc(self,button,state,x,y)
[docs]class SensorEditor(RigidTransformEditor):
"""Edits a SimRobotSensor.
Assumes that the sensor is attached to the first robot in the world, if
``world`` is provided.
"""
def __init__(self,name,value,description,world):
from klampt import SimRobotSensor
from ..model import sensing
assert isinstance(value,SimRobotSensor),"Need to provide SensorEditor with a SimRobotSensor"
T = sensing.get_sensor_xform(value)
link_frame = None
if world is not None:
robot = world.robot(0)
link = int(value.getSetting('link'))
link_frame = robot.link(link).getTransform()
RigidTransformEditor.__init__(self,name,T,description,world,frame=link_frame)
self.value = value
self.measurements = []
[docs] def initialize(self):
res = RigidTransformEditor.initialize(self)
self.refreshVisualization()
return res
[docs] def loadable(self):
return False
[docs] def savable(self):
return False
[docs] def addDialogItems(self,parent,ui='qt'):
layout = QHBoxLayout(parent)
self.selectionList = QListWidget()
settings = self.value.settings()
self.settingsOrder = sorted([k for k in settings if k not in ['Tsensor','rate']])
for k in self.settingsOrder:
self.selectionList.addItem(k)
self.selectionList.currentRowChanged.connect(self.onSettingSelected)
self.editBox = QLineEdit("value")
self.editBox.editingFinished.connect(self.onSettingEdited)
self.onSettingSelected()
layout.addWidget(self.selectionList)
layout.addWidget(self.editBox)
[docs] def refreshVisualization(self):
self.value.kinematicSimulate(0.01)
self.measurements = self.value.getMeasurements()
self.refresh()
[docs] def display(self):
from ..model import sensing
sensor = self.value
self.value = sensing.get_sensor_xform(sensor)
RigidTransformEditor.display(self)
self.value = sensor
self.value.drawGL(self.measurements)
[docs] def onSettingSelected(self):
if self.selectionList.currentRow() < 0:
self.editBox.setText('')
return
item = self.settingsOrder[self.selectionList.currentRow()]
value = self.value.getSetting(item)
self.editBox.setText(value)
[docs] def onSettingEdited(self):
if self.selectionList.currentRow() < 0:
return
value = str(self.editBox.text())
item = self.settingsOrder[self.selectionList.currentRow()]
try:
self.value.setSetting(item,value)
self.refreshVisualization()
except Exception as e:
QMessageBox.warning(self,"Invalid sensor setting","Sensor does not accept setting: "+str(e))
self.editBox.setText(self.value.getSetting(item))
[docs] def instructions(self):
return 'Right-click and drag on the widget to pose the sensor'
[docs] def mousefunc(self,button,state,x,y):
from ..model import sensing
sensor = self.value
self.value = sensing.get_sensor_xform(sensor)
res = RigidTransformEditor.mousefunc(self,button,state,x,y)
if res:
sensing.set_sensor_xform(sensor,self.value)
self.value = sensor
self.refreshVisualization()
return True
self.value = sensor
return False
[docs] def motionfunc(self,x,y,dx,dy):
from ..model import sensing
sensor = self.value
self.value = sensing.get_sensor_xform(sensor)
res = RigidTransformEditor.motionfunc(self,x,y,dx,dy)
sensing.set_sensor_xform(sensor,self.value)
self.value = sensor
return res
[docs] def updateValueFromGui(self):
sensor = self.value
from ..model import sensing
self.value = sensing.get_sensor_xform(sensor)
RigidTransformEditor.updateValueFromGui(self)
sensing.set_sensor_xform(sensor,self.value)
self.value = sensor
[docs] def updateGuiFromValue(self):
sensor = self.value
from ..model import sensing
self.value = sensing.get_sensor_xform(sensor)
RigidTransformEditor.updateGuiFromValue(self)
self.value = sensor
#Qt stuff
global _has_qt
_has_qt = False
try:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
_has_qt = True
except ImportError:
try:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
_has_qt = True
except ImportError:
_has_qt = False
if _has_qt:
global _vis_id,_my_dialog_res,_doexit
_vis_id = None
_my_dialog_res = None
_doexit = False
class _EditDialog(QDialog):
def __init__(self,glwidget):
QDialog.__init__(self)
self.glwidget = glwidget
#glwidget.setMinimumSize(glwidget.width,glwidget.height)
glwidget.setMaximumSize(4000,4000)
glwidget.setSizePolicy(QSizePolicy(QSizePolicy.Maximum,QSizePolicy.Maximum))
self.instructions = QLabel()
self.description = QLabel()
self.description2 = QLabel("Press OK to save, Cancel to continue without saving")
self.topBox = QFrame()
self.topBoxLayout = QVBoxLayout(self.topBox)
self.topBoxLayout.addWidget(self.description)
self.topBoxLayout.addWidget(self.instructions)
self.loadButton = QPushButton("Load...")
self.saveButton = QPushButton("Save...")
self.loadsave = QFrame()
self.loadButton.clicked.connect(self.load)
self.saveButton.clicked.connect(self.save)
self.extraDialog = QFrame()
self.extraDialog.setSizePolicy(QSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum))
self.topBoxLayout.addWidget(self.extraDialog)
self.layout = QVBoxLayout()
self.layout.addWidget(self.topBox)
self.layout.addWidget(glwidget)
self.layout.addWidget(self.description2)
self.layout.setStretchFactor(glwidget,10)
#self.layout.setStretchFactor(self.topBox,0)
self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel,Qt.Horizontal, self)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
self.layout.addWidget(self.buttons)
self.splitter = QSplitter(Qt.Vertical)
top = QWidget(self)
bottom = QWidget(self)
top.setLayout(self.topBoxLayout)
bottom.setLayout(self.layout)
self.splitter.addWidget(top)
self.splitter.addWidget(bottom)
hbox = QHBoxLayout(self)
hbox.addWidget(self.splitter)
self.splitter.setSizes([self.topBoxLayout.sizeHint().height(),self.layout.sizeHint().height()])
def setEditor(self,editorObject):
self.editorObject = editorObject
self.setWindowTitle("Editing "+editorObject.name)
if editorObject.description is None:
self.description.setText("")
else:
self.description.setText(editorObject.description)
self.instructions.setText(editorObject.instructions())
loadsavelayout = QHBoxLayout(self.loadsave)
if editorObject.loadable():
loadsavelayout.addWidget(self.loadButton)
if editorObject.savable():
loadsavelayout.addWidget(self.saveButton)
if editorObject.loadable() or editorObject.savable():
self.topBoxLayout.addWidget(self.loadsave)
editorObject.addDialogItems(self.extraDialog,ui='qt')
def closeEvent(self,event):
global _doexit
reply = QMessageBox.question(self, 'Message',
"Are you sure you wish to quit the program?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
_doexit = True
event.accept()
else:
event.ignore()
def accept(self):
global _my_dialog_res
_my_dialog_res = True
print("#########################################")
print("klampt.vis: EditDialog accept")
print("#########################################")
self.editorObject.updateValueFromGui()
return QDialog.accept(self)
def reject(self):
global _my_dialog_res
_my_dialog_res = False
print("#########################################")
print("klampt.vis: EditDialog reject")
print("#########################################")
return QDialog.reject(self)
def load(self):
from ..io import resource
from ..model import types
type = types.objectToTypes(self.editorObject.value)
if isinstance(type,list):
type = type[0]
res = resource.load(type,'.')
if res is not None:
#TODO: check compatibility with world
self.editorObject.value = res[1]
self.editorObject.updateGuiFromValue()
def save(self):
from ..io import resource
self.editorObject.updateValueFromGui()
resource.save(self.editorObject.value,'auto','.')
def run(editorObject):
"""
Launches a visual editor.
Args:
editorObject (VisualEditorBase): some subclass of VisualEditorBase
Returns:
tuple: A pair (res,value) containing:
* res (bool):True / False if OK / Cancel was pressed, respectively,
* value: the return value of the editor object
"""
assert isinstance(editorObject,VisualEditorBase),"Must provide a VisualEditorBase instance to vis.editors.run()"
global _vis_id, _my_dialog_res
old_vis_window = visualization.getWindow()
if _vis_id is None:
_vis_id = visualization.createWindow("Resource Editor")
else:
visualization.setWindow(_vis_id)
visualization.setPlugin(editorObject)
def makefunc(gl_backend):
assert gl_backend is not None
res = _EditDialog(gl_backend)
res.setEditor(editorObject)
return res
visualization.customUI(makefunc)
visualization.dialog()
res,retVal = _my_dialog_res,editorObject.value
if _doexit:
visualization.kill()
print("Exiting program.")
exit(0)
assert res is not None,"vis.editors.run(): There may be something wrong with the vis module not catching the customUI, or terminating from a prior dialog?"
visualization.setPlugin(None)
visualization.customUI(None)
if old_vis_window is not None:
print("Returning from editor to old window",old_vis_window)
visualization.setWindow(old_vis_window)
else:
print("Returning from editor to window 0")
visualization.setWindow(0)
print("vis.editors.run(): Result",res,"return value",retVal)
return res,retVal
else:
[docs] def run(editorObject):
"""
Args:
editorObject (VisualEditorBase): some subclass of VisualEditorBase
Returns:
res,value (bool, value pair):
* res is True / False if OK / Cancel was pressed, respectively,
* value is the return value of the editor object
"""
raise ValueError("Unable to perform visual editing without PyQt")