diff --git a/Elements/_version.py b/Elements/_version.py index f5504a86..4041347b 100644 --- a/Elements/_version.py +++ b/Elements/_version.py @@ -5,4 +5,4 @@ # 1) we don't load dependencies by storing it in __init__.py # 2) we can import it in setup.py for the same reason # 3) we can import it into your module -__version__ = '1.3.1' \ No newline at end of file +__version__ = '1.3.2' \ No newline at end of file diff --git a/Elements/pyECSS/Component.py b/Elements/pyECSS/Component.py index b3b9d488..5bab9bbb 100644 --- a/Elements/pyECSS/Component.py +++ b/Elements/pyECSS/Component.py @@ -26,6 +26,7 @@ import uuid import Elements.pyECSS.math_utilities as util import numpy as np +from scipy.spatial.transform import Rotation as R class Component(ABC, Iterable): @@ -231,7 +232,7 @@ def __str__(self): Returns: - str: A string representation of this Component. """ - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: {self._parent._name}" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}" class ComponentDecorator(Component): @@ -344,20 +345,31 @@ def translation(self): def rotationEulerAngles(self): # First get rotation matrix from trs. Divide by scale rotationMatrix = self.trs.copy(); - rotationMatrix[:][0] /= self.scale[0]; - rotationMatrix[:][1] /= self.scale[1]; - rotationMatrix[:][2] /= self.scale[2]; - # Now, extract euler angles from rotation matrix - x = atan2(rotationMatrix[1][2], rotationMatrix[2][2]); - y = 0; - z = 0; - return [x, y, z]; + sc = self.scale; + rotationMatrix = rotationMatrix @ util.scale(1/sc[0], 1/sc[1], 1/sc[2]) + myR = rotationMatrix[:3,:3] + if myR[2,0] not in [-1,1]: + y = -np.arcsin(myR[2,0]); + x = np.arctan2(myR[2,1]/np.cos(y), myR[2,2]/np.cos(y)); + z = np.arctan2(myR[1,0]/np.cos(y), myR[0,0]/np.cos(y)); + else: + z = 0; + if myR[2,0] == -1: + y = np.pi/2; + x = z + np.arctan2(myR[0,1], myR[0,2]); + else: + y = -np.pi/2; + x = -z + np.arctan2(-myR[0,1], -myR[0,2]); + return np.array([x,y,z])*180/np.pi; @property #scale vector def scale(self): - x = self.trs[0, 0]; - y = self.trs[1, 1]; - z = self.trs[2, 2]; - return [x, y, z]; + m = self.trs.copy()[:3,:3]; + A = m.transpose() @ m + # if m = R @ S then A = m^T @ m = S^T @ R^T @ R @ S = S^T @ S = S^2 + sx = np.sqrt(A[0,0]) + sy = np.sqrt(A[1,1]) + sz = np.sqrt(A[2,2]) + return sx, sy, sz def update(self, **kwargs): """ Local 2 world transformation calculation @@ -407,7 +419,7 @@ def init(self): def __str__(self): np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)}) # print only one 3 decimals - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: {self._parent._name}, \nl2world: \n{self.l2world}, \nl2cam: \n{self.l2cam}, \ntrs: \n{self.trs}" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}, \nl2world: \n{self.l2world}, \nl2cam: \n{self.l2cam}, \ntrs: \n{self.trs}" def __iter__(self) ->CompNullIterator: """ A concrete component does not have children to iterate, thus a NULL iterator @@ -479,7 +491,7 @@ def init(self): pass def __str__(self): - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: {self._parent._name}, \n projMat: \n{self.projMat},\n root2cam: \n{self.root2cam}" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}, \nprojMat: \n{self.projMat},\nroot2cam: \n{self.root2cam}" def __iter__(self) ->CompNullIterator: """ A component does not have children to iterate, thus a NULL iterator @@ -559,7 +571,7 @@ def print(self): def __str__(self): - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: {self._parent._name}, vertex_attributes: \n{self._vertex_attributes}" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}, \nvertex_attributes: \n{self._vertex_attributes}" def __iter__(self) ->CompNullIterator: diff --git a/Elements/pyECSS/Entity.py b/Elements/pyECSS/Entity.py index 2d0e6afd..7d1a8e7b 100644 --- a/Elements/pyECSS/Entity.py +++ b/Elements/pyECSS/Entity.py @@ -174,9 +174,9 @@ def __iter__(self) -> EntityDfsIterator: def __str__(self): if (self._parent is not None): #in case this is not the root node - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: {self._parent._name}" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: {self._parent._name}" else: - return f"\n {self.getClassName()} name: {self._name}, type: {self._type}, id: {self._id}, parent: None (root node)" + return f"\n{self.getClassName()} \nname: {self._name}, \ntype: {self._type}, \nid: {self._id}, \nparent: None (root node)" diff --git a/Elements/pyECSS/tests/test_EntityComponents.py b/Elements/pyECSS/tests/test_EntityComponents.py index 563d3ed4..f81147be 100644 --- a/Elements/pyECSS/tests/test_EntityComponents.py +++ b/Elements/pyECSS/tests/test_EntityComponents.py @@ -286,6 +286,47 @@ def test_init(self): myComponent.print() print("TestBasicTransform:test_init() END") + def test_extract_TRS_atributes(self): + #default constructor of Component class + print("\TestBasicTransform:test_init() START") + + rot = util.rotate([0,0,1], 70) @ util.rotate([0,1,0], 20) @ util.rotate([1,0,0], 90 ) + trans = util.translate(3,2,4) + sc = util.scale( 2,3,4 ) + + myComponent = BasicTransform( trs = trans ) + np.testing.assert_array_almost_equal(myComponent.translation, [3,2,4]) + + myComponent = BasicTransform( trs = rot ) + np.testing.assert_array_almost_equal(myComponent.rotationEulerAngles, [90, 20, 70]) + + myComponent = BasicTransform( trs = sc ) + np.testing.assert_array_almost_equal(myComponent.scale, [2, 3, 4]) + + myComponent = BasicTransform( trs = trans @ sc ) + np.testing.assert_array_almost_equal(myComponent.translation, [3,2,4]) + np.testing.assert_array_almost_equal(myComponent.scale, [2, 3, 4]) + + myComponent = BasicTransform( trs = rot @ sc) + np.testing.assert_array_almost_equal(myComponent.rotationEulerAngles, [90, 20, 70]) + np.testing.assert_array_almost_equal(myComponent.scale, [2, 3, 4]) + + myComponent = BasicTransform( trs = trans @ rot ) + np.testing.assert_array_almost_equal(myComponent.translation, [3,2,4]) + np.testing.assert_array_almost_equal(myComponent.rotationEulerAngles, [90, 20, 70]) + + myComponent = BasicTransform( trs = trans @ rot @ sc) + np.testing.assert_array_almost_equal(myComponent.translation, [3,2,4]) + np.testing.assert_array_almost_equal(myComponent.rotationEulerAngles, [90, 20, 70]) + np.testing.assert_array_almost_equal(myComponent.scale, [2, 3, 4]) + + + + + + + + print("TestBasicTransform:test_extract_TRS_atributes() END") def test_BasicTransform_compNullIterator(self): #test null iterator diff --git a/Elements/pyGLV/GUI/Viewer.py b/Elements/pyGLV/GUI/Viewer.py index 012c1746..5a2d9a81 100644 --- a/Elements/pyGLV/GUI/Viewer.py +++ b/Elements/pyGLV/GUI/Viewer.py @@ -32,6 +32,7 @@ from Elements.pyECSS.System import System from Elements.pyECSS.Component import BasicTransform import numpy as np +from scipy.spatial.transform import Rotation # from Elements.pyGLV.GL.Scene import Scene # from Elements.pyGLV.GL.Scene import Scene @@ -561,14 +562,6 @@ def event_input_process(self): #keyboard events elif event.type == sdl2.SDL_KEYDOWN: - # if event.key.keysym.sym == sdl2.SDLK_UP or event.key.keysym.sym == sdl2.SDLK_w : - # pass - # if event.key.keysym.sym == sdl2.SDLK_DOWN or event.key.keysym.sym == sdl2.SDLK_s : - # pass - # if event.key.keysym.sym == sdl2.SDLK_LEFT or event.key.keysym.sym == sdl2.SDLK_a : - # pass - # if event.key.keysym.sym == sdl2.SDLK_RIGHT or event.key.keysym.sym == sdl2.SDLK_d : - # pass ################## toggle the wireframe using the alt+F buttons ############################# if (event.key.keysym.sym == sdl2.SDLK_f and (sdl2.SDL_GetModState() & shortcut_HotKey)): self.toggle_Wireframe() @@ -707,8 +700,8 @@ def __init__(self, wrapee: RenderWindow, imguiContext = None): self._colorEditor = wrapee._colorEditor ### Bool variables for Scenegraph Visualizer imgui - self.collapseElementsWindow = False - self.collapseScenegraphVisualizer = True + self.showElementsWindow = True + self.collapseElementsWindow = True ### Bool variables to collapse and close the ECSS graph self.showScenegraphVisualizer = True @@ -770,7 +763,6 @@ def display(self): # draw scenegraph tree widget self.scenegraphVisualiser() self.menuBar() - # print(f'{self.getClassName()}: display()') def moveEye(self, value, direction): @@ -1098,6 +1090,8 @@ class ImGUIecssDecorator(ImGUIDecorator): def __init__(self, wrapee: RenderWindow, imguiContext = None): super().__init__(wrapee, imguiContext) self.selected = None; # Selected should be a component + self.countTRSOpened = 0 + self.countOpened = 0 def scenegraphVisualiser(self): """display the ECSS in an ImGUI tree node structure @@ -1106,28 +1100,15 @@ def scenegraphVisualiser(self): sceneRoot = self.wrapeeWindow.scene.world.root.name if sceneRoot is None: sceneRoot = "ECSS Root Entity" - - twoColumn = False + ########## Added bool variable to enable to close the graph window ############### if self.showScenegraphVisualizer: imgui.core.set_next_window_collapsed(not self.collapseScenegraphVisualizer, imgui.FIRST_USE_EVER) - if twoColumn: - # 2 Column Version - self.collapseScenegraphVisualizer, self.showScenegraphVisualizer = imgui.begin("ECSS graph",True) - imgui.columns(2, "Properties") - if imgui.tree_node(sceneRoot, imgui.TREE_NODE_OPEN_ON_ARROW): - self.drawNode(self.wrapeeWindow.scene.world.root) - imgui.tree_pop() - imgui.next_column() - imgui.text("Properties") - imgui.separator() - else: - self.collapseScenegraphVisualizer, self.showScenegraphVisualizer = imgui.begin("ECSS graph",True) - imgui.columns(1, "Properties") - imgui.text("Properties") - imgui.separator() + + self.collapseScenegraphVisualizer, self.showScenegraphVisualizer = imgui.begin("ECSS graph",True) + imgui.columns(2, "Properties") ###### do this so we can be able to move the window after it was collapsed ######### ####### and we re open it ######### @@ -1135,40 +1116,46 @@ def scenegraphVisualiser(self): imgui.set_window_position(self.graph_x,self.graph_y,imgui.FIRST_USE_EVER) else: imgui.set_window_position(self.graph_x,self.graph_y, imgui.FIRST_USE_EVER) - - # smallerTRSgui = True - #TRS sample - # if(isinstance(self.selected, BasicTransform)): - - if imgui.tree_node("Translation", imgui.TREE_NODE_LEAF): - imgui.same_line() - changed, value = imgui.drag_float3("X,Y,Z",self.translation["x"],self.translation["y"],self.translation["z"], 0.01, -30, 30, "%.001f", 1); - self.translation["x"],self.translation["y"],self.translation["z"] = value[0],value[1], value[2] - imgui.tree_pop(); - if imgui.tree_node("Rotation ", imgui.TREE_NODE_LEAF): - imgui.same_line() - changed, value = imgui.drag_float3("X,Y,Z",self.rotation["x"],self.rotation["y"],self.rotation["z"], 1, -180, 180, "%.1f", 1); - self.rotation["x"],self.rotation["y"],self.rotation["z"] = value[0],value[1], value[2] - imgui.tree_pop(); - if imgui.tree_node("Scale ", imgui.TREE_NODE_LEAF): - imgui.same_line() - changed, value = imgui.drag_float3("X,Y,Z",self.scale["x"],self.scale["y"],self.scale["z"], 0.01, 0, 4, "%.01f", 1); - self.scale["x"],self.scale["y"],self.scale["z"] = value[0],value[1], value[2] - imgui.tree_pop(); + + ## DRAW THE ECSS GRAPH + # imgui.next_column() + if imgui.tree_node(sceneRoot, imgui.TREE_NODE_LEAF): + self.countTRSOpened = 0 + self.countOpened = 0 + self.drawNode(self.wrapeeWindow.scene.world.root) + imgui.tree_pop() + + imgui.next_column() + imgui.text("Properties") + # imgui.tree_pop() + imgui.separator() - - if twoColumn: - pass - else: - imgui.separator() - if imgui.tree_node(sceneRoot, imgui.TREE_NODE_OPEN_ON_ARROW): - self.drawNode(self.wrapeeWindow.scene.world.root) - imgui.tree_pop() + if self.selected is not None and self.countOpened > 0: + imgui.selectable(self.selected.__str__(), True) + if hasattr(self.selected, "drawSelfGui"): + self.selected.drawSelfGui(imgui); + + if self.countTRSOpened == 1: + if imgui.tree_node("Translation:", imgui.TREE_NODE_LEAF): + # imgui.same_line() + changed, value = imgui.drag_float3("",self.translation["x"],self.translation["y"],self.translation["z"], 0.01, -30, 30, "%.001f", 1); + self.translation["x"],self.translation["y"],self.translation["z"] = value[0],value[1], value[2] + imgui.tree_pop(); + if imgui.tree_node("Rotation :", imgui.TREE_NODE_LEAF): + # imgui.same_line() + changed, value = imgui.drag_float3(" ",self.rotation["x"],self.rotation["y"],self.rotation["z"], 1, -180, 180, "%.1f", 1); + self.rotation["x"],self.rotation["y"],self.rotation["z"] = value[0],value[1], value[2] + imgui.tree_pop(); + if imgui.tree_node("Scale :", imgui.TREE_NODE_LEAF): + # imgui.same_line() + changed, value = imgui.drag_float3("",self.scale["x"],self.scale["y"],self.scale["z"], 0.01, -4, 4, "%.01f", 1); + self.scale["x"],self.scale["y"],self.scale["z"] = value[0],value[1], value[2] + imgui.tree_pop(); imgui.end() def drawNode(self, component): - #create a local iterator of Entity's children + if component._children is not None: debugIterator = iter(component._children) #call print() on all children (Concrete Components or Entities) while there are more children to traverse @@ -1183,11 +1170,16 @@ def drawNode(self, component): # using ## creates unique labels, without showing anything after ## # see: https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-how-can-i-have-multiple-widgets-with-the-same-label if imgui.tree_node(comp.name + "##" + str(comp.id), imgui.TREE_NODE_OPEN_ON_ARROW): - imgui.text(comp.name) - _, selected = imgui.selectable(comp.__str__(), True) + # imgui.text(comp.name) + # _, selected = imgui.selectable(comp.__str__(), True) + closed , selected = imgui.selectable('Info for ' + comp.name + " -> ", True) + if not closed: # if the node is closed, reset the count of opened nodes + self.countOpened +=1 + # self.countTRSOpened = 0 if selected: if ( comp != self.selected ): # First time selecting it. Set trs values to GUI; self.selected = comp + # print("selected: ", self.selected.name) if isinstance(comp, BasicTransform): [x, y, z] = comp.translation; self.translation["x"] = x; @@ -1205,16 +1197,17 @@ def drawNode(self, component): # self.color = comp.color.copy(); else: # Set GUI values to trs; if isinstance(comp, BasicTransform): + self.countTRSOpened += 1 transMat = util.translate(self.translation["x"], self.translation["y"], self.translation["z"]); rotMatX = util.rotate((1, 0, 0), self.rotation["x"]) rotMatY = util.rotate((0, 1, 0), self.rotation["y"]) rotMatZ = util.rotate((0, 0, 1), self.rotation["z"]) scaleMat = util.scale(self.scale["x"], self.scale["y"], self.scale["z"]) - comp.trs = util.identity() @ transMat @ rotMatX @ rotMatY @ rotMatZ @ scaleMat; - # comp.trs = scaleMat @ rotMatZ @ rotMatY @ rotMatX @ transMat; - elif hasattr(comp, "drawSelfGui"): - comp.drawSelfGui(imgui); + comp.trs = util.identity() @ transMat @ rotMatZ @ rotMatY @ rotMatX @ scaleMat; + # elif hasattr(comp, "drawSelfGui"): + # comp.drawSelfGui(imgui); + imgui.tree_pop() @@ -1270,14 +1263,15 @@ def apply2SDLWindow(self, sdlWindow, event=None): if __name__ == "__main__": - # # The client code. - gWindow = SDL2Window(openGLversion=3) # uses openGL version 3.2 instead of the default 4.1 - gWindow.init() - gWindow.init_post() - running = True - # MAIN RENDERING LOOP - while running: - gWindow.display() - running = gWindow.event_input_process(running) - gWindow.display_post() + # The client code. + gWindow = SDL2Window(openGLversion=3) + # uses openGL version 3.2 instead of the default 4.1 + gWindow.init() + gWindow.init_post() + running = True + # MAIN RENDERING LOOP + while running: + gWindow.display() + running = gWindow.event_input_process(running) + gWindow.display_post() gWindow.shutdown() \ No newline at end of file