Skip to content

Commit

Permalink
Merge pull request #52 from kamarianakis/feature/ECSS_Graph_Improvements
Browse files Browse the repository at this point in the history
Feature/ecss graph improvements
  • Loading branch information
kamarianakis authored Nov 16, 2023
2 parents 6914613 + d2cc757 commit 7de59fd
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 92 deletions.
2 changes: 1 addition & 1 deletion Elements/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
__version__ = '1.3.2'
44 changes: 28 additions & 16 deletions Elements/pyECSS/Component.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions Elements/pyECSS/Entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"



Expand Down
41 changes: 41 additions & 0 deletions Elements/pyECSS/tests/test_EntityComponents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 67 additions & 73 deletions Elements/pyGLV/GUI/Viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -1106,69 +1100,62 @@ 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 #########
if self.collapseScenegraphVisualizer:
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
Expand All @@ -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;
Expand All @@ -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()

Expand Down Expand Up @@ -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()

0 comments on commit 7de59fd

Please sign in to comment.