Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 31 additions & 51 deletions examples/basic_usage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "d59622c6",
"metadata": {},
"outputs": [],
Expand All @@ -27,7 +27,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 2,
"id": "f6cb8c80",
"metadata": {},
"outputs": [
Expand All @@ -37,7 +37,7 @@
"'tcs00001'"
]
},
"execution_count": 3,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
Expand Down Expand Up @@ -78,7 +78,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"id": "56285449",
"metadata": {},
"outputs": [],
Expand All @@ -88,7 +88,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"id": "d00b88d6",
"metadata": {},
"outputs": [
Expand All @@ -98,6 +98,14 @@
"text": [
"package MyStructure {\n",
"\n",
"\n",
" part def Onshape_Component {\n",
" attribute onshape_url;\n",
" }\n",
" part def Omniverse_Component {\n",
" attribute ov_filepath;\n",
" }\n",
"\n",
" part def Component{\n",
" attribute tx;\n",
" attribute ty;\n",
Expand All @@ -114,15 +122,15 @@
"\n",
" part def Context {\n",
" part rootelement :Component {\n",
" part nx00001 subsets children {\n",
" part nx00001: Onshape_Component, Omniverse_Component subsets children {\n",
" attribute :>> tx=1.0;\n",
" attribute :>> ty=1.0;\n",
" attribute :>> tz=1.0;\n",
" attribute :>> rx=1.0;\n",
" attribute :>> ry=1.0;\n",
" attribute :>> rz=1.0;\n",
" attribute :>> typeID = 0;\n",
" part tcs00001 subsets children {\n",
" part tcs00001: Onshape_Component, Omniverse_Component subsets children {\n",
" attribute :>> tx=1.0;\n",
" attribute :>> ty=1.0;\n",
" attribute :>> tz=1.0;\n",
Expand Down Expand Up @@ -152,7 +160,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "5bfc772f",
"metadata": {},
"outputs": [
Expand All @@ -166,7 +174,7 @@
],
"source": [
"#flexo config\n",
"BASE_URL = \"URL\" \n",
"BASE_URL = \"http://192.168.1.214:31083/\" \n",
"\n",
"BEARER_TOKEN = \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJmbGV4by1tbXMtYXVkaWVuY2UiLCJpc3MiOiJodHRwOi8vZmxleG8tbW1zLXNlcnZpY2VzIiwidXNlcm5hbWUiOiJ1c2VyMDEiLCJncm91cHMiOlsic3VwZXJfYWRtaW5zIl0sImV4cCI6MTc2OTY3MzYwMH0.UqU5KOPSCbYyqbj3BBZs4u7lWbpHyDHPEd7Tbd4wWsM\"\n",
"\n",
Expand All @@ -187,8 +195,8 @@
"metadata": {},
"outputs": [],
"source": [
"project, proj_id = v2.get_project_by_name(client, name = \"myproject\")\n",
"created_project, example_project_id, _ = v2.create_sysml_project(client, name=\"myproject\")\n",
"project, proj_id = v2.get_project_by_name(client, name = \"Flexo_SysIDE_TestProject\")\n",
"created_project, example_project_id, _ = v2.create_sysml_project(client, name=\"Flexo_SysIDE_TestProject\")\n",
"change_payload_str = convert_sysml_string_textual_to_json(sysml_text, minimal=False)\n",
"commit1_response, commit1_id = v2.commit_to_project(client, example_project_id, change_payload_str, commit_msg = \"default commit message\")"
]
Expand All @@ -205,55 +213,27 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 11,
"id": "6b1870b8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Walking the ownership tree printing all elements:\n",
"[{'abs_rx': 1.0,\n",
" 'abs_ry': 1.0,\n",
" 'abs_rz': 1.0,\n",
" 'abs_tx': 1.0,\n",
" 'abs_ty': 1.0,\n",
" 'abs_tz': 1.0,\n",
" 'name': 'nx00001',\n",
" 'parent_name': None,\n",
" 'parent_typeID': None,\n",
" 'rx': 1.0,\n",
" 'ry': 1.0,\n",
" 'rz': 1.0,\n",
" 'tx': 1.0,\n",
" 'ty': 1.0,\n",
" 'typeID': 1000.0,\n",
" 'tz': 1.0},\n",
" {'abs_rx': 2.469355406085109,\n",
" 'abs_ry': 0.28857876165748914,\n",
" 'abs_rz': 2.469355406085109,\n",
" 'abs_tx': 2.1735727354212466,\n",
" 'abs_ty': 2.2703235189345308,\n",
" 'abs_tz': 0.9051043103313733,\n",
" 'name': 'tcs00001',\n",
" 'parent_name': 'nx00001',\n",
" 'parent_typeID': 1000.0,\n",
" 'rx': 1.0,\n",
" 'ry': 1.0,\n",
" 'rz': 1.0,\n",
" 'tx': 1.0,\n",
" 'ty': 1.0,\n",
" 'typeID': 2.0,\n",
" 'tz': 1.0}]\n"
"ename": "ImportError",
"evalue": "cannot import name 'find_part_with_components' from 'geometry_api.geometry_api' (/Users/johannes/sysmlv2_dls/.venv/lib/python3.13/site-packages/geometry_api/geometry_api.py)",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mImportError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 5\u001b[39m\n\u001b[32m 3\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mpprint\u001b[39;00m\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtransformation_api\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mtransformations\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m transformation_matrix, euler_from_matrix\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mgeometry_api\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mgeometry_api\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m components_from_part_world, find_part_with_components, walk_ownership_tree, find_part_by_name\n\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# If needed to import your transformations file:\u001b[39;00m\n\u001b[32m 8\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msyside\u001b[39;00m\n",
"\u001b[31mImportError\u001b[39m: cannot import name 'find_part_with_components' from 'geometry_api.geometry_api' (/Users/johannes/sysmlv2_dls/.venv/lib/python3.13/site-packages/geometry_api/geometry_api.py)"
]
}
],
"source": [
"# example_mypattern_components.py\n",
"import json\n",
"import pprint\n",
"from geometry_api.transformations import transformation_matrix, euler_from_matrix\n",
"from transformation_api.transformations import transformation_matrix, euler_from_matrix\n",
"from geometry_api.geometry_api import components_from_part_world, find_part_with_components, walk_ownership_tree, find_part_by_name\n",
"\n",
"# If needed to import your transformations file:\n",
Expand Down Expand Up @@ -332,7 +312,7 @@
],
"metadata": {
"kernelspec": {
"display_name": ".venv (3.13.2)",
"display_name": ".venv",
"language": "python",
"name": "python3"
},
Expand All @@ -346,7 +326,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
"version": "3.13.7"
}
},
"nbformat": 4,
Expand Down
22 changes: 22 additions & 0 deletions examples/example_geometry_commit_retrieve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

from flexo_syside_lib.committer import commit_sysml_to_flexo


DEFAULT_PROJECT_NAME = "Flexo_SysIDE_TestProject"
sysml_sample = """
package TestPackage {
part Satellite {
attribute mass = 500.0;
}
}
"""

FLEXO_API_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJmbGV4by1tbXMtYXVkaWVuY2UiLCJpc3MiOiJodHRwOi8vZmxleG8tbW1zLXNlcnZpY2VzIiwidXNlcm5hbWUiOiJ1c2VyMDEiLCJncm91cHMiOlsic3VwZXJfYWRtaW5zIl0sImV4cCI6MTc2OTY3MzYwMH0.UqU5KOPSCbYyqbj3BBZs4u7lWbpHyDHPEd7Tbd4wWsM"

result = commit_sysml_to_flexo(
sysml_output=sysml_sample,
project_name=DEFAULT_PROJECT_NAME,
verbose=True,
)

print("Commit Result:", result)
105 changes: 105 additions & 0 deletions src/geometry_api/geometry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,108 @@ def dfs(node):
found, _ = dfs(elem)
return found

def load_from_sysml(root, clear_existing: bool = True):
"""
Inverse of get_sysmlv2_text():
Reads a SysMLv2 model (from syside) starting at `root` and rebuilds the
Python Component hierarchy (_components dict).

Args:
root: SysIDE model element (e.g., the Context or Component PartUsage).
clear_existing: If True, clears any previous _components registry.
Returns:
The root Component object reconstructed from the model.
"""
if clear_existing:
_components.clear()

def collect_attrs(el):
"""Collect numeric and string attribute values under a PartUsage element."""
vals = {}
if not hasattr(el, "owned_elements"):
return vals
el.owned_elements.for_each(lambda e: _collect_attr(e, vals))
return vals

def _collect_attr(e, vals):
au = e.try_cast(syside.AttributeUsage)
if not au:
return
expression = next(iter(au.owned_elements), None)
if isinstance(expression, (syside.LiteralRational, syside.LiteralInteger)):
vals[au.name] = float(expression.value)
elif isinstance(expression, syside.LiteralString):
vals[au.name] = str(expression.value)



def visit(el, parent_component=None, level=0):
indent = " " * level
try:
name = getattr(el, "name", None)
except Exception:
name = None
#print(f"{indent}▶ Visiting element: {name or type(el)}")

# Try to cast to PartUsage
part = el.try_cast(syside.PartUsage)
if part:
#print(f"{indent} ✓ is PartUsage: {part.name}")
vals = collect_attrs(el)
#print(f"{indent} attributes found: {vals}")

# Check for typeID or Component definition
has_typeid = "typeID" in vals
part_defs = getattr(part, "part_definitions", [])
def_names = [getattr(pd, "name", None) for pd in part_defs]
#print(f"{indent} part_definitions: {def_names}")

has_component_def = any(n == "Component" for n in def_names)
#print(f"{indent} has_typeid={has_typeid}, has_component_def={has_component_def}")

if has_typeid or has_component_def:
type_id = int(vals.get("typeID", len(_components) + 1))
#print(f"{indent} → creating Component(name={part.name}, typeID={type_id})")

translation = CartesianRepresentation(
vals.get("tx", 0.0),
vals.get("ty", 0.0),
vals.get("tz", 0.0),
)
rotation = CartesianRepresentation(
vals.get("rx", 0.0),
vals.get("ry", 0.0),
vals.get("rz", 0.0),
)

this_component = Component(
name=part.name or f"Unnamed_{len(_components)}",
typeID=type_id,
translation=translation,
rotation=rotation,
parent=parent_component,
)
_components[this_component.name] = this_component
#print (this_component.name, this_component.typeID, this_component.translation, this_component.rotation)
parent_component = this_component
#else:
# print(f"{indent} ✗ skipping (no typeID or Component def)")
#else:
# Not a PartUsage
# print(f"{indent} ✗ not a PartUsage")

# Recurse into owned elements
if hasattr(el, "owned_elements"):
try:
el.owned_elements.for_each(lambda c: visit(c, parent_component, level + 1))
except Exception as e:
print(f"{indent} ⚠️ Error iterating children of {name}: {e}")


visit(root, None)

# Return the highest (first) component as the likely root
roots = [c for c in _components.values() if c.parent is None]
if roots:
roots = roots[0]
return roots, _components
6 changes: 5 additions & 1 deletion tests/geometry_example.sysml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ package DemoStructure {
part def Omniverse_Component {
attribute ov_filepath;
}
part def AttitudeSolari :> Onshape_Component{
attribute :>> onshape_url = "https://cad.onshape.com/documents/4a29c75993840faff03a0c45/w/64843d9e906f6a84703e03a0/e/fdc17e965aef7326b7f8c27c";

}

part def Component{
attribute tx;
Expand All @@ -19,7 +23,7 @@ package DemoStructure {
}

part def Context {
part geometryroot :Component {
part geometryroot :Component, Onshape_Component,Omniverse_Component {
part nexus: Onshape_Component,Omniverse_Component subsets children {
attribute :>> tx=0.0;
attribute :>> ty=0.0;
Expand Down
34 changes: 32 additions & 2 deletions tests/test_geometry_api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from multiprocessing import context
from annotated_types import doc
import pytest

import syside
from geometry_api.geometry_api import (
create_component,
get_sysmlv2_text,
clear_components,
load_from_sysml,
find_partusage_by_definition,
)


Expand Down Expand Up @@ -42,7 +46,8 @@ def test_create_child_component_hierarchy():
)

text = get_sysmlv2_text("root")
assert "part child subsets children {" in text

assert "part child: Onshape_Component, Omniverse_Component subsets children {" in text
assert "tx=1.0;" in text
assert "ry=0.2;" in text
assert "typeID = 2;" in text
Expand Down Expand Up @@ -79,4 +84,29 @@ def test_get_text_missing_root_raises():
with pytest.raises(ValueError, match="Root component 'nope' not found"):
get_sysmlv2_text("nope")

from pathlib import Path


def test_load_from_sysml_and_regenerate_text():
this_dir = Path(__file__).parent
model_path = this_dir / "geometry_example.sysml"
print(f"Looking for model file at: {model_path.resolve()}")
model, _ = syside.load_model([str(model_path)])
model, _ = syside.load_model([str(model_path)])

# Get the first document root for traversal
context = None
for doc_res in model.documents:
with doc_res.lock() as doc:
context = find_partusage_by_definition(doc.root_node, "Component", usage_name="geometryroot")
if context:
break

assert context is not None, "Could not find PartUsage for geometryroot"
print("Loading from SysMLv2 model...")
root_comp = load_from_sysml(context)

print(root_comp.to_textual())
#test_load_from_sysml_and_regenerate_text()


Loading
Loading