treeViewer creation

Hi,

I’m trying to create treeViwer to show the elements from Project browser with the following preliminary code

loadModule('/System/UI')
loadModule('/System/UI Builder')
loadModule('/System/Resources')

# Include needed for the Capella modeller API
include('workspace://Python4Capella/simplified_api/capella.py')
if False:
    from simplified_api.capella import *

# Include needed for utilities
include('workspace://Python4Capella/utilities/CapellaPlatform.py')
if False:
    from utilities.CapellaPlatform import *


aird_path = '/In-Flight Entertainment System/In-Flight Entertainment System.aird'

model = CapellaModel()
model.open(aird_path)


se = model.get_system_engineering()
print("System Engineering:", se.get_name())

def get_model_hierarchy():
    """Retrieve only the Operational Analysis as the root element for the TreeViewer."""
    operational_analysis = se.get_operational_analysis()
    if operational_analysis:
        print("Root Element Retrieved:", operational_analysis.get_name())
        return [operational_analysis]  # Return the actual Operational Analysis object
    else:
        print("No Operational Analysis found.")
        return []

def get_children():
    """Retrieve children by navigating the containment structure using eContainer."""
    element = getProviderElement()

    if hasattr(element, "get_all_contents"):
        children = element.get_all_contents()
        print(f"{element.get_name()} has {len(children)} children.")
        return children if children else None
    else:
        print(f"{element.get_name()} has no children.")
        return None

def get_resource_size():
    """Callback function to provide a size metric for each element."""
    element = getProviderElement()
    if hasattr(element, "get_all_contents"):
        child_count = len(element.get_all_contents())
        return f"{child_count} children"
    return "Size not available"

# UI setup with tree viewer, columns, and comparator
createView("Operational Analysis Hierarchy")
viewer = createTreeViewer(get_model_hierarchy(), get_children)

# Create columns for the viewer
createViewerColumn(viewer, "Element", createLabelProvider(lambda: getProviderElement().get_name()), 4)
createViewerColumn(viewer, "Size", createLabelProvider(get_resource_size), 1)

# Add comparator to sort elements
createComparator(viewer, "return (getProviderElement().get_all_contents() != null) ? 1 : 2;")

But I’m not able to show the treViewer, by any chance, does anyone know how to view the project browser?. Any help would be appreciated

Thank you

I was not able to make this works. The first level is displayed as expected but when I try to expend the first level I have empty list passed to the label and content providers… I’m not sure why. Maybe you will figure it out, if you do let me know what was wrong.

def get_model_hierarchy():
    """Retrieve only the Operational Analysis as the root element for the TreeViewer."""
    operational_analysis = se.get_operational_analysis()
    if operational_analysis:
        print("Root Element Retrieved:", operational_analysis.get_name())
        return [operational_analysis.get_java_object()]  # Return the actual Operational Analysis object
    else:
        print("No Operational Analysis found.")
        return None

def get_children(element):
    """Retrieve children by navigating the containment structure using eContainer."""
    print("get_children()", element)
    try:
        children = element.eContents()
        print(f"{element.getName()} has {len(children)} children.")
        res = children
        print(res)
        return res
    except Exception:
        return None

def get_name_label(element):
    """Retrieve children by navigating the containment structure using eContainer."""
    print("get_name_label()", element)
    try:
        return element.getName()
    except Exception:
        return "<No name>"

def get_size_label(element):
    """Callback function to provide a size metric for each element."""
    try:
        child_count = len(element.eContents())
        return f"{child_count} children"
    except Exception:
        return "No children"

# UI setup with tree viewer, columns, and comparator
createView("Operational Analysis Hierarchy")
viewer = createTreeViewer(get_model_hierarchy(), "get_children(getProviderElement())")

# Create columns for the viewer
createViewerColumn(viewer, "Element", createLabelProvider("get_name_label(getProviderElement())"), 4)
createViewerColumn(viewer, "Size", createLabelProvider("get_size_label(getProviderElement())"), 1)

# Add comparator to sort elements
# createComparator(viewer, "return (getProviderElement().get_all_contents() != null) ? 1 : 2;")

Hey @YvanLussaud and @Licia,

I have been following the discussion on this topic thread with great interest. Could you kindly guide me to the relevant documentation that I should review to effectively execute the tasks discussed?

Thank you in advance for your assistance.

The EASE documentation on the UI can be found here. There are also some examples but they are in Javascript.

And finally you can have a look at the GUI section of the Python4Capella tips and tricks. I Added some working examples for Python. One important point is that callbacks are strings with the code that need to be interpreted in Python where in Javascript the function itself is passed.

If you have any progress on the tree viewer or a table viewer please contribute to this thread.

1 Like

Thank you for reply Yvan, I appreciate it. Maybe I should search alternative to treeViewer that able to show the project browser from UI.
Here an example of ListViewer that could be help for someone

def get_architecture_elements(element, level=0):
    elements = []
    indent = "  " * level 

    element_name = getattr(element, "get_name", lambda: f"Unnamed Element ({type(element).__name__})")()
    elements.append(f"{indent}{element_name}")

    children = element.get_contents() if hasattr(element, "get_all_contents") else []
    for child in children:
        elements.extend(get_architecture_elements(child, level + 1))

    return elements

def get_model_hierarchy():
    main_architectures = [se.get_operational_analysis()]
    unique_elements = set() 
    hierarchy = []

    for architecture in main_architectures:
        if architecture is not None:
            elements = get_architecture_elements(architecture)
            for element in elements:
                if element not in unique_elements: 
                    unique_elements.add(element)
                    hierarchy.append(element)
    return hierarchy

class ModelTreeDialog:
    def __init__(self):
        self.listViewer = None
        self.elements = get_model_hierarchy()

    def build(self):
        print("Building UI components...")

        scrolledComposite = createScrolledComposite("1/1 o! o!")
        pushComposite(scrolledComposite)  
        
       
        self.listViewer = createListViewer(self.elements, layout="o! o!")
        print("List viewer created:", self.listViewer)

        popComposite()  # Pop the scrolled composite to return to the main composite

dialog_instance = ModelTreeDialog()
javaDialog = createDialog("dialog_instance.build()", "Capella Model Hierarchy", "Browse architectures and their elements")
result = executeUI("javaDialog.open()")

You probably can ask on the EASE development mailing list or on the EASE bugzilla what we are missing here. It’s probably not far from a working script. They moved to gitlab. Please link your question here so I can keep track to provide new sample script and/or improve the documentation.

1 Like

Hey @Licia, a follow up after a long time, I executed the listViewer of the project hierarchy, I have a trouble executing the complete code, for me instance of ModelTreeDialog is successfully create which implies that get_model_hierarchy() and get_architecture_elements() methods are working perfectly, but calback to the dialog_instance.build() in the createDialog() function is throwing below given error:

org.eclipse.ease.ScriptExecutionException: java.lang.SecurityException: Script UI access disabled by user preferences.

Java Stacktrace:
java.lang.SecurityException: Script UI access disabled by user preferences.
	at org.eclipse.ease.security.ScriptUIAccess.doIt(ScriptUIAccess.java:38)
	at org.eclipse.ease.AbstractScriptEngine.inject(AbstractScriptEngine.java:178)
	at org.eclipse.ease.AbstractScriptEngine.inject(AbstractScriptEngine.java:151)
	at org.eclipse.ease.modules.platform.UIModule.executeUI(UIModule.java:80)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)
	at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:357)
	at py4j.Gateway.invoke(Gateway.java:282)
	at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
	at py4j.commands.CallCommand.execute(CallCommand.java:79)
	at py4j.ClientServerConnection.sendCommand(ClientServerConnection.java:244)
	at py4j.CallbackClient.sendCommand(CallbackClient.java:384)
	at py4j.CallbackClient.sendCommand(CallbackClient.java:356)
	at py4j.reflection.PythonProxyHandler.invoke(PythonProxyHandler.java:106)
	at com.sun.proxy.$Proxy30.executeScript(Unknown Source)
	at org.eclipse.ease.lang.python.py4j.internal.Py4jScriptEngine.internalExecute(Py4jScriptEngine.java:240)
	at org.eclipse.ease.lang.python.py4j.internal.Py4jScriptEngine.execute(Py4jScriptEngine.java:227)
	at org.eclipse.ease.AbstractScriptEngine.inject(AbstractScriptEngine.java:189)
	at org.eclipse.ease.AbstractScriptEngine.run(AbstractScriptEngine.java:242)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)

I am more concerned with the statement Script UI access disabled by user preferences, does it mean that i need to provide some access via the Preferences, if yes please guide me through that,

Thank you

You can change this settings in your preferences (menu window/preferences):

If you can share a working example it would be nice.

1 Like

Wow that worked !!! Thank you @YvanLussaud for the patch.

Here is the working code for the Model hierarchy using list Viewer
(For running the code IFE project should be in the workspace and open.):

# Import necessary modules for the Capella modeller API
include('workspace://Python4Capella/simplified_api/capella.py')
if False:
    from simplified_api.capella import *

def createDialog():
    pass
def createScrolledComposite(): 
    pass
def pushComposite():
    pass
def popComposite():
    pass
def createView(label_string, uri, targetview, position_wrt_target):
    pass
def createListViewer():
    pass
def executeUI():
    pass

loadModule('/System/UI Builder')
loadModule('/System/UI')

# Define a function to recursively get architecture elements and their hierarchy
def get_architecture_elements(element, level=0):
    elements = []
    indent = "  " * level 

    # Get the name of the element, or use a default name if not available
    element_name = getattr(element, "get_name", lambda: f"Unnamed Element ({type(element).__name__})")()
    elements.append(f"{indent}{element_name}")

    # Get the children of the element if available
    children = element.get_contents() if hasattr(element, "get_all_contents") else []
    for child in children:
        elements.extend(get_architecture_elements(child, level + 1))

    return elements

# Define a function to get the model hierarchy
def get_model_hierarchy():
    aird_path = '/In-Flight Entertainment System/In-Flight Entertainment System.aird'
    model = CapellaModel()
    model.open(aird_path)
    
    # Get the SystemEngineering and print its name
    se = model.get_system_engineering()
    main_architectures = [se.get_operational_analysis()]
    unique_elements = set() 
    hierarchy = []

    # Iterate through the main architectures and get their elements
    for architecture in main_architectures:
        if architecture is not None:
            elements = get_architecture_elements(architecture)
            for element in elements:
                if element not in unique_elements: 
                    unique_elements.add(element)
                    hierarchy.append(element)
    
    return hierarchy

# Define a class for the model tree dialog
class ModelTreeDialog:
    def __init__(self):
        self.listViewer = None
        self.elements = get_model_hierarchy()
        print("ModelTreeDialog instance created")

    # Build the UI components for the dialog
    def build(self):
        print("Building UI components...")

        scrolledComposite = createScrolledComposite("1/1")
        pushComposite(scrolledComposite)  
        
        self.listViewer = createListViewer(self.elements)
        print("List viewer created:", self.listViewer)

        popComposite()  # Pop the scrolled composite to return to the main composite

# Create an instance of the ModelTreeDialog class
dialog_instance = ModelTreeDialog()
print("about to create the dialog")

# Create and execute the dialog
javaDialog = createDialog("dialog_instance.build()", "Capella Model Hierarchy", "Browse architectures and their elements")
result = executeUI("javaDialog.open()")

Output:

Console output
Console output

Dialog
Created Dialog with the hierarchy

~
That bieng the output, I have a doubt regarding the dialog created: it was meant to be scrollable as we pushed a scrollable composite but that is not the case, code I am pointing to is below:

 scrolledComposite = createScrolledComposite("1/1")
 pushComposite(scrolledComposite)  

in the .build() of the ModelTreeDialog class.

I would love the explaination on such behaviour of the dialog.

Thank you @YvanLussaud

~
I don’t know if this would help,

SMW version : 6.1.1
Capella version: 5.2.0
P4C version: 1.2.0

1 Like

In the above code removing the layout string from the createScrolledComposite() will work and provide the scroll bar

Updated code looks like this

scrolledComposite = createScrolledComposite()
pushComposite(scrolledComposite)  
2 Likes

Thank you for sharing your example. It will help others.

1 Like

Hi, I followed this discussion and tried to run the script in my project, but it only works for Operational Analysis. If I change

main_architectures = [se.get_operational_analysis()]

with

main_architectures = [se.get_system_analysis()]

I get the error ‘py4j.protocol.Py4JError: An error occurred while calling o677.getName. Trace:
py4j.Py4JException: Method getName() does not exist’. From other discussions, I understood that the problem resides in

element_name = getattr(element, "get_name", lambda: f"Unnamed Element ({type(element).__name__})")()

Because it tries to use get_name on a Java Object and not a Python object. Trying to change it with

element_name = getattr(element, "getName", lambda: f"Unnamed Element ({type(element).__name__})")()

gets rid of the error but prints
image
Any hep would be appreciated.

Thank you

I am really sorry but it doesn’t seem to be the case with me:
image

Could you share for your code for better understanding?

The code is pretty much the same as yours, I only changed the line ‘main_architectures = [se.get_system_analysis()]’. This is the code:

# Import necessary modules for the Capella modeller API
include('workspace://Python4Capella/simplified_api/capella.py')
if False:
    from simplified_api.capella import *

def createDialog():
    pass
def createScrolledComposite(): 
    pass
def pushComposite():
    pass
def popComposite():
    pass
def createView(label_string, uri, targetview, position_wrt_target):
    pass
def createListViewer():
    pass
def executeUI():
    pass

loadModule('/System/UI Builder')
loadModule('/System/UI')

# Define a function to recursively get architecture elements and their hierarchy
def get_architecture_elements(element, level=0):
    elements = []
    indent = "  " * level 

    # Get the name of the element, or use a default name if not available
    element_name = getattr(element, "get_name", lambda: f"Unnamed Element ({type(element).__name__})")()
    elements.append(f"{indent}{element_name}")

    # Get the children of the element if available
    children = element.get_contents() if hasattr(element, "get_all_contents") else []
    for child in children:
        elements.extend(get_architecture_elements(child, level + 1))

    return elements

# Define a function to get the model hierarchy
def get_model_hierarchy():
    aird_path = '/In-Flight Entertainment System/In-Flight Entertainment System.aird'
    model = CapellaModel()
    model.open(aird_path)
    
    # Get the SystemEngineering and print its name
    se = model.get_system_engineering()
    main_architectures = [se.get_system_analysis()]
    unique_elements = set() 
    hierarchy = []

    # Iterate through the main architectures and get their elements
    for architecture in main_architectures:
        if architecture is not None:
            elements = get_architecture_elements(architecture)
            for element in elements:
                if element not in unique_elements: 
                    unique_elements.add(element)
                    hierarchy.append(element)
    
    return hierarchy

# Define a class for the model tree dialog
class ModelTreeDialog:
    def __init__(self):
        self.listViewer = None
        self.elements = get_model_hierarchy()
        print("ModelTreeDialog instance created")

    # Build the UI components for the dialog
    def build(self):
        print("Building UI components...")

        scrolledComposite = createScrolledComposite()
        pushComposite(scrolledComposite)  
        
        self.listViewer = createListViewer(self.elements)
        print("List viewer created:", self.listViewer)

        popComposite()  # Pop the scrolled composite to return to the main composite

# Create an instance of the ModelTreeDialog class
dialog_instance = ModelTreeDialog()
print("about to create the dialog")

# Create and execute the dialog
javaDialog = createDialog("dialog_instance.build()", "Capella Model Hierarchy", "Browse architectures and their elements")
result = executeUI("javaDialog.open()")

Regardless of the architecture I choose to read elements from, I always get this error:

org.eclipse.ease.ScriptExecutionException: Traceback (most recent call last):
  File "workspace://TRY/src/TreeViewer.py", line 83, in <module>
    Tries to convert the given value using different strategies.
  File "workspace://TRY/src/TreeViewer.py", line 67, in __init__
  File "workspace://TRY/src/TreeViewer.py", line 55, in get_model_hierarchy
    def _pyease_patch_builtins(name, value):
  File "workspace://TRY/src/TreeViewer.py", line 36, in get_architecture_elements
    from six import integer_types as _pyease_integer_types
  File "workspace://TRY/src/TreeViewer.py", line 36, in get_architecture_elements
    from six import integer_types as _pyease_integer_types
  File "workspace://TRY/src/TreeViewer.py", line 36, in get_architecture_elements
    from six import integer_types as _pyease_integer_types
  File "workspace://TRY/src/TreeViewer.py", line 30, in get_architecture_elements
    from py4j.java_gateway import JavaClass as _pyease_JavaClass
  File "workspace://Python4Capella/simplified_api/capella.py", line 348, in get_name
    filtered = {
  File "C:\Users\User\Desktop\capella-7.0.0.202407091438-win32-win32-x86_64\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\java_gateway.py", line 1322, in __call__
    answer, self.gateway_client, self.target_id, self.name)
  File "C:\Users\User\Desktop\capella-7.0.0.202407091438-win32-win32-x86_64\capella\plugins\py4j-python_0.10.9.5-bnd-2odeag\src\py4j\protocol.py", line 332, in get_return_value
    format(target_id, ".", name, value))
py4j.protocol.Py4JError: An error occurred while calling o2492.getName. Trace:
py4j.Py4JException: Method getName([]) does not exist
	at py4j.reflection.ReflectionEngine.getMethod(ReflectionEngine.java:318)
	at py4j.reflection.ReflectionEngine.getMethod(ReflectionEngine.java:326)
	at py4j.Gateway.invoke(Gateway.java:274)
	at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
	at py4j.commands.CallCommand.execute(CallCommand.java:79)
	at py4j.ClientServerConnection.sendCommand(ClientServerConnection.java:244)
	at py4j.CallbackClient.sendCommand(CallbackClient.java:384)
	at py4j.CallbackClient.sendCommand(CallbackClient.java:356)
	at py4j.reflection.PythonProxyHandler.invoke(PythonProxyHandler.java:106)
	at jdk.proxy12/jdk.proxy12.$Proxy28.executeScript(Unknown Source)
	at org.eclipse.ease.lang.python.py4j.internal.Py4jScriptEngine.internalExecute(Py4jScriptEngine.java:240)
	at org.eclipse.ease.lang.python.py4j.internal.Py4jScriptEngine.execute(Py4jScriptEngine.java:227)
	at org.eclipse.ease.AbstractScriptEngine.inject(AbstractScriptEngine.java:189)
	at org.eclipse.ease.AbstractScriptEngine.run(AbstractScriptEngine.java:242)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)

If I try to read operational analysis from a custom project, it works, but not with system, logical or physical.
If I change the line

element_name = getattr(element, "get_name", lambda: f"Unnamed Element ({type(element).__name__})")()

with

element_name = getattr(element, "getName", lambda: f"Unnamed Element ({type(element).__name__})")()

I don’t get the error, but I get the types and not the names
image

1 Like

I don’t really understand why it doesn’t work… The error message tells that the get_name() method was not found. All the element From Capella are probably named. Maybe you have some extensions with no named element ? But still for other element we should probably see the name.

You can probably first start by checking if the attribute get_name() exists and only call it if it exists. When it doesn’t you can return the .get_java_object().toString() to see which object doesn’t have the attribute.

I solved the issue, even if I don’t fully understand what caused it. First, I put this code inside the function that gets the architecture:

def get_architecture_elements(element, level=0):
    try:
        element_name = element.get_name()
    except Exception as e:
        print(f"Error on element: {element}")  # Stampa l'oggetto Python
        print(f"Java class: {element.get_java_object().getClass().getName()}")  # Classe Java
        raise e
.....

It showed that the problematic class was ‘org.polarsys.capella.core.data.fa.impl.ComponentFunctionalAllocationImpl’.
So I wrote the function again to exclude this specific class:

def get_architecture_elements(element, level=0):
    """Recursively retrieves architecture elements and their hierarchy, skipping problematic classes."""
    elements = []
    indent = "  " * level  # Indentation based on hierarchy level

    # Skip processing if the Java class is problematic (e.g., ComponentFunctionalAllocationImpl)
    try:
        java_class = element.get_java_object().getClass().getName()
        if "ComponentFunctionalAllocationImpl" in java_class:
            return elements  # Skip this element entirely
    except Exception:
        pass

    # Attempt to retrieve the element's name
    try:
        element_name = element.get_name()  # Primary method to fetch the name
        elements.append(f"{indent}{element_name}")
    except Exception as e:
        # Optional: Log errors if needed (e.g., print(f"Error: {str(e)}"))
        return elements  # Skip elements that fail name resolution

    # Recursively process child elements
    if hasattr(element, "get_contents"):
        try:
            children = element.get_contents()
            for child in children:
                elements.extend(get_architecture_elements(child, level + 1))
        except Exception:
            pass  # Gracefully skip children if retrieval fails

    return elements

And now it works without issues. Thanks for the help.

2 Likes