From 48aed4dc9d1af1c09835e51792de297a81d6228a Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 31 Jan 2024 14:57:40 +0100 Subject: [PATCH 1/7] Fix Site packages. --- setup_dev.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup_dev.py b/setup_dev.py index c73d5d6..cac9e05 100644 --- a/setup_dev.py +++ b/setup_dev.py @@ -3,13 +3,14 @@ from os.path import join, islink, isdir, isfile, dirname from shutil import rmtree, which from pathlib import Path -from site import USER_SITE +from site import getsitepackages from pip._internal.operations.install.wheel import PipScriptMaker # Package information PROJECT = 'SSD' root = join(Path(__file__).parent.absolute(), 'src') +USER_SITE = getsitepackages()[0] # Check user entry if len(argv) == 2 and argv[1] not in ['set', 'del']: From 914193253b7b43b7c84ea0a9de82b70ab3f7e9dd Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 31 Jan 2024 18:29:48 +0100 Subject: [PATCH 2/7] Update dependencies versions. --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index b464b65..cb5f4aa 100644 --- a/setup.py +++ b/setup.py @@ -37,9 +37,9 @@ packages=packages, package_dir=packages_dir, package_data={f'{PROJECT}.examples.Core.rendering': ['armadillo.obj']}, - install_requires=['numpy >= 1.23.5', - 'peewee >= 3.15.1', - 'vedo >= 2022.4.1', - 'matplotlib >= 3.6.2', + install_requires=['numpy >= 1.26.3', + 'peewee >= 3.17.0', + 'vedo >= 2023.5.0', + 'matplotlib >= 3.8.2', 'open3d >= 0.16.0'], entry_points={'console_scripts': ['SSD=SSD.cli:execute_cli']}) From 4d61be27c8fb5386568192f16f4ead33cf5ced79 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 31 Jan 2024 18:30:06 +0100 Subject: [PATCH 3/7] Update Core.Storage package. --- docs/src/Core/Storage/database.rst | 6 +- docs/src/Core/Storage/utils.rst | 20 +++--- examples/Core/storage/README.md | 11 +-- .../{foreignkeyDB.py => foreignkey_db.py} | 2 +- .../Core/storage/{readingDB.py => read_db.py} | 4 +- .../storage/{signalDB.py => signal_db.py} | 2 +- .../storage/{updatingDB.py => update_db.py} | 6 +- examples/Core/storage/utils_db.py | 45 ++++++++++++ .../storage/{writingDB.py => write_db.py} | 2 +- src/Core/Storage/Exporter.py | 54 -------------- src/Core/Storage/__init__.py | 3 +- .../{AdaptiveTable.py => adaptive_table.py} | 25 ++++--- src/Core/Storage/{Database.py => database.py} | 43 +++++------ src/Core/Storage/exporter.py | 39 ++++++++++ .../{ExtendedFields.py => numpy_field.py} | 8 +-- ...{ExtendedPeewee.py => peewee_extension.py} | 48 +++++++------ src/Core/Storage/utils.py | 71 +++++++++++-------- src/Core/__init__.py | 2 - src/cli.py | 14 ++-- 19 files changed, 223 insertions(+), 182 deletions(-) rename examples/Core/storage/{foreignkeyDB.py => foreignkey_db.py} (99%) rename examples/Core/storage/{readingDB.py => read_db.py} (94%) rename examples/Core/storage/{signalDB.py => signal_db.py} (97%) rename examples/Core/storage/{updatingDB.py => update_db.py} (89%) create mode 100644 examples/Core/storage/utils_db.py rename examples/Core/storage/{writingDB.py => write_db.py} (98%) delete mode 100644 src/Core/Storage/Exporter.py rename src/Core/Storage/{AdaptiveTable.py => adaptive_table.py} (89%) rename src/Core/Storage/{Database.py => database.py} (96%) create mode 100644 src/Core/Storage/exporter.py rename src/Core/Storage/{ExtendedFields.py => numpy_field.py} (66%) rename src/Core/Storage/{ExtendedPeewee.py => peewee_extension.py} (79%) diff --git a/docs/src/Core/Storage/database.rst b/docs/src/Core/Storage/database.rst index 277cd8f..6d5f8ca 100644 --- a/docs/src/Core/Storage/database.rst +++ b/docs/src/Core/Storage/database.rst @@ -9,7 +9,7 @@ To create this file with a `.db` extension, a call to the ``new`` method is requ .. code-block:: python - from SSD.Core import Database + from SSD.Core.Storage import Database # Create a new Database object and a new storage file db = Database(database_dir='my_directory', @@ -28,7 +28,7 @@ Loading an existing *Database* is pretty similar as creating a new one, except t .. code-block:: python - from SSD.Core import Database + from SSD.Core.Storage import Database # Create a new Database object and load an exiting storage file db = Database(database_dir='my_directory', @@ -107,7 +107,7 @@ The following *Field* types are available: +--------------+--------------------------------------+---------------------------------------------------------------------------------------+ | ``bool`` | :guilabel:`bool` | `BooleanField `_ | +--------------+--------------------------------------+---------------------------------------------------------------------------------------+ - | ``ndarray`` | :guilabel:`import numpy.ndarray` | See ``AdaptiveDB/ExtendedFields.py`` | + | ``ndarray`` | :guilabel:`import numpy.ndarray` | Field class for storing numpy arrays. | +--------------+--------------------------------------+---------------------------------------------------------------------------------------+ | ``datetime`` | :guilabel:`import datetime.datetime` | `DateTimeField `_ | +--------------+--------------------------------------+---------------------------------------------------------------------------------------+ diff --git a/docs/src/Core/Storage/utils.rst b/docs/src/Core/Storage/utils.rst index 0e3e197..2abe09a 100644 --- a/docs/src/Core/Storage/utils.rst +++ b/docs/src/Core/Storage/utils.rst @@ -10,10 +10,10 @@ Each *Table* from all *Databases* will be duplicated in the new *Database*; in t .. code-block:: python - from SSD.Core import merge + from SSD.Core.Storage import merge - merge(database_names=['my_Database1', 'my_Database2'], - new_database_name='my_MergedDatabase', + merge(database_files=['my_Database1', 'my_Database2'], + new_database_file='my_MergedDatabase', remove_existing=True) """ >> DATABASE my_Database1.db @@ -51,7 +51,9 @@ Each *Table* from all *Databases* will be duplicated in the new *Database*; in t - _dt_ (DATETIME) (default) - my_Data (FLOAT) - >> Confirm new Database architecture ? (y/n): + >> Confirm new Database architecture ? (y/n): y + Proceeding... + Merge complete. """ @@ -63,9 +65,9 @@ Both methods require tuples defines as :guilabel:`(current_name, new_name)`: .. code-block:: python - from SSD.Core import rename_tables, rename_fields + from SSD.Core.Storage import rename_tables, rename_fields - rename_tables(database_name='my_Database', + rename_tables(database_file='my_Database', renamed_tables=[('my_StoringTable', 'my_NewStoringTable'), ('my_ExchangeTable', 'my_NewExchangeTable')]) """ >> DATABASE my_Database.db @@ -80,7 +82,7 @@ Both methods require tuples defines as :guilabel:`(current_name, new_name)`: - my_Data (FLOAT) """ - rename_fields(database_name='my_Database', + rename_fields(database_file='my_Database', table_name='my_NewStoringTable', renamed_fields=('my_Condition', 'my_Test')) """ @@ -104,7 +106,7 @@ The ``remove_tables`` and ``remove_fields`` tools allows users to remove *Tables .. code-block:: python - from SSD.Core import remove_tables, remove_fields + from SSD.Core.Storage import remove_tables, remove_fields rename_tables(database_name='my_Database', remove_tables='my_ExchangeTable') @@ -135,7 +137,7 @@ The ``export`` tool allows users to export a *Database* either in CSV format eit .. code-block:: python - from SSD.Core import export + from SSD.Core.Storage import export export(database_name='my_Database', exporter='csv', diff --git a/examples/Core/storage/README.md b/examples/Core/storage/README.md index bfc067e..9b8d051 100644 --- a/examples/Core/storage/README.md +++ b/examples/Core/storage/README.md @@ -4,10 +4,11 @@ This repository contains a few examples that give a great overviews of the **sto It is recommended to inspect and run the python scripts following this order: -* ``writingDB.py``: create a Database, create Tables with Fields, add data. -* ``readingDB.py``: load a Database, read data. -* ``updatingDB.py``: update entries. -* ``signalDB.py``: connect handlers to signals. +* ``write_db.py``: create a Database, create Tables with Fields, add data. +* ``read_db.py``: load a Database, read data. +* ``update_db.py``: update entries. +* ``signal_db.py``: connect handlers to signals. * ``foreignkeyDB.py``: manage relationship between Tables. +* ``utils_db.py``: illustrate the usage of the utils functions. -Run the examples using ``python3 .py``. \ No newline at end of file +Run the examples using ``python3 .py``. diff --git a/examples/Core/storage/foreignkeyDB.py b/examples/Core/storage/foreignkey_db.py similarity index 99% rename from examples/Core/storage/foreignkeyDB.py rename to examples/Core/storage/foreignkey_db.py index 2cf2e91..d63996c 100644 --- a/examples/Core/storage/foreignkeyDB.py +++ b/examples/Core/storage/foreignkey_db.py @@ -1,7 +1,7 @@ from numpy import ndarray, where from numpy.random import uniform -from SSD.Core import Database +from SSD.Core.Storage import Database # Create a new Database object and a new storage file db = Database(database_dir='my_databases', diff --git a/examples/Core/storage/readingDB.py b/examples/Core/storage/read_db.py similarity index 94% rename from examples/Core/storage/readingDB.py rename to examples/Core/storage/read_db.py index 096a9d3..f996fa6 100644 --- a/examples/Core/storage/readingDB.py +++ b/examples/Core/storage/read_db.py @@ -1,11 +1,11 @@ from os.path import exists, join from os import system -from SSD.Core import Database +from SSD.Core.Storage import Database # Assert DB existence if not exists(join('my_databases', 'database_1.db')) or not exists(join('my_databases', 'database_2.db')): - system('python3 writingDB.py') + system('python3 write_db.py') # Load an existing Database storage file diff --git a/examples/Core/storage/signalDB.py b/examples/Core/storage/signal_db.py similarity index 97% rename from examples/Core/storage/signalDB.py rename to examples/Core/storage/signal_db.py index f076dbf..dedb43b 100644 --- a/examples/Core/storage/signalDB.py +++ b/examples/Core/storage/signal_db.py @@ -1,7 +1,7 @@ from numpy import ndarray from numpy.random import uniform -from SSD.Core import Database +from SSD.Core.Storage import Database # Create a new Database object and a new storage file db = Database(database_dir='my_databases', diff --git a/examples/Core/storage/updatingDB.py b/examples/Core/storage/update_db.py similarity index 89% rename from examples/Core/storage/updatingDB.py rename to examples/Core/storage/update_db.py index b0918ea..57a79f1 100644 --- a/examples/Core/storage/updatingDB.py +++ b/examples/Core/storage/update_db.py @@ -3,12 +3,12 @@ from numpy import where from numpy.random import uniform -from SSD.Core import Database +from SSD.Core.Storage import Database # Assert DB existence if not exists(join('my_databases', 'database_1.db')) or not exists(join('my_databases', 'database_2.db')): - system('python3 writingDB.py') + system('python3 write_db.py') # Load an existing Database storage file @@ -19,7 +19,7 @@ # Updating data new_array = uniform(low=-1, high=1, size=(10,)) new_mean = new_array.mean() -new_positive = len(where(new_mean > 0)[0]) +new_positive = len(where(new_array > 0)[0]) print("\nBEFORE UPDATE:") print(db.get_line(table_name='RawData', line_id=1)) print(db.get_line(table_name='Stats', line_id=1)) diff --git a/examples/Core/storage/utils_db.py b/examples/Core/storage/utils_db.py new file mode 100644 index 0000000..d6941d7 --- /dev/null +++ b/examples/Core/storage/utils_db.py @@ -0,0 +1,45 @@ +import os +from os import chdir +from numpy.random import uniform + +from SSD.Core.Storage import Database, merge, rename_tables, rename_fields, remove_table, remove_field, export + + +# Create new Databases +for i in [1, 2]: + db = Database(database_dir='my_databases', + database_name=f'database_{i}').new(remove_existing=True) + db.create_table(table_name=f'Table_{i}', + storing_table=True, + fields=[('value', float), ('is_positive', bool)]) + new_values = [round(uniform(low=-1, high=1), 2) for _ in range(5)] + db.add_batch(table_name=f'Table_{i}', + batch={'value': new_values, + 'is_positive': [x > 0 for x in new_values]}) + db.close() +os.chdir('my_databases') + +# Merge Databases +merge(database_files=['database_1', 'database_2'], + new_database_file='database_3', + remove_existing=True) + +# Renaming Tables and Fields +rename_tables(database_file='database_3', + renamed_tables=('Table_1', 'Table')) +rename_fields(database_file='database_3', + table_name='Table', + renamed_fields=[('value', 'data'), ('is_positive', 'flag')]) + +# Remove Tables and Fields +remove_field(database_file='database_3', + table_name='Table_2', + fields='is_positive') +remove_table(database_file='database_3', + table_names='Table_2') + +# Export to JSON and CSV +export(database_file='database_3', + exporter='json') +export(database_file='database_3', + exporter='csv') diff --git a/examples/Core/storage/writingDB.py b/examples/Core/storage/write_db.py similarity index 98% rename from examples/Core/storage/writingDB.py rename to examples/Core/storage/write_db.py index af5c7cc..740682f 100644 --- a/examples/Core/storage/writingDB.py +++ b/examples/Core/storage/write_db.py @@ -1,7 +1,7 @@ from numpy import ndarray, where from numpy.random import uniform -from SSD.Core import Database +from SSD.Core.Storage import Database # Create a new Database object and a new storage file diff --git a/src/Core/Storage/Exporter.py b/src/Core/Storage/Exporter.py deleted file mode 100644 index 8594bd6..0000000 --- a/src/Core/Storage/Exporter.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import Union, Dict, Any -import json -import csv -from peewee import Query -from datetime import datetime -from numpy import ndarray - - -class Exporter: - - @classmethod - def export(cls, - filename: str, - query: Union[Dict[str, Any], Query]): - - pass - - -class ExporterJson(Exporter): - - @staticmethod - def default(o: Any): - - if isinstance(o, datetime): - return o.isoformat() - - elif isinstance(o, ndarray): - return o.tolist() - - @classmethod - def export(cls, - filename: str, - query: Union[Dict[str, Any], Query]): - - file = open(filename, 'w') - json.dump(query, file, default=cls.default) - file.close() - - -class ExporterCSV(Exporter): - - @classmethod - def export(cls, - filename: str, - query: Union[Dict[str, Any], Query]): - - with open(filename, 'w') as file: - writer = csv.writer(file) - t = query.execute() - t.initialize() - if getattr(t, 'columns', None): - writer.writerow([column for column in t.columns]) - for row in t: - writer.writerow(row) diff --git a/src/Core/Storage/__init__.py b/src/Core/Storage/__init__.py index 3a850d7..a4b6e38 100644 --- a/src/Core/Storage/__init__.py +++ b/src/Core/Storage/__init__.py @@ -1,3 +1,2 @@ -from .AdaptiveTable import AdaptiveTable -from .Database import Database +from .database import Database from .utils import merge, rename_tables, rename_fields, remove_table, remove_field, export diff --git a/src/Core/Storage/AdaptiveTable.py b/src/Core/Storage/adaptive_table.py similarity index 89% rename from src/Core/Storage/AdaptiveTable.py rename to src/Core/Storage/adaptive_table.py index 6b94928..4889009 100644 --- a/src/Core/Storage/AdaptiveTable.py +++ b/src/Core/Storage/adaptive_table.py @@ -3,9 +3,10 @@ from peewee import chunked from playhouse.signals import Model, pre_save, post_save from playhouse.migrate import migrate, SqliteMigrator, SqliteDatabase +from numpy import ndarray from datetime import datetime -from SSD.Core.Storage.ExtendedFields import NumpyField, ndarray +from SSD.Core.Storage.numpy_field import NumpyField class AdaptiveTable(Model): @@ -32,14 +33,12 @@ def database(cls) -> SqliteDatabase: return cls._meta.database @classmethod - def fields(cls, - only_names: bool = True) -> Union[List[str], Dict[str, Field]]: + def fields(cls, only_names: bool = True) -> Union[List[str], Dict[str, Field]]: return list(cls._meta.fields.keys()) if only_names else cls._meta.fields @classmethod - def connect(cls, - database: SqliteDatabase): + def connect(cls, database: SqliteDatabase) -> None: cls.bind(database) cls.database().create_tables([cls]) @@ -48,7 +47,7 @@ def connect(cls, def extend(cls, field_name: str, data_type: Type, - default_value: Any): + default_value: Any) -> None: migrator = SqliteMigrator(cls.database()) atts = {'null': True} @@ -67,7 +66,7 @@ def extend(cls, @classmethod def extend_fk(cls, model: Model, - field_name: str): + field_name: str) -> None: migrator = SqliteMigrator(cls.database()) field = ForeignKeyField(model=model, backref=field_name, null=True, field=model._meta.primary_key) @@ -77,7 +76,7 @@ def extend_fk(cls, @classmethod def rename_table(cls, old_table_name: str, - new_table_name: str): + new_table_name: str) -> None: migrator = SqliteMigrator(cls.database()) migrate(migrator.rename_table(old_table_name, new_table_name)) @@ -85,7 +84,7 @@ def rename_table(cls, @classmethod def rename_field(cls, old_field_name: str, - new_field_name: str): + new_field_name: str) -> None: migrator = SqliteMigrator(cls.database()) migrate(migrator.rename_column(cls._meta.name, old_field_name, new_field_name)) @@ -94,7 +93,7 @@ def rename_field(cls, @classmethod def remove_field(cls, - field_name: str): + field_name: str) -> None: migrator = SqliteMigrator(cls.database()) migrate(migrator.drop_column(cls._meta.name, field_name)) @@ -122,7 +121,7 @@ def description(cls, def add_data(cls, fields_names: List[str], fields_values: List[Any], - batched: bool = False): + batched: bool = False) -> Union[int, List[int]]: pass @@ -134,7 +133,7 @@ class StoringTable(AdaptiveTable): def add_data(cls, fields_names: List[str], fields_values: List[Any], - batched: bool = False): + batched: bool = False) -> Union[int, List[int]]: if not batched: line = cls(**dict(zip(fields_names, fields_values))) @@ -159,7 +158,7 @@ class ExchangeTable(AdaptiveTable): def add_data(cls, fields_names: List[str], fields_values: List[Any], - batched: bool = False): + batched: bool = False) -> Union[int, List[int]]: if not batched: cls.delete().execute() diff --git a/src/Core/Storage/Database.py b/src/Core/Storage/database.py similarity index 96% rename from src/Core/Storage/Database.py rename to src/Core/Storage/database.py index 5c9884d..4466a01 100644 --- a/src/Core/Storage/Database.py +++ b/src/Core/Storage/database.py @@ -2,14 +2,15 @@ from os import remove, mkdir from os.path import exists, join, sep, getsize from inspect import getmembers +from peewee import ForeignKeyField from playhouse.migrate import SqliteDatabase from playhouse.signals import Signal, pre_save, post_save from datetime import datetime from numpy import unique -from SSD.Core.Storage.AdaptiveTable import AdaptiveTable, StoringTable, ExchangeTable, ForeignKeyField -from SSD.Core.Storage.ExtendedPeewee import generate_models -from SSD.Core.Storage.Exporter import Exporter, ExporterJson, ExporterCSV +from SSD.Core.Storage.adaptive_table import AdaptiveTable, StoringTable, ExchangeTable +from SSD.Core.Storage.peewee_extension import generate_models +from SSD.Core.Storage.exporter import Exporter FieldType = Union[Tuple[str, Type], Tuple[str, Type, Any], Tuple[str, str]] @@ -36,11 +37,9 @@ def __init__(self, self.__tables: Dict[str, type(AdaptiveTable)] = {} self.__fk: Dict[str, Dict[str, str]] = {} self.__signals: List[Tuple[str, Signal, str, Callable, str]] = [] - self.__exporters: Dict[str, Tuple[Type[Exporter], str]] = {'json': (ExporterJson, 'json'), - 'csv': (ExporterCSV, 'csv')} @staticmethod - def make_name(table_name: str): + def make_name(table_name: str) -> str: """ Harmonize the Table names. @@ -49,8 +48,7 @@ def make_name(table_name: str): return table_name[0] + table_name[1:].lower() if len(table_name) > 1 else table_name - def new(self, - remove_existing: bool = False): + def new(self, remove_existing: bool = False) -> 'Database': """ Create a new Database file. @@ -77,8 +75,7 @@ def new(self, self.__database = SqliteDatabase(database_path) return self - def load(self, - show_architecture: bool = False): + def load(self, show_architecture: bool = False) -> 'Database': """ Load an existing Database file. @@ -118,7 +115,7 @@ def load(self, return self - def get_path(self): + def get_path(self) -> Tuple[str, str]: """ Access the Database file path. """ @@ -744,20 +741,25 @@ def remove_field(self, def export(self, exporter: str, filename: str, - tables: Optional[Union[str, List[str]]] = None): + tables: Optional[Union[str, List[str]]] = None) -> None: + """ + Export the Database to a CSV or JSON file. + + :param exporter: Exporter type ('json' or 'csv'). + :param filename: Exported filename. + :param tables: Tables to export. + """ # Check exporter format exporter = exporter.lower() - if exporter not in self.__exporters: - raise ValueError(f"Unknown exporter with name {exporter}. " - f"Available exporters are {self.__exporters.keys()}.") + if exporter not in ['json', 'csv']: + raise ValueError(f"Unknown exporter with name {exporter}. Available exporters are ['json', 'csv'].") # Set good file extension file_path = filename.split(sep) file_name = file_path.pop(-1) file_name = file_name if len(file_name.split('.')) == 1 else file_name.split('.')[0] filename = join(*file_path[:-1], file_name) - extension = self.__exporters[exporter][1] # Get the tables to export tables = self.get_tables() if tables is None else tables @@ -769,11 +771,10 @@ def export(self, # Export each table # Todo: see 'at once' version for table in tables: - _filename = filename + f'_{table}.{extension}' + _filename = filename + f'_{table}.{exporter}' if exporter == 'json': - query = self.get_lines(table_name=table, - batched=True) + query = self.get_lines(table_name=table, batched=True) + Exporter.export_json(filename=_filename, query=query) else: query = self.__tables[table].select().tuples() - self.__exporters[exporter][0].export(filename=_filename, - query=query) + Exporter.export_csv(filename=_filename, query=query) diff --git a/src/Core/Storage/exporter.py b/src/Core/Storage/exporter.py new file mode 100644 index 0000000..cff0f3f --- /dev/null +++ b/src/Core/Storage/exporter.py @@ -0,0 +1,39 @@ +from typing import Union, Dict, Any +import json +import csv +from peewee import Query +from datetime import datetime +from numpy import ndarray + + +def default_format(o: Any): + if isinstance(o, datetime): + return o.isoformat() + elif isinstance(o, ndarray): + return o.tolist() + + +class Exporter: + + @classmethod + def export_json(cls, + filename: str, + query: Union[Dict[str, Any], Query]): + + file = open(filename, 'w') + json.dump(query, file, default=default_format) + file.close() + + @classmethod + def export_csv(cls, + filename: str, + query: Union[Dict[str, Any], Query]): + + with open(filename, 'w') as file: + writer = csv.writer(file) + t = query.execute() + t.initialize() + if getattr(t, 'columns', None): + writer.writerow([column for column in t.columns]) + for row in t: + writer.writerow(row) diff --git a/src/Core/Storage/ExtendedFields.py b/src/Core/Storage/numpy_field.py similarity index 66% rename from src/Core/Storage/ExtendedFields.py rename to src/Core/Storage/numpy_field.py index 5e2a546..150e1cd 100644 --- a/src/Core/Storage/ExtendedFields.py +++ b/src/Core/Storage/numpy_field.py @@ -6,12 +6,8 @@ class NumpyField(Field): field_type = 'NUMPY' - def db_value(self, - value: ndarray): - + def db_value(self, value: ndarray): return value if value is None else value.dumps() - def python_value(self, - value: bytes): - + def python_value(self, value: bytes): return value if value is None else loads(value) diff --git a/src/Core/Storage/ExtendedPeewee.py b/src/Core/Storage/peewee_extension.py similarity index 79% rename from src/Core/Storage/ExtendedPeewee.py rename to src/Core/Storage/peewee_extension.py index 74ab0a6..36f2638 100644 --- a/src/Core/Storage/ExtendedPeewee.py +++ b/src/Core/Storage/peewee_extension.py @@ -2,45 +2,43 @@ from playhouse.reflection import Introspector, SqliteDatabase, SqliteMetadata, UnknownField import warnings -from SSD.Core.Storage.AdaptiveTable import StoringTable, ExchangeTable -from SSD.Core.Storage.ExtendedFields import NumpyField +from SSD.Core.Storage.adaptive_table import StoringTable, ExchangeTable +from SSD.Core.Storage.numpy_field import NumpyField def generate_models(database, schema=None, **options): - # Use extended inspector - introspector = _ExtendedIntrospector.from_database(database, schema=schema) + + # EXTENSION: Use extended inspector + introspector = ExtendedIntrospector.from_database(database, schema=schema) return introspector.generate_models(**options) -class _ExtendedSqliteMetadata(SqliteMetadata): +class ExtendedSqliteMetadata(SqliteMetadata): def __init__(self, database): super().__init__(database) - # Extend the columns mapper + # EXTENSION: Extend the columns mapper with the new NumpyField self.column_map['numpy'] = NumpyField -class _ExtendedIntrospector(Introspector): +class ExtendedIntrospector(Introspector): @classmethod def from_database(cls, database, schema=None): if isinstance(database, SqliteDatabase): - # Use extended metadata class - metadata = _ExtendedSqliteMetadata(database) + # EXTENSION: Use extended metadata class + metadata = ExtendedSqliteMetadata(database) return cls(metadata, schema=schema) return Introspector.from_database(database, schema) - def generate_models(self, skip_invalid=False, table_names=None, - literal_column_names=False, bare_fields=False, + def generate_models(self, skip_invalid=False, table_names=None, literal_column_names=False, bare_fields=False, include_views=False): - database = self.introspect(table_names, literal_column_names, - include_views) + database = self.introspect(table_names, literal_column_names, include_views) models = {} pending = set() def _create_model(table, models): - pending.add(table) for foreign_key in database.foreign_keys[table]: dest = foreign_key.dest_table @@ -68,7 +66,11 @@ class Meta: # Fix models with multi-column primary keys. composite_key = False if len(primary_keys) == 0: - primary_keys = columns.keys() + if 'id' not in columns: + Meta.primary_key = False + else: + primary_keys = columns.keys() + if len(primary_keys) > 1: Meta.primary_key = CompositeKey(*[ field.name for col, field in columns.items() @@ -112,14 +114,16 @@ class Meta: constraint = SQL('DEFAULT %s' % column.default) params['constraints'] = [constraint] - if column_name in column_indexes and not \ - column.is_primary_key(): - if column_indexes[column_name]: - params['unique'] = True - elif not column.is_foreign_key(): - params['index'] = True + if not column.is_primary_key(): + if column_name in column_indexes: + if column_indexes[column_name]: + params['unique'] = True + elif not column.is_foreign_key(): + params['index'] = True + else: + params['index'] = False - attrs[column_name] = FieldClass(**params) + attrs[column.name] = FieldClass(**params) # EXTENSION: BaseModel must inherit from our adaptive models class BaseModel(ExchangeTable if '_dt_' in database.columns[table] else StoringTable): diff --git a/src/Core/Storage/utils.py b/src/Core/Storage/utils.py index 6a203a6..9a1b8ee 100644 --- a/src/Core/Storage/utils.py +++ b/src/Core/Storage/utils.py @@ -1,26 +1,26 @@ from typing import List, Tuple, Union, Optional -from SSD.Core.Storage.Database import Database -from SSD.Core.Storage.AdaptiveTable import AdaptiveTable +from SSD.Core.Storage.adaptive_table import AdaptiveTable +from SSD.Core.Storage.database import Database -def merge(database_names: List[str], - new_database_name: str = 'merged', +def merge(database_files: List[str], + new_database_file: str = 'merged', remove_existing: bool = False): """ Merge Databases in a new Database. - :param database_names: List of Databases files. - :param new_database_name: Name of the new Database. + :param database_files: List of Databases files. + :param new_database_file: Name of the new Database. :param remove_existing: If True, Database file with name 'new_database_name' will be overwritten. """ # Load working Databases - databases = [Database(database_name=database_name).load() for database_name in database_names] - merged_database = Database(database_name=new_database_name).new(remove_existing=remove_existing) + databases = [Database(database_name=database_name).load() for database_name in database_files] + merged_database = Database(database_name=new_database_file).new(remove_existing=remove_existing) # Create Tables with their Fields - print("Merging the following Databases...") + print("\nMerging the following Databases...") table_type = AdaptiveTable.table_type for db in databases: db.print_architecture() @@ -57,17 +57,17 @@ def merge(database_names: List[str], print("Merge complete.") -def rename_tables(database_name: str, +def rename_tables(database_file: str, renamed_tables: Union[Tuple[str, str], List[Tuple[str, str]]]): """ Rename Tables of the Database. - :param database_name: Database filename. + :param database_file: Database filename. :param renamed_tables: Tuple or list of tuples defined as ('old_name', 'new_name'). """ # Load the Database - db = Database(database_name=database_name).load() + db = Database(database_name=database_file).load() # Check the table names to change renamed_tables = [renamed_tables] if type(renamed_tables) != list else renamed_tables @@ -78,27 +78,28 @@ def rename_tables(database_name: str, f"Available Tables are {current_tables}.") # Renaming - print("Proceeding...") + print("\nRenaming Table(s). \nProceeding...") for (old_table_name, new_table_name) in renamed_tables: db.rename_table(table_name=old_table_name, new_table_name=new_table_name) db.print_architecture() + db.close() print("Renaming done.") -def rename_fields(database_name: str, +def rename_fields(database_file: str, table_name: str, renamed_fields: Union[Tuple[str, str], List[Tuple[str, str]]]): """ Rename Fields of a Table of the Database. - :param database_name: Database filename. + :param database_file: Database filename. :param table_name: Name of the Table. :param renamed_fields: Tuple or list of tuples defined as ('old_name', 'new_name'). """ # Load the Database - db = Database(database_name=database_name).load() + db = Database(database_name=database_file).load() # Check the fields to change renamed_fields = [renamed_fields] if type(renamed_fields) != list else renamed_fields @@ -110,50 +111,51 @@ def rename_fields(database_name: str, raise ValueError(f"The field '{old_field_name} is not in the list of available fields: {current_fields}") # Renaming - print("Proceeding...") + print("\nRenaming Field(s). \nProceeding...") for (old_field_name, new_field_name) in renamed_fields: db.rename_field(table_name=table_name, field_name=old_field_name, new_field_name=new_field_name) db.print_architecture() + db.close() print("Renaming done.") - print(db.get_fields('Training')) -def remove_table(database_name: str, +def remove_table(database_file: str, table_names: Union[str, List[str]]): """ Remove Tables of the Database. - :param database_name: Database filename. + :param database_file: Database filename. :param table_names: Table(s) to remove from the Database. """ # Load the Database - db = Database(database_name=database_name).load() + db = Database(database_name=database_file).load() table_names = [table_names] if type(table_names) != list else table_names # Removing - print("Proceeding...") + print("\nRemoving Table(s). \nProceeding...") for table_name in table_names: db.remove_table(table_name=table_name) db.print_architecture() + db.close() print("Removing done.") -def remove_field(database_name: str, +def remove_field(database_file: str, table_name: str, fields: Union[str, List[str]]): """ Remove Fields of a Table of the Database. - :param database_name: Database filename. + :param database_file: Database filename. :param table_name: Name of the Table. :param fields: Field(s) to remove from the Table. """ # Load the Database - db = Database(database_name=database_name).load() + db = Database(database_name=database_file).load() # Check the fields to remove fields = [fields] if type(fields) != list else fields @@ -165,7 +167,7 @@ def remove_field(database_name: str, raise ValueError(f"The field '{field} is not in the list of available fields: {current_fields}") # Removing - print("Proceeding...") + print("\nRemoving Filed(s). \nProceeding...") for field in fields: db.remove_field(table_name=table_name, field_name=field) @@ -173,13 +175,20 @@ def remove_field(database_name: str, print("Removing done.") -def export(database_name: str, +def export(database_file: str, exporter: str, - filename: Optional[str] = None, - remove_existing: bool = False): + filename: Optional[str] = None) -> None: + """ + Export the Database file to CSV or JSON formats. + + :param database_file: Database filename. + :param exporter: Exporter type (either 'csv' or 'json'). + :param filename: Exported filename. + """ # Load the Database - db = Database(database_name=database_name).load() + db = Database(database_name=database_file).load() + # Export to file db.export(exporter=exporter, - filename=filename) + filename=filename if filename is not None else 'export') diff --git a/src/Core/__init__.py b/src/Core/__init__.py index 46b2f7d..e69de29 100644 --- a/src/Core/__init__.py +++ b/src/Core/__init__.py @@ -1,2 +0,0 @@ -from .Rendering import * -from .Storage import * diff --git a/src/cli.py b/src/cli.py index 5d6a80b..f49c237 100644 --- a/src/cli.py +++ b/src/cli.py @@ -131,14 +131,16 @@ def execute_cli(): clean_examples_dir() return - examples = {'visualization': 'Core.rendering.visualization.py', + examples = {'write': 'Core.storage.write_db.py', + 'read': 'Core.storage.read_db.py', + 'update': 'Core.storage.update_db.py', + 'signal': 'Core.storage.signal_db.py', + 'foreignkey': 'Core.storage.foreignkey_db.py', + + 'visualization': 'Core.rendering.visualization.py', 'replay': 'Core.rendering.replay.py', 'offscreen': 'Core.rendering.offscreen.py', - 'foreignkey': 'Core.storage.foreignkeyDB.py', - 'reading': 'Core.storage.readingDB.py', - 'signal': 'Core.storage.signalDB.py', - 'updating': 'Core.storage.updatingDB.py', - 'writing': 'Core.storage.writingDB.py', + 'liver': ['SOFA.rendering.record.py', 'SOFA.rendering.replay.py'], 'caduceus': ['SOFA.rendering-offscreen.record.py', 'SOFA.rendering-offscreen.replay.py'], 'caduceus_store': 'SOFA.storage.record.py'} From aee7e46ca91983db5e1d515fa480ee35f561c660 Mon Sep 17 00:00:00 2001 From: robin Date: Thu, 1 Feb 2024 14:49:00 +0100 Subject: [PATCH 4/7] Update Core.Rendering package. --- examples/Core/rendering/README.md | 2 +- examples/Core/rendering/offscreen.py | 25 +-- examples/Core/rendering/replay.py | 10 +- examples/Core/rendering/several_factories.py | 15 +- .../rendering/several_factories_offscreen.py | 17 +- examples/Core/rendering/visualization.py | 36 ++-- src/Core/Rendering/__init__.py | 6 +- .../Rendering/backend/Open3d/Open3dReplay.py | 173 ------------------ src/Core/Rendering/backend/Vedo/VedoReplay.py | 107 ----------- src/Core/Rendering/backend/Vedo/utils.py | 17 -- .../backend/{BaseActor.py => base_object.py} | 38 ++-- .../backend/{BaseReplay.py => base_replay.py} | 102 +++++------ .../{BaseVisualizer.py => base_visualizer.py} | 112 ++++++------ .../backend/{Open3d => open3d}/__init__.py | 0 .../Open3dBaseApp.py => open3d/open3d_app.py} | 0 .../open3d_object.py} | 38 ++-- .../Rendering/backend/open3d/open3d_replay.py | 173 ++++++++++++++++++ .../open3d_visualizer.py} | 100 +++++----- .../backend/{Open3d => open3d}/utils.py | 0 .../{DataTables.py => rendering_table.py} | 39 ++-- .../backend/{Vedo => vedo}/__init__.py | 0 src/Core/Rendering/backend/vedo/utils.py | 17 ++ .../{Vedo/VedoActor.py => vedo/vedo_objet.py} | 52 +++--- .../Rendering/backend/vedo/vedo_replay.py | 111 +++++++++++ .../vedo_visualizer.py} | 84 ++++----- src/Core/Rendering/{Replay.py => replay.py} | 10 +- .../Rendering/{UserAPI.py => user_api.py} | 12 +- .../{Visualizer.py => visualizer.py} | 10 +- 28 files changed, 666 insertions(+), 640 deletions(-) delete mode 100644 src/Core/Rendering/backend/Open3d/Open3dReplay.py delete mode 100644 src/Core/Rendering/backend/Vedo/VedoReplay.py delete mode 100644 src/Core/Rendering/backend/Vedo/utils.py rename src/Core/Rendering/backend/{BaseActor.py => base_object.py} (78%) rename src/Core/Rendering/backend/{BaseReplay.py => base_replay.py} (52%) rename src/Core/Rendering/backend/{BaseVisualizer.py => base_visualizer.py} (69%) rename src/Core/Rendering/backend/{Open3d => open3d}/__init__.py (100%) rename src/Core/Rendering/backend/{Open3d/Open3dBaseApp.py => open3d/open3d_app.py} (100%) rename src/Core/Rendering/backend/{Open3d/Open3dActor.py => open3d/open3d_object.py} (93%) create mode 100644 src/Core/Rendering/backend/open3d/open3d_replay.py rename src/Core/Rendering/backend/{Open3d/Open3dVisualizer.py => open3d/open3d_visualizer.py} (65%) rename src/Core/Rendering/backend/{Open3d => open3d}/utils.py (100%) rename src/Core/Rendering/backend/{DataTables.py => rendering_table.py} (84%) rename src/Core/Rendering/backend/{Vedo => vedo}/__init__.py (100%) create mode 100644 src/Core/Rendering/backend/vedo/utils.py rename src/Core/Rendering/backend/{Vedo/VedoActor.py => vedo/vedo_objet.py} (86%) create mode 100644 src/Core/Rendering/backend/vedo/vedo_replay.py rename src/Core/Rendering/backend/{Vedo/VedoVisualizer.py => vedo/vedo_visualizer.py} (67%) rename src/Core/Rendering/{Replay.py => replay.py} (81%) rename src/Core/Rendering/{UserAPI.py => user_api.py} (98%) rename src/Core/Rendering/{Visualizer.py => visualizer.py} (93%) diff --git a/examples/Core/rendering/README.md b/examples/Core/rendering/README.md index 0f2606e..c5a406f 100644 --- a/examples/Core/rendering/README.md +++ b/examples/Core/rendering/README.md @@ -6,7 +6,7 @@ It is recommended to inspect and run the python scripts following this order: * ``visualization.py``: discover all visual object types (how to create and update them). * ``replay.py``: how to replay a simulation from a *Database*. -* ``_offscreen.py``: an example of offscreen rendering. +* ``offscreen.py``: an example of off-screen rendering. * ``several_factories.py``: how to manage several *Factories* with a single *Visualizer*. Run the examples using ``python3 .py `` with backend being either 'vedo' (by default) or 'open3d'. diff --git a/examples/Core/rendering/offscreen.py b/examples/Core/rendering/offscreen.py index 87b2378..6bc4093 100644 --- a/examples/Core/rendering/offscreen.py +++ b/examples/Core/rendering/offscreen.py @@ -2,29 +2,30 @@ from numpy.random import random from sys import argv -from SSD.Core import UserAPI, Replay +from SSD.Core.Rendering import UserAPI, Replay # 1. Create the visualization API -factory = UserAPI(database_name='offscreen', +factory = UserAPI(database_dir='my_databases', + database_name='offscreen', remove_existing=True) # 2. Create the object to render -armadillo = Mesh('armadillo.obj') +mesh = Mesh('armadillo.obj').compute_normals() # 3. Add objects to the Visualizer # 3.1. Add a Mesh (object_id = 0) -factory.add_mesh(positions=armadillo.points(), - cells=armadillo.cells(), +factory.add_mesh(positions=mesh.vertices, + cells=mesh.cells, at=0, alpha=0.8, c='orange6', wireframe=False, line_width=0.) # 3.2. Add a PointCloud (object_id = 1) -factory.add_points(positions=armadillo.points(), +factory.add_points(positions=mesh.vertices, at=1, point_size=5, - scalar_field=armadillo.points()[:, 1]) + scalar_field=mesh.vertices[:, 1]) # 4. Initialize the visualization factory.launch_visualizer(offscreen=True) @@ -32,18 +33,20 @@ # 5. Run a few steps print('Running offscreen...') for step in range(100): - updated_armadillo = armadillo.clone().points(armadillo.points() + 0.1 * random(armadillo.points().shape)) + updated_mesh = mesh.clone() + updated_mesh.vertices = mesh.vertices + 0.1 * random(mesh.vertices.shape) # 5.1. Update the Mesh factory.update_mesh(object_id=0, - positions=updated_armadillo.points()) + positions=updated_mesh.vertices) # 5.2. Update the PointCloud factory.update_points(object_id=1, - positions=updated_armadillo.points()) + positions=updated_mesh.vertices) # 5.3. Call a rendering step factory.render() # 6. Close the visualization, replay steps print('...then replay.') factory.close() -Replay(database_name='offscreen', +Replay(database_dir='my_databases', + database_name='offscreen', backend='vedo' if len(argv) == 1 else argv[1]).launch() diff --git a/examples/Core/rendering/replay.py b/examples/Core/rendering/replay.py index eac7bc0..bfd85bd 100644 --- a/examples/Core/rendering/replay.py +++ b/examples/Core/rendering/replay.py @@ -1,12 +1,14 @@ from os.path import exists +from os import system from sys import argv -from SSD.Core import Replay +from SSD.Core.Rendering import Replay # Check Database existence -if not exists('visualization.db'): - raise FileNotFoundError("You must create the Database using `python3 visualization.py` before to replay it.") +if not exists('my_databases/visualization.db'): + system('python3 visualization.py') # Launch replay -Replay(database_name='visualization', +Replay(database_dir='my_databases', + database_name='visualization', backend='vedo' if len(argv) == 1 else argv[1]).launch() diff --git a/examples/Core/rendering/several_factories.py b/examples/Core/rendering/several_factories.py index 7ea66d2..4e31f74 100644 --- a/examples/Core/rendering/several_factories.py +++ b/examples/Core/rendering/several_factories.py @@ -3,7 +3,8 @@ from threading import Thread from sys import argv -from SSD.Core import Database, UserAPI, Visualizer +from SSD.Core.Storage import Database +from SSD.Core.Rendering import UserAPI, Visualizer class Simulation: @@ -13,12 +14,12 @@ def __init__(self, idx_instance: int): # Create the Mesh object - self.armadillo = Mesh('armadillo.obj') + self.mesh = Mesh('armadillo.obj') # Create a Factory and add the Mesh object self.factory = UserAPI(database=database, idx_instance=idx_instance) - self.factory.add_mesh(positions=self.armadillo.points(), - cells=self.armadillo.cells(), + self.factory.add_mesh(positions=self.mesh.vertices, + cells=self.mesh.cells, at=idx_instance, c='orange3' if idx_instance == 0 else 'blue3') @@ -30,7 +31,7 @@ def connect_to_visualizer(self): def step(self): # Update the Mesh positions - updated_positions = self.armadillo.points() + 0.1 * random(self.armadillo.points().shape) + updated_positions = self.mesh.vertices + 0.1 * random(self.mesh.vertices.shape) self.factory.update_mesh(object_id=0, positions=updated_positions) self.factory.render() @@ -44,7 +45,8 @@ def close(self): if __name__ == '__main__': # 1. Create a new Database - db = Database(database_name='several_factories').new(remove_existing=True) + db = Database(database_dir='my_databases', + database_name='several_factories').new(remove_existing=True) # 2. Create several simulations nb_simu = 2 @@ -54,6 +56,7 @@ def close(self): # 3. Connect a single Visualizer to the Factories # 3.1. Create a new Visualizer Visualizer.launch(backend='vedo' if len(argv) == 1 else argv[1], + database_dir='my_databases', database_name='several_factories', nb_clients=nb_simu) # 3.2. Connect each Factory to the Visualizer (must be launched in thread) diff --git a/examples/Core/rendering/several_factories_offscreen.py b/examples/Core/rendering/several_factories_offscreen.py index c549c27..4859c53 100644 --- a/examples/Core/rendering/several_factories_offscreen.py +++ b/examples/Core/rendering/several_factories_offscreen.py @@ -2,7 +2,8 @@ from numpy.random import random from sys import argv -from SSD.Core import Database, UserAPI, Replay +from SSD.Core.Storage import Database +from SSD.Core.Rendering import UserAPI, Replay class Simulation: @@ -12,12 +13,12 @@ def __init__(self, idx_instance: int): # Create the Mesh object - self.armadillo = Mesh('armadillo.obj') + self.mesh = Mesh('armadillo.obj') # Create a Factory and add the Mesh object self.factory = UserAPI(database=database, idx_instance=idx_instance) - self.factory.add_mesh(positions=self.armadillo.points(), - cells=self.armadillo.cells(), + self.factory.add_mesh(positions=self.mesh.vertices, + cells=self.mesh.cells, at=idx_instance, c='orange3' if idx_instance == 0 else 'blue3') @@ -29,7 +30,7 @@ def connect_to_visualizer(self): def step(self): # Update the Mesh positions - updated_positions = self.armadillo.points() + 0.1 * random(self.armadillo.points().shape) + updated_positions = self.mesh.vertices + 0.1 * random(self.mesh.vertices.shape) self.factory.update_mesh(object_id=0, positions=updated_positions) self.factory.render() @@ -43,7 +44,8 @@ def close(self): if __name__ == '__main__': # 1. Create a new Database - db = Database(database_name='several_factories_offscreen').new(remove_existing=True) + db = Database(database_dir='my_databases', + database_name='several_factories_offscreen').new(remove_existing=True) # 2. Create several simulations nb_simu = 2 @@ -63,5 +65,6 @@ def close(self): # 5. Close the Factories & Replay steps for simu in simulations: simu.close() - Replay(database_name='several_factories_offscreen', + Replay(database_dir='my_databases', + database_name='several_factories_offscreen', backend='vedo' if len(argv) == 1 else argv[1]).launch() diff --git a/examples/Core/rendering/visualization.py b/examples/Core/rendering/visualization.py index 0eaab1c..8243414 100644 --- a/examples/Core/rendering/visualization.py +++ b/examples/Core/rendering/visualization.py @@ -3,32 +3,33 @@ from numpy.random import random from sys import argv -from SSD.Core import UserAPI +from SSD.Core.Rendering import UserAPI # 1. Create the visualization API -factory = UserAPI(database_name='visualization', +factory = UserAPI(database_dir='my_databases', + database_name='visualization', remove_existing=True) # 2. Create the object to render -armadillo = Mesh('armadillo.obj') +mesh = Mesh('armadillo.obj').compute_normals() # 3. Add objects to the Visualizer # 3.1. Add a Mesh (object_id = 0) -factory.add_mesh(positions=armadillo.points(), - cells=armadillo.cells(), +factory.add_mesh(positions=mesh.vertices, + cells=mesh.cells, at=0, alpha=0.8, c='orange6', wireframe=False, line_width=0.) # 3.2. Add a PointCloud (object_id = 1) -factory.add_points(positions=armadillo.points(), +factory.add_points(positions=mesh.vertices, at=1, point_size=5, - scalar_field=armadillo.points()[:, 1]) + scalar_field=mesh.vertices[:, 1]) # 3.3. Add Arrows (object_id = 2) -factory.add_arrows(positions=armadillo.points()[:100], - vectors=armadillo.normals()[:100], +factory.add_arrows(positions=mesh.vertices[:100], + vectors=mesh.vertex_normals[:100], at=2, res=15) # 3.4. Add Markers (object_id = 3) @@ -36,7 +37,7 @@ indices=arange(0, 10), at=3, size=1, - symbol='0') + symbol='*') # 3.5. Add Text (object_id = 4) factory.add_text(content='0', at=0, @@ -47,12 +48,13 @@ factory.launch_visualizer(backend='vedo' if len(argv) == 1 else argv[1], fps=20) -# 5. Run a few steps +# # 5. Run a few steps for step in range(100): - updated_armadillo = armadillo.clone().points(armadillo.points() + 0.1 * random(armadillo.points().shape)) + updated_mesh = mesh.clone() + updated_mesh.vertices = mesh.vertices + 0.1 * random(mesh.vertices.shape) # 5.1. Update the Mesh factory.update_mesh(object_id=0, - positions=updated_armadillo.points()) + positions=updated_mesh.vertices) if step == 50: factory.update_mesh(object_id=0, wireframe=True, @@ -60,16 +62,16 @@ line_width=2.) # 5.2. Update the PointCloud factory.update_points(object_id=1, - positions=updated_armadillo.points()) + positions=updated_mesh.vertices) if step == 50: factory.update_points(object_id=1, alpha=0.5, point_size=10, - scalar_field=armadillo.points()[:, 2]) + scalar_field=mesh.vertices[:, 2]) # 5.3. Update the Arrows factory.update_arrows(object_id=2, - positions=armadillo.points()[step:step + 100], - vectors=armadillo.normals()[step:step + 100]) + positions=mesh.vertices[step:step + 100], + vectors=mesh.vertex_normals[step:step + 100]) if step == 50: factory.update_arrows(object_id=2, c='red') diff --git a/src/Core/Rendering/__init__.py b/src/Core/Rendering/__init__.py index 5084b7c..5699af4 100644 --- a/src/Core/Rendering/__init__.py +++ b/src/Core/Rendering/__init__.py @@ -1,3 +1,3 @@ -from .Replay import Replay -from .Visualizer import Visualizer -from .UserAPI import UserAPI +from .replay import Replay +from .visualizer import Visualizer +from .user_api import UserAPI diff --git a/src/Core/Rendering/backend/Open3d/Open3dReplay.py b/src/Core/Rendering/backend/Open3d/Open3dReplay.py deleted file mode 100644 index 8fed7ce..0000000 --- a/src/Core/Rendering/backend/Open3d/Open3dReplay.py +++ /dev/null @@ -1,173 +0,0 @@ -from typing import Dict -from threading import Thread -from copy import copy -from time import time, sleep -import open3d as o3d - -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseReplay import BaseReplay -from SSD.Core.Rendering.backend.Open3d.Open3dBaseApp import BaseApp -from SSD.Core.Rendering.backend.Open3d.Open3dActor import Open3dActor - - -class Open3dReplay(BaseApp, BaseReplay): - - def __init__(self, - database: Database, - fps: int = 20): - """ - Replay a simulation from saved visual data with Open3D. - - :param database: Database to connect to. - :param fps: Max frame rate. - """ - - BaseReplay.__init__(self, - database=database, - fps=fps) - - self.actors: Dict[int, Dict[str, Open3dActor]] = {} - self.__current_group: int = 0 - self.__previous_group: int = 0 - self.__group_change: bool = False - self.__is_done = False - - def get_actor(self, - actor_name: str) -> Open3dActor: - """ - Get an Actor instance. - - :param actor_name: Name of the Actor. - """ - - group = self.groups[actor_name] - return self.actors[group][actor_name] - - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: - """ - Specific Actor creation instructions. - - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. - """ - - self.actors[actor_group][actor_name] = Open3dActor(actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) - if actor_type == 'Text': - self.additional_labels[actor_name] = self.actors[actor_group][actor_name] - - def launch_visualizer(self) -> None: - """ - Start the Visualizer: create all Actors and render them. - """ - - # 1. Init Visualizer instance - self._create_settings(len(self.actors)) - self._window.set_on_close(self.close) - - # 2. Add all Text - for actor in self.additional_labels.values(): - self._window.add_child(actor.instance) - actor.instance.visible = False - - # 3. Add geometries to the Visualizer - for actor in self.actors[self.__current_group].values(): - if actor.type == 'Text': - actor.instance.visible = True - else: - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) - bounds = self._scene.scene.bounding_box - self._scene.setup_camera(60, bounds, bounds.get_center()) - - # 4. Add Start button - bu = o3d.visualization.gui.Button('Start') - bu.set_on_clicked(self.reset) - separation_height = int(round(self._window.theme.font_size * 0.5)) - self._settings_panel.add_fixed(2 * separation_height) - self._settings_panel.add_child(bu) - - # 5. Launch mainloop - Thread(target=self.update_thread).start() - o3d.visualization.gui.Application.instance.run() - - def update_thread(self) -> None: - """ - Timer callback to update the rendering view. - """ - - while not self.__is_done: - self.step += 1 - process_time = time() - o3d.visualization.gui.Application.instance.post_to_main_thread(self._window, - self.__update_thread) - # Respect frame rate - dt = max(0., self.fps - (time() - process_time)) - sleep(dt) - - # Close the Visualizer - o3d.visualization.gui.Application.instance.quit() - - def __update_thread(self) -> None: - """ - Update the rendering view. - """ - - step = copy(self.step) - - # 1. If the group ID changed, change the visibility of Actors - if self.__group_change: - self.__group_change = False - # Remove previous group - for table_name in self.actors[self.__previous_group].keys(): - actor = self.get_actor(table_name) - if actor.type == 'Text': - actor.instance.visible = False - else: - self._scene.scene.remove_geometry(actor.name) - # Add new group - for table_name in self.actors[self.__current_group].keys(): - actor = self.get_actor(table_name) - if actor.type == 'Text': - actor.instance.visible = True - else: - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) - - # 2. Update all the Actors - if step < max(self.nb_sample.values()): - self.update_actors(step=step) - - def update_actor_backend(self, - actor: Open3dActor) -> None: - """ - Specific Actor update instructions. - - :param actor: Actor object. - """ - - actor.update() - if actor.group == self.__current_group: - if actor.type == 'Text': - pass - else: - self._scene.scene.remove_geometry(actor.name) - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) - - def close(self) -> None: - """ - Exit procedure of the Visualizer. - """ - - self.__is_done = True - - def _change_group(self, - index: int) -> None: - - if index != self.__current_group: - self.__previous_group = self.__current_group - self.__current_group = index - self.__group_change = True diff --git a/src/Core/Rendering/backend/Vedo/VedoReplay.py b/src/Core/Rendering/backend/Vedo/VedoReplay.py deleted file mode 100644 index 53dbd82..0000000 --- a/src/Core/Rendering/backend/Vedo/VedoReplay.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import Dict, Optional -from vedo import show, Plotter - -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseReplay import BaseReplay -from SSD.Core.Rendering.backend.Vedo.VedoActor import VedoActor -from SSD.Core.Rendering.backend.Vedo.utils import do_remove - - -class VedoReplay(BaseReplay): - - def __init__(self, - database: Database, - fps: int = 20): - """ - Replay a simulation from saved visual data with Vedo. - - :param database: Database to connect to. - :param fps: Max frame rate. - """ - - BaseReplay.__init__(self, - database=database, - fps=fps) - - self.actors: Dict[int, Dict[str, VedoActor]] = {} - self.__plotter: Optional[Plotter] = None - - def get_actor(self, - actor_name: str) -> VedoActor: - """ - Get an Actor instance. - - :param actor_name: Name of the Actor. - """ - - group = self.groups[actor_name] - return self.actors[group][actor_name] - - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: - """ - Specific Actor creation instructions. - - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. - """ - - self.actors[actor_group][actor_name] = VedoActor(actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) - - def launch_visualizer(self) -> None: - """ - Start the Visualizer: create all Actors and render them. - """ - - # 1. Create the list of actors to render - actors = [] - for group in sorted(self.actors.keys()): - actors.append([]) - for actor in self.actors[group].values(): - actors[-1].append(actor.instance) - - # 2. Create a non_interactive Plotter instance - self.__plotter = show(actors, - new=True, - N=len(actors), - sharecam=True, - interactive=False, - title='SSD', - axes=4) - - # 3. Add a timer callback and set the Plotter in interactive mode - self.__plotter.add_callback('Timer', self.update_thread) - self.__plotter.timer_callback('create', dt=int(self.fps * 1e3)) - self.__plotter.add_button(self.reset, states=['start']) - self.__plotter.interactive() - - def update_thread(self, _): - """ - Timer callback to update the rendering view. - """ - - self.step += 1 - if self.step < max(self.nb_sample.values()): - self.update_actors(step=self.step) - self.__plotter.render() - - def update_actor_backend(self, - actor: VedoActor) -> None: - """ - Specific Actor update instructions. - - :param actor: Actor object. - """ - - removed = False - if do_remove(actor, actor.updated_fields): - self.__plotter.remove(actor.instance, at=actor.group) - removed = True - actor.update() - if removed: - self.__plotter.add(actor.instance, at=actor.group) diff --git a/src/Core/Rendering/backend/Vedo/utils.py b/src/Core/Rendering/backend/Vedo/utils.py deleted file mode 100644 index 861b63d..0000000 --- a/src/Core/Rendering/backend/Vedo/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import List - -from SSD.Core.Rendering.backend.Vedo.VedoActor import VedoActor - - -def do_remove(actor: VedoActor, - data_keys: List[str]) -> bool: - - # Arrows must be re-added to update the vectors - if actor.type == 'Arrows' and ('positions' in data_keys or 'vectors' in data_keys): - return True - - # Markers must be re-added to update the positions - elif actor.type == 'Markers' and (len(data_keys) > 0 or 'positions' in actor.object_data['normal_to'].updated_fields): - return True - - return False diff --git a/src/Core/Rendering/backend/BaseActor.py b/src/Core/Rendering/backend/base_object.py similarity index 78% rename from src/Core/Rendering/backend/BaseActor.py rename to src/Core/Rendering/backend/base_object.py index 48f96b5..3f4690e 100644 --- a/src/Core/Rendering/backend/BaseActor.py +++ b/src/Core/Rendering/backend/base_object.py @@ -1,33 +1,33 @@ from typing import Dict, Any, Callable, Optional, List -class BaseActor: +class BaseObject: def __init__(self, - actor_type: str, - actor_name: str, - actor_group: int): + object_type: str, + object_name: str, + object_group: int): """ - The BaseActor is the common API for all backend Actors. + The BaseObject is the common API for all backend Objects. - :param actor_type: Type of the Actor. - :param actor_name: Name of the Actor. - :param actor_group: Index of the group of the Actor. + :param object_type: Type of the Object. + :param object_name: Name of the Object. + :param object_group: Index of the group of the Object. """ - # Actor identification - self.type = actor_type - self.name = actor_name - self.group = actor_group + # Object identification + self.type = object_type + self.name = object_name + self.group = object_group self.instance: Optional[Any] = None - # Actor data + # Object data self._object_data: Dict[str, Any] = {} self._cmap_data: Dict[str, Any] = {} self._updated_fields: List[str] = [] self.__updated_cmap: bool = False - # Actor specialization + # Object specialization self._create_object: Optional[Callable] = None self._update_object: Optional[Callable] = None self._cmap_object: Optional[Callable] = None @@ -47,9 +47,9 @@ def updated_fields(self) -> List[str]: def create(self, data: Dict[str, Any]) -> None: """ - Register data and create visual object. + Register data and create visual Object. - :param data: Initial object data. + :param data: Initial Object data. """ # Register & sort data @@ -71,7 +71,7 @@ def update_data(self, """ Register updated data. - :param data: Updated object data. + :param data: Updated Object data. """ # Sort data @@ -79,7 +79,7 @@ def update_data(self, cmap_data = cmap_data if 'scalar_field' in cmap_data and len(cmap_data['scalar_field']) > 0 else {} self.__updated_cmap = len(cmap_data.keys()) > 0 - # Register Actor data + # Register Object data self._updated_fields = [] for key, value in data.items(): self._object_data[key] = value @@ -89,7 +89,7 @@ def update_data(self, def update(self) -> None: """ - Update visual object. + Update visual Object. """ # Update the object diff --git a/src/Core/Rendering/backend/BaseReplay.py b/src/Core/Rendering/backend/base_replay.py similarity index 52% rename from src/Core/Rendering/backend/BaseReplay.py rename to src/Core/Rendering/backend/base_replay.py index 7cd07c5..36d9058 100644 --- a/src/Core/Rendering/backend/BaseReplay.py +++ b/src/Core/Rendering/backend/base_replay.py @@ -1,7 +1,7 @@ from typing import Dict -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseActor import BaseActor +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_object import BaseObject class BaseReplay: @@ -24,32 +24,32 @@ def __init__(self, self.nb_sample: Dict[str, int] = {} self.step: int = 1 - # Actors parameters - self.actors: Dict[int, Dict[str, BaseActor]] = {} + # Objects parameters + self.objects: Dict[int, Dict[str, BaseObject]] = {} self.groups: Dict[str, int] = {} - def get_actor(self, - actor_name: str) -> BaseActor: + def get_object(self, + object_name: str) -> BaseObject: """ - Get an Actor instance. + Get an Object instance. - :param actor_name: Name of the Actor. + :param object_name: Name of the Object. """ - group = self.groups[actor_name] - return self.actors[group][actor_name] + group = self.groups[object_name] + return self.objects[group][object_name] def start_replay(self) -> None: """ - Start the Replay: create all Actors and render them. + Start the Replay: create all Objects and render them. """ - self.create_actors() + self.create_objects() self.launch_visualizer() - def create_actors(self) -> None: + def create_objects(self) -> None: """ - Create an Actor object for each table in the Database. + Create an Object for each table in the Database. """ # 1. Sort the Table names per factory and per object indices @@ -66,7 +66,7 @@ def create_actors(self) -> None: for table_id in sorted(sorter[factory_id].keys()): sorted_table_names.append(sorter[factory_id][table_id]) - # 2. Retrieve visual data and create Actors (one Table per Actor) + # 2. Retrieve visual data and create Objects (one Table per Object) for table_name in sorted_table_names: # 2.1. Get the number of sample @@ -78,51 +78,51 @@ def create_actors(self) -> None: object_data.pop('id') group = object_data.pop('at') - # 2.3. Retrieve the good indexing of the Actor - actor_type = table_name.split('_')[0] - if group not in self.actors: - self.actors[group] = {} - - # 2.4. Create the Actor - self.create_actor_backend(actor_name=table_name, - actor_type=actor_type, - actor_group=group) - if actor_type == 'Markers': - object_data['normal_to'] = self.get_actor(object_data['normal_to']) - self.actors[group][table_name].create(data=object_data) + # 2.3. Retrieve the good indexing of the Object + object_type = table_name.split('_')[0] + if group not in self.objects: + self.objects[group] = {} + + # 2.4. Create the Object + self.create_object_backend(object_name=table_name, + object_type=object_type, + object_group=group) + if object_type == 'Markers': + object_data['normal_to'] = self.get_object(object_data['normal_to']) + self.objects[group][table_name].create(data=object_data) self.groups[table_name] = group - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: """ - Specific Actor creation instructions. + Specific Object creation instructions. - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. + :param object_name: Name of the Object. + :param object_type: Type of the Object. + :param object_group: Group of the Object. """ raise NotImplementedError def launch_visualizer(self) -> None: """ - Start the Visualizer: create all Actors and render them. + Start the Visualizer: create all Objects and render them. """ raise NotImplementedError - def update_actors(self, - step: int) -> None: + def update_objects(self, + step: int) -> None: """ - Update the Actors of a Factory. + Update the Objects of a Factory. :param step: Index of the current step. """ - for group in self.actors.keys(): - for table_name in self.actors[group].keys(): + for group in self.objects.keys(): + for table_name in self.objects[group].keys(): # Get the current step line in the Table object_data = self.database.get_line(table_name=table_name, @@ -130,22 +130,22 @@ def update_actors(self, object_data = dict(filter(lambda item: item[1] is not None, object_data.items())) object_data.pop('id') - # Update the Actor and its visualization + # Update the Object and its visualization if len(object_data.keys()) > 0 or 'Markers' in table_name: - actor = self.get_actor(table_name) + v_object = self.get_object(table_name) # Markers are updated if their associated object was updated - if actor.type == 'Markers' and 'normal_to' in object_data.keys(): - object_data['normal_to'] = self.get_actor(object_data['normal_to']) + if v_object.type == 'Markers' and 'normal_to' in object_data.keys(): + object_data['normal_to'] = self.get_object(object_data['normal_to']) # Update - actor.update_data(data=object_data) - self.update_actor_backend(actor=actor) + v_object.update_data(data=object_data) + self.update_object_backend(v_object=v_object) - def update_actor_backend(self, - actor: BaseActor) -> None: + def update_object_backend(self, + v_object: BaseObject) -> None: """ - Specific Actor update instructions. + Specific Object update instructions. - :param actor: Actor object. + :param v_object: Object object. """ raise NotImplementedError diff --git a/src/Core/Rendering/backend/BaseVisualizer.py b/src/Core/Rendering/backend/base_visualizer.py similarity index 69% rename from src/Core/Rendering/backend/BaseVisualizer.py rename to src/Core/Rendering/backend/base_visualizer.py index 3ba33be..faa021a 100644 --- a/src/Core/Rendering/backend/BaseVisualizer.py +++ b/src/Core/Rendering/backend/base_visualizer.py @@ -2,8 +2,8 @@ from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR from struct import unpack -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseActor import BaseActor +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_object import BaseObject class BaseVisualizer: @@ -36,8 +36,8 @@ def __init__(self, # Visualization parameters self.fps: float = 1 / min(max(1, abs(fps)), 50) - # Actors parameters - self.actors: Dict[int, Dict[str, BaseActor]] = {} + # Objects parameters + self.objects: Dict[int, Dict[str, BaseObject]] = {} self.groups: Dict[str, int] = {} self.factories: Dict[int, List[str]] = {} @@ -48,30 +48,30 @@ def __init__(self, self.requests: List[Tuple[int, int]] = [] @property - def database_path(self) -> Tuple[str]: + def database_path(self) -> Tuple[str, str]: return self.database.get_path() - def get_actor(self, - actor_name: str) -> BaseActor: + def get_object(self, + object_name: str) -> BaseObject: """ - Get an Actor instance. + Get an Object instance. - :param actor_name: Name of the Actor. + :param object_name: Name of the Object. """ - group = self.groups[actor_name] - return self.actors[group][actor_name] + group = self.groups[object_name] + return self.objects[group][object_name] def start_visualizer(self, nb_clients: int) -> None: """ - Start the Visualizer: create all Actors and render them. + Start the Visualizer: create all Objects and render them. :param nb_clients: Number of Factories to connect to. """ self.launch_server(nb_clients=nb_clients) - self.create_actors() + self.create_objects() self.launch_visualizer(nb_clients=nb_clients) def launch_server(self, @@ -92,7 +92,7 @@ def launch_server(self, clients = {} for _ in range(nb_clients): client, _ = self.server.accept() - client.settimeout(0.1) + # client.settimeout(0.1) idx_client: int = unpack('i', client.recv(4))[0] clients[idx_client] = client @@ -101,9 +101,9 @@ def launch_server(self, self.clients.append(clients[idx_client]) self.is_done.append(False) - def create_actors(self) -> None: + def create_objects(self) -> None: """ - Create an Actor object for each table in the Database. + Create an Object for each table in the Database. """ # 1. Sort the Table names per factory index and per object index @@ -120,7 +120,7 @@ def create_actors(self) -> None: for table_id in sorted(sorter[factory_id].keys()): sorted_table_names.append(sorter[factory_id][table_id]) - # 2. Retrieve visual data and create Actors (one Table per Actor) + # 2. Retrieve visual data and create Objects (one Table per Object) pre_groups = {} for table_name in sorted_table_names: @@ -129,49 +129,49 @@ def create_actors(self) -> None: object_data.pop('id') group = object_data.pop('at') - # 2.2. Retrieve the good indexing of Actors - actor_type, factory_id = table_name.split('_')[0:2] - if group not in self.actors: - self.actors[group] = {} + # 2.2. Retrieve the good indexing of Objects + object_type, factory_id = table_name.split('_')[0:2] + if group not in self.objects: + self.objects[group] = {} pre_groups[group] = [] if (factory_id := int(factory_id)) not in self.factories: self.factories[factory_id] = [] self.factories[factory_id].append(table_name) - # 2.3. Create the Actor - self.create_actor_backend(actor_name=table_name, - actor_type=actor_type, - actor_group=group) - if actor_type == 'Markers': - object_data['normal_to'] = self.get_actor(object_data['normal_to']) - self.actors[group][table_name].create(data=object_data) + # 2.3. Create the Object + self.create_object_backend(object_name=table_name, + object_type=object_type, + object_group=group) + if object_type == 'Markers': + object_data['normal_to'] = self.get_object(object_data['normal_to']) + self.objects[group][table_name].create(data=object_data) self.groups[table_name] = group pre_groups[group].append(table_name) # 3. Update the group values - for i, group in enumerate(sorted(self.actors.keys())): + for i, group in enumerate(sorted(self.objects.keys())): # 3.1. Update value in the Database if i != group: for table_name in pre_groups[group]: self.database.update(table_name=table_name, data={'at': i}) - # 3.2. Update value in the Actors - self.actors[i] = self.actors.pop(group) - for idx, actor in self.actors[i].items(): - actor.group = i + # 3.2. Update value in the Objects + self.objects[i] = self.objects.pop(group) + for idx, v_object in self.objects[i].items(): + v_object.group = i self.groups[idx] = i - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: """ - Specific Actor creation instructions. + Specific Object creation instructions. - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. + :param object_name: Name of the Object. + :param object_type: Type of the Object. + :param object_group: Group of the Object. """ raise NotImplementedError @@ -179,7 +179,7 @@ def create_actor_backend(self, def launch_visualizer(self, nb_clients: int) -> None: """ - Start the Visualizer: create all Actors and render them. + Start the Visualizer: create all Objects and render them. :param nb_clients: Number of Factories to connect to. """ @@ -208,11 +208,11 @@ def listen_client(self, except socket.timeout: pass - def update_actors(self, - step: int, - idx_factory: int) -> None: + def update_objects(self, + step: int, + idx_factory: int) -> None: """ - Update the Actors of a Factory. + Update the Objects of a Factory. :param step: Index of the current step. :param idx_factory: Index of the Factory to update. @@ -226,22 +226,22 @@ def update_actors(self, object_data = dict(filter(lambda item: item[1] is not None, object_data.items())) object_data.pop('id') - # Update the Actor and its visualization + # Update the Object and its visualization if len(object_data.keys()) > 0 or 'Markers' in table_name: - actor = self.get_actor(table_name) + v_object = self.get_object(table_name) # Markers are updated if their associated object was updated - if actor.type == 'Markers' and 'normal_to' in object_data.keys(): - object_data['normal_to'] = self.get_actor(object_data['normal_to']) + if v_object.type == 'Markers' and 'normal_to' in object_data.keys(): + object_data['normal_to'] = self.get_object(object_data['normal_to']) # Update - actor.update_data(data=object_data) - self.update_actor_backend(actor=actor) + v_object.update_data(data=object_data) + self.update_object_backend(v_object=v_object) - def update_actor_backend(self, - actor: BaseActor) -> None: + def update_object_backend(self, + v_object: BaseObject) -> None: """ - Specific Actor update instructions. + Specific Object update instructions. - :param actor: Actor object. + :param v_object: Visual Object. """ raise NotImplementedError diff --git a/src/Core/Rendering/backend/Open3d/__init__.py b/src/Core/Rendering/backend/open3d/__init__.py similarity index 100% rename from src/Core/Rendering/backend/Open3d/__init__.py rename to src/Core/Rendering/backend/open3d/__init__.py diff --git a/src/Core/Rendering/backend/Open3d/Open3dBaseApp.py b/src/Core/Rendering/backend/open3d/open3d_app.py similarity index 100% rename from src/Core/Rendering/backend/Open3d/Open3dBaseApp.py rename to src/Core/Rendering/backend/open3d/open3d_app.py diff --git a/src/Core/Rendering/backend/Open3d/Open3dActor.py b/src/Core/Rendering/backend/open3d/open3d_object.py similarity index 93% rename from src/Core/Rendering/backend/Open3d/Open3dActor.py rename to src/Core/Rendering/backend/open3d/open3d_object.py index cddca9e..c574011 100644 --- a/src/Core/Rendering/backend/Open3d/Open3dActor.py +++ b/src/Core/Rendering/backend/open3d/open3d_object.py @@ -8,35 +8,35 @@ from matplotlib.colors import Normalize from matplotlib.pyplot import get_cmap -from SSD.Core.Rendering.backend.BaseActor import BaseActor -from SSD.Core.Rendering.backend.Open3d.utils import get_rotation_matrix +from SSD.Core.Rendering.backend.base_object import BaseObject +from SSD.Core.Rendering.backend.open3d.utils import get_rotation_matrix -class Open3dActor(BaseActor): +class Open3dObject(BaseObject): def __init__(self, - actor_type: str, - actor_name: str, - actor_group: int): + object_type: str, + object_name: str, + object_group: int): """ - The Open3dActor is used to create and update Open3D object instances. + The Open3dObject is used to create and update Open3D object instances. - :param actor_type: Type of the Actor. - :param actor_name: Name of the Actor. - :param actor_group: Index of the group of the Actor. + :param object_type: Type of the Object. + :param object_name: Name of the Object. + :param object_group: Index of the group of the Object. """ - BaseActor.__init__(self, - actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) + BaseObject.__init__(self, + object_type=object_type, + object_name=object_name, + object_group=object_group) - # Actor information + # Object information self.instance: Optional[o3d.geometry.Geometry3D] = None self.material = o3d.visualization.rendering.MaterialRecord() self.utils: Optional[Any] = None - # Actor specialization + # Object specialization spec = {'Mesh': (self.__create_mesh, self.__update_mesh, self.__cmap_mesh), 'Points': (self.__create_points, self.__update_points, self.__cmap_points), 'Arrows': (self.__create_arrows, self.__update_arrows, self.__cmap_arrows), @@ -256,12 +256,12 @@ def __create_markers(self, # Create the Marker mesh vedo_marker = Marker(symbol=data['symbol'], - s=data['size']).orientation(newaxis=[1, 0, 0], rotation=90, rad=False) + s=data['size']).rotate(axis=[0, 1, 0], angle=90, rad=False) vedo_glyph = Glyph(mesh=positions, glyph=vedo_marker, orientation_array=orientations).triangulate() - self.instance = o3d.geometry.TriangleMesh(vertices=o3d.utility.Vector3dVector(vedo_glyph.points()), - triangles=o3d.utility.Vector3iVector(array(vedo_glyph.cells()))) + self.instance = o3d.geometry.TriangleMesh(vertices=o3d.utility.Vector3dVector(vedo_glyph.vertices), + triangles=o3d.utility.Vector3iVector(array(vedo_glyph.cells))) self.instance.compute_vertex_normals() def __update_markers(self, diff --git a/src/Core/Rendering/backend/open3d/open3d_replay.py b/src/Core/Rendering/backend/open3d/open3d_replay.py new file mode 100644 index 0000000..56da43f --- /dev/null +++ b/src/Core/Rendering/backend/open3d/open3d_replay.py @@ -0,0 +1,173 @@ +from typing import Dict +from threading import Thread +from copy import copy +from time import time, sleep +import open3d as o3d + +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_replay import BaseReplay +from SSD.Core.Rendering.backend.open3d.open3d_app import BaseApp +from SSD.Core.Rendering.backend.open3d.open3d_object import Open3dObject + + +class Open3dReplay(BaseApp, BaseReplay): + + def __init__(self, + database: Database, + fps: int = 20): + """ + Replay a simulation from saved visual data with Open3D. + + :param database: Database to connect to. + :param fps: Max frame rate. + """ + + BaseReplay.__init__(self, + database=database, + fps=fps) + + self.objects: Dict[int, Dict[str, Open3dObject]] = {} + self.__current_group: int = 0 + self.__previous_group: int = 0 + self.__group_change: bool = False + self.__is_done = False + + def get_object(self, + object_name: str) -> Open3dObject: + """ + Get an Object instance. + + :param object_name: Name of the Object. + """ + + group = self.groups[object_name] + return self.objects[group][object_name] + + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: + """ + Specific Object creation instructions. + + :param object_name: Name of the Object. + :param object_type: Type of the Object. + :param object_group: Group of the Object. + """ + + self.objects[object_group][object_name] = Open3dObject(object_type=object_type, + object_name=object_name, + object_group=object_group) + if object_type == 'Text': + self.additional_labels[object_name] = self.objects[object_group][object_name] + + def launch_visualizer(self) -> None: + """ + Start the Visualizer: create all Objects and render them. + """ + + # 1. Init Visualizer instance + self._create_settings(len(self.objects)) + self._window.set_on_close(self.close) + + # 2. Add all Text + for v_object in self.additional_labels.values(): + self._window.add_child(v_object.instance) + v_object.instance.visible = False + + # 3. Add geometries to the Visualizer + for v_object in self.objects[self.__current_group].values(): + if v_object.type == 'Text': + v_object.instance.visible = True + else: + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) + bounds = self._scene.scene.bounding_box + self._scene.setup_camera(60, bounds, bounds.get_center()) + + # 4. Add Start button + bu = o3d.visualization.gui.Button('Start') + bu.set_on_clicked(self.reset) + separation_height = int(round(self._window.theme.font_size * 0.5)) + self._settings_panel.add_fixed(2 * separation_height) + self._settings_panel.add_child(bu) + + # 5. Launch mainloop + Thread(target=self.update_thread).start() + o3d.visualization.gui.Application.instance.run() + + def update_thread(self) -> None: + """ + Timer callback to update the rendering view. + """ + + while not self.__is_done: + self.step += 1 + process_time = time() + o3d.visualization.gui.Application.instance.post_to_main_thread(self._window, + self.__update_thread) + # Respect frame rate + dt = max(0., self.fps - (time() - process_time)) + sleep(dt) + + # Close the Visualizer + o3d.visualization.gui.Application.instance.quit() + + def __update_thread(self) -> None: + """ + Update the rendering view. + """ + + step = copy(self.step) + + # 1. If the group ID changed, change the visibility of Objects + if self.__group_change: + self.__group_change = False + # Remove previous group + for table_name in self.objects[self.__previous_group].keys(): + v_object = self.get_object(table_name) + if v_object.type == 'Text': + v_object.instance.visible = False + else: + self._scene.scene.remove_geometry(v_object.name) + # Add new group + for table_name in self.objects[self.__current_group].keys(): + v_object = self.get_object(table_name) + if v_object.type == 'Text': + v_object.instance.visible = True + else: + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) + + # 2. Update all the Objects + if step < max(self.nb_sample.values()): + self.update_objects(step=step) + + def update_object_backend(self, + v_object: Open3dObject) -> None: + """ + Specific Object update instructions. + + :param v_object: Object object. + """ + + v_object.update() + if v_object.group == self.__current_group: + if v_object.type == 'Text': + pass + else: + self._scene.scene.remove_geometry(v_object.name) + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) + + def close(self) -> None: + """ + Exit procedure of the Visualizer. + """ + + self.__is_done = True + + def _change_group(self, + index: int) -> None: + + if index != self.__current_group: + self.__previous_group = self.__current_group + self.__current_group = index + self.__group_change = True diff --git a/src/Core/Rendering/backend/Open3d/Open3dVisualizer.py b/src/Core/Rendering/backend/open3d/open3d_visualizer.py similarity index 65% rename from src/Core/Rendering/backend/Open3d/Open3dVisualizer.py rename to src/Core/Rendering/backend/open3d/open3d_visualizer.py index 82220f9..44f5cdf 100644 --- a/src/Core/Rendering/backend/Open3d/Open3dVisualizer.py +++ b/src/Core/Rendering/backend/open3d/open3d_visualizer.py @@ -6,10 +6,10 @@ from time import time, sleep import open3d.visualization.gui as gui -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseVisualizer import BaseVisualizer -from SSD.Core.Rendering.backend.Open3d.Open3dActor import Open3dActor -from SSD.Core.Rendering.backend.Open3d.Open3dBaseApp import BaseApp +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_visualizer import BaseVisualizer +from SSD.Core.Rendering.backend.open3d.open3d_object import Open3dObject +from SSD.Core.Rendering.backend.open3d.open3d_app import BaseApp class Open3dVisualizer(BaseApp, BaseVisualizer): @@ -37,40 +37,40 @@ def __init__(self, remove_existing=remove_existing, fps=fps) - self.actors: Dict[int, Dict[str, Open3dActor]] = {} + self.objects: Dict[int, Dict[str, Open3dObject]] = {} self.__current_group: int = 0 self.__previous_group: int = 0 self.__group_change: bool = False self.__step: Tuple[int, int] = (1, 1) - def get_actor(self, - actor_name: str) -> Open3dActor: + def get_object(self, + object_name: str) -> Open3dObject: """ Get an Actor instance. - :param actor_name: Name of the Actor. + :param object_name: Name of the Actor. """ - group = self.groups[actor_name] - return self.actors[group][actor_name] + group = self.groups[object_name] + return self.objects[group][object_name] - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: """ Specific Actor creation instructions. - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. + :param object_name: Name of the Actor. + :param object_type: Type of the Actor. + :param object_group: Group of the Actor. """ - self.actors[actor_group][actor_name] = Open3dActor(actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) - if actor_type == 'Text': - self.additional_labels[actor_name] = self.actors[actor_group][actor_name] + self.objects[object_group][object_name] = Open3dObject(object_type=object_type, + object_name=object_name, + object_group=object_group) + if object_type == 'Text': + self.additional_labels[object_name] = self.objects[object_group][object_name] def launch_visualizer(self, nb_clients: int) -> None: @@ -81,20 +81,20 @@ def launch_visualizer(self, """ # 1. Init Visualizer instance - self._create_settings(len(self.actors)) + self._create_settings(len(self.objects)) self._window.set_on_close(self.exit) # 2 Add all Text - for actor in self.additional_labels.values(): - self._window.add_child(actor.instance) - actor.instance.visible = False + for v_object in self.additional_labels.values(): + self._window.add_child(v_object.instance) + v_object.instance.visible = False # 3. Add geometries to the Visualizer - for actor in self.actors[self.__current_group].values(): - if actor.type == 'Text': - actor.instance.visible = True + for v_object in self.objects[self.__current_group].values(): + if v_object.type == 'Text': + v_object.instance.visible = True else: - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) bounds = self._scene.scene.bounding_box self._scene.setup_camera(60, bounds, bounds.get_center()) @@ -163,40 +163,40 @@ def update_visualizer(self) -> None: if self.__group_change: self.__group_change = False # Remove previous group - for table_name in self.actors[self.__previous_group].keys(): - actor: Open3dActor = self.get_actor(table_name) - if actor.type == 'Text': - actor.instance.visible = False + for table_name in self.objects[self.__previous_group].keys(): + v_object: Open3dObject = self.get_object(table_name) + if v_object.type == 'Text': + v_object.instance.visible = False else: - self._scene.scene.remove_geometry(actor.name) + self._scene.scene.remove_geometry(v_object.name) # Add new group - for table_name in self.actors[self.__current_group].keys(): - actor = self.get_actor(table_name) - if actor.type == 'Text': - actor.instance.visible = True + for table_name in self.objects[self.__current_group].keys(): + v_object = self.get_object(table_name) + if v_object.type == 'Text': + v_object.instance.visible = True else: - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) # 2. Update all the Actors - self.update_actors(step=step, - idx_factory=idx_factory) + self.update_objects(step=step, + idx_factory=idx_factory) self.clients[idx_factory].send(b'done') - def update_actor_backend(self, - actor: Open3dActor) -> None: + def update_object_backend(self, + v_object: Open3dObject) -> None: """ Specific Actor update instructions. - :param actor: Actor object. + :param v_object: Actor object. """ - actor.update() - if actor.group == self.__current_group: - if actor.type == 'Text': + v_object.update() + if v_object.group == self.__current_group: + if v_object.type == 'Text': pass else: - self._scene.scene.remove_geometry(actor.name) - self._scene.scene.add_geometry(actor.name, actor.instance, actor.material) + self._scene.scene.remove_geometry(v_object.name) + self._scene.scene.add_geometry(v_object.name, v_object.instance, v_object.material) def exit(self, force_quit: bool = True) -> None: diff --git a/src/Core/Rendering/backend/Open3d/utils.py b/src/Core/Rendering/backend/open3d/utils.py similarity index 100% rename from src/Core/Rendering/backend/Open3d/utils.py rename to src/Core/Rendering/backend/open3d/utils.py diff --git a/src/Core/Rendering/backend/DataTables.py b/src/Core/Rendering/backend/rendering_table.py similarity index 84% rename from src/Core/Rendering/backend/DataTables.py rename to src/Core/Rendering/backend/rendering_table.py index e1e79a3..c65e801 100644 --- a/src/Core/Rendering/backend/DataTables.py +++ b/src/Core/Rendering/backend/rendering_table.py @@ -2,34 +2,26 @@ from numpy import array, ndarray from vedo.utils import is_sequence -from SSD.Core.Storage.Database import Database +from SSD.Core.Storage.database import Database -class DataTables: +class RenderingTable: def __init__(self, database: Database, table_name: str): """ - The DataTables are used to create a specific Table in the Database for each object type. + A RenderingTable is used to create a specific Table in the Database for each object type. :param database: Database to connect to. :param table_name: Name of the Table to create. Should be '__'. """ - + # Table information self.database: Database = database self.table_name: str = table_name self.table_type: str = table_name.split('_')[0] - # Select the good methods according to the Table type - create_columns = {'Mesh': self.__create_mesh_columns, - 'Points': self.__create_points_columns, - 'Arrows': self.__create_arrows_columns, - 'Markers': self.__create_markers_columns, - 'Text': self.__create_text_columns} - self.create_columns = create_columns[self.table_type] - def send_data(self, data: Dict[str, Any], update: bool) -> None: @@ -49,7 +41,19 @@ def send_data(self, self.database.add_data(table_name=self.table_name, data=self.__format_data(data=data)) - def __create_mesh_columns(self): + def create_columns(self) -> 'RenderingTable': + """ + Create the Fields of the Table for a specific object type. + """ + + return {'Mesh': self.__create_mesh_columns, + 'Points': self.__create_points_columns, + 'Arrows': self.__create_arrows_columns, + 'Markers': self.__create_markers_columns, + 'Text': self.__create_text_columns + }[self.table_type]() + + def __create_mesh_columns(self) -> 'RenderingTable': self.database.create_table(table_name=self.table_name, fields=[('positions', ndarray), @@ -64,7 +68,7 @@ def __create_mesh_columns(self): ]) return self - def __create_points_columns(self): + def __create_points_columns(self) -> 'RenderingTable': self.database.create_table(table_name=self.table_name, fields=[('positions', ndarray), @@ -77,7 +81,7 @@ def __create_points_columns(self): ]) return self - def __create_arrows_columns(self): + def __create_arrows_columns(self) -> 'RenderingTable': self.database.create_table(table_name=self.table_name, fields=[('positions', ndarray), @@ -91,7 +95,7 @@ def __create_arrows_columns(self): ]) return self - def __create_markers_columns(self): + def __create_markers_columns(self) -> 'RenderingTable': self.database.create_table(table_name=self.table_name, fields=[('normal_to', str), @@ -107,7 +111,7 @@ def __create_markers_columns(self): ]) return self - def __create_text_columns(self): + def __create_text_columns(self) -> 'RenderingTable': self.database.create_table(table_name=self.table_name, fields=[('content', str), @@ -142,4 +146,3 @@ def __parse_vector(cls, vec = [vec] vec = array(vec) return vec - \ No newline at end of file diff --git a/src/Core/Rendering/backend/Vedo/__init__.py b/src/Core/Rendering/backend/vedo/__init__.py similarity index 100% rename from src/Core/Rendering/backend/Vedo/__init__.py rename to src/Core/Rendering/backend/vedo/__init__.py diff --git a/src/Core/Rendering/backend/vedo/utils.py b/src/Core/Rendering/backend/vedo/utils.py new file mode 100644 index 0000000..2dc51c4 --- /dev/null +++ b/src/Core/Rendering/backend/vedo/utils.py @@ -0,0 +1,17 @@ +from typing import List + +from SSD.Core.Rendering.backend.vedo.vedo_objet import VedoObject + + +def do_remove(v_object: VedoObject, + data_keys: List[str]) -> bool: + + # Arrows must be re-added to update the vectors + if v_object.type == 'Arrows' and ('positions' in data_keys or 'vectors' in data_keys): + return True + + # Markers must be re-added to update the positions + elif v_object.type == 'Markers' and (len(data_keys) > 0 or 'positions' in v_object.object_data['normal_to'].updated_fields): + return True + + return False diff --git a/src/Core/Rendering/backend/Vedo/VedoActor.py b/src/Core/Rendering/backend/vedo/vedo_objet.py similarity index 86% rename from src/Core/Rendering/backend/Vedo/VedoActor.py rename to src/Core/Rendering/backend/vedo/vedo_objet.py index 6d7ffb7..8bdac94 100644 --- a/src/Core/Rendering/backend/Vedo/VedoActor.py +++ b/src/Core/Rendering/backend/vedo/vedo_objet.py @@ -3,32 +3,32 @@ from matplotlib.colors import Normalize from matplotlib.pyplot import get_cmap -from SSD.Core.Rendering.backend.BaseActor import BaseActor +from SSD.Core.Rendering.backend.base_object import BaseObject -class VedoActor(BaseActor): +class VedoObject(BaseObject): def __init__(self, - actor_type: str, - actor_name: str, - actor_group: int): + object_type: str, + object_name: str, + object_group: int): """ - The VedoActor is used to create and update Vedo object instances. + The VedoObject is used to create and update Vedo object instances. - :param actor_type: Type of the Actor. - :param actor_name: Name of the Actor. - :param actor_group: Index of the group of the Actor. + :param object_type: Type of the Object. + :param object_name: Name of the Object. + :param object_group: Index of the group of the Object. """ - BaseActor.__init__(self, - actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) + BaseObject.__init__(self, + object_type=object_type, + object_name=object_name, + object_group=object_group) - # Actor information + # Object information self.instance: Optional[Points] = None - # Actor specialization + # Object specialization spec = {'Mesh': (self.__create_mesh, self.__update_mesh), 'Points': (self.__create_points, self.__update_points), 'Arrows': (self.__create_arrows, self.__update_arrows), @@ -76,9 +76,11 @@ def __update_mesh(self, data: Dict[str, Any], updated_fields: List[str]) -> None: + self.instance: Mesh + # Update positions if 'positions' in updated_fields: - self.instance.points(data['positions']) + self.instance.vertices = data['positions'] # Update rendering style if 'line_width' in updated_fields or 'wireframe' in updated_fields: @@ -106,9 +108,11 @@ def __update_points(self, data: Dict[str, Any], updated_fields: List[str]) -> None: + self.instance: Points + # Update positions if 'positions' in updated_fields: - self.instance.points(data['positions']) + self.instance.vertices = data['positions'] # Update rendering style if 'point_size' in updated_fields: @@ -162,15 +166,15 @@ def __update_arrows(self, def __create_markers(self, data: Dict[str, Any]) -> None: - # Get position and orientation information from the associated Actor - normal_to = data['normal_to'] - positions = normal_to.instance.points()[data['indices']][0] - orientations = normal_to.instance.normals()[data['indices']][0] + # Get position and orientation information from the associated Object + normal_to: Mesh = data['normal_to'].instance + positions = normal_to.vertices[data['indices']][0] + orientations = normal_to.vertex_normals[data['indices']][0] # Create the Marker object marker = Marker(symbol=data['symbol'], s=data['size'], - filled=data['filled']).orientation(newaxis=[1, 0, 0], rotation=90, rad=False) + filled=data['filled']).rotate(axis=[0, 1, 0], angle=90, rad=False) # With a colormap, create a color vector if 'scalar_field' in data: @@ -194,7 +198,7 @@ def __update_markers(self, data: Dict[str, Any], updated_fields: List[str]) -> None: - # Re-create instance to update any change (from current Actor or from associated Actor's positions) + # Re-create instance to update any change (from current Object or from associated Object's positions) if len(updated_fields) > 0 or 'positions' in data['normal_to'].updated_fields: self._create_object(data) @@ -225,6 +229,8 @@ def __update_text(self, data: Dict[str, Any], updated_fields: List[str]) -> None: + self.instance: Text2D + # Update text content if 'content' in updated_fields: self.instance.text(data['content']) diff --git a/src/Core/Rendering/backend/vedo/vedo_replay.py b/src/Core/Rendering/backend/vedo/vedo_replay.py new file mode 100644 index 0000000..a8fcdc1 --- /dev/null +++ b/src/Core/Rendering/backend/vedo/vedo_replay.py @@ -0,0 +1,111 @@ +from typing import Dict, Optional +from vedo import show, Plotter + +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_replay import BaseReplay +from SSD.Core.Rendering.backend.vedo.vedo_objet import VedoObject +from SSD.Core.Rendering.backend.vedo.utils import do_remove + + +class VedoReplay(BaseReplay): + + def __init__(self, + database: Database, + fps: int = 20): + """ + Replay a simulation from saved visual data with Vedo. + + :param database: Database to connect to. + :param fps: Max frame rate. + """ + + BaseReplay.__init__(self, + database=database, + fps=fps) + + self.objects: Dict[int, Dict[str, VedoObject]] = {} + self.__plotter: Optional[Plotter] = None + + def get_object(self, + object_name: str) -> VedoObject: + """ + Get an Object instance. + + :param object_name: Name of the Object. + """ + + group = self.groups[object_name] + return self.objects[group][object_name] + + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: + """ + Specific Object creation instructions. + + :param object_name: Name of the Object. + :param object_type: Type of the Object. + :param object_group: Group of the Object. + """ + + self.objects[object_group][object_name] = VedoObject(object_type=object_type, + object_name=object_name, + object_group=object_group) + + def launch_visualizer(self) -> None: + """ + Start the Visualizer: create all Objects and render them. + """ + + # 1. Create the list of objects to render + objects = [] + for group in sorted(self.objects.keys()): + objects.append([]) + for v_object in self.objects[group].values(): + objects[-1].append(v_object.instance) + + # 2. Create a non_interactive Plotter instance + self.__plotter = show(objects, + new=True, + N=len(objects), + sharecam=True, + interactive=False, + title='SSD', + axes=4) + + # 3. Add a timer callback and set the Plotter in interactive mode + self.__plotter.add_callback('Timer', self.update_thread) + self.__plotter.timer_callback('create', dt=int(self.fps * 1e3)) + self.__plotter.add_button(self.__reset, states=['start']) + self.__plotter.interactive() + + def update_thread(self, _): + """ + Timer callback to update the rendering view. + """ + + self.step += 1 + if self.step < max(self.nb_sample.values()): + self.update_objects(step=self.step) + self.__plotter.render() + + def update_object_backend(self, + v_object: VedoObject) -> None: + """ + Specific Object update instructions. + + :param v_object: Object object. + """ + + removed = False + if do_remove(v_object, v_object.updated_fields): + self.__plotter.remove(v_object.instance, at=v_object.group) + removed = True + v_object.update() + if removed: + self.__plotter.add(v_object.instance, at=v_object.group) + + def __reset(self, obj, name): + + self.reset() diff --git a/src/Core/Rendering/backend/Vedo/VedoVisualizer.py b/src/Core/Rendering/backend/vedo/vedo_visualizer.py similarity index 67% rename from src/Core/Rendering/backend/Vedo/VedoVisualizer.py rename to src/Core/Rendering/backend/vedo/vedo_visualizer.py index 9c949ba..e82909c 100644 --- a/src/Core/Rendering/backend/Vedo/VedoVisualizer.py +++ b/src/Core/Rendering/backend/vedo/vedo_visualizer.py @@ -4,10 +4,10 @@ from vedo import show, Plotter from threading import Thread -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseVisualizer import BaseVisualizer -from SSD.Core.Rendering.backend.Vedo.VedoActor import VedoActor -from SSD.Core.Rendering.backend.Vedo.utils import do_remove +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_visualizer import BaseVisualizer +from SSD.Core.Rendering.backend.vedo.vedo_objet import VedoObject +from SSD.Core.Rendering.backend.vedo.utils import do_remove class VedoVisualizer(BaseVisualizer): @@ -19,7 +19,7 @@ def __init__(self, remove_existing: bool = False, fps: int = 20): """ - The VedoVisualizer is used to manage the creation, update and rendering of Vedo Actors. + The VedoVisualizer is used to manage the creation, update and rendering of Vedo Objects. :param database: Database to connect to. :param database_dir: Directory which contains the Database file (used if 'database' is not defined). @@ -35,55 +35,55 @@ def __init__(self, remove_existing=remove_existing, fps=fps) - self.actors: Dict[int, Dict[str, VedoActor]] = {} + self.objects: Dict[int, Dict[str, VedoObject]] = {} self.__plotter: Optional[Plotter] = None - def get_actor(self, - actor_name: str) -> VedoActor: + def get_object(self, + object_name: str) -> VedoObject: """ - Get an Actor instance. + Get an Object instance. - :param actor_name: Name of the Actor. + :param object_name: Name of the Object. """ - group = self.groups[actor_name] - return self.actors[group][actor_name] + group = self.groups[object_name] + return self.objects[group][object_name] - def create_actor_backend(self, - actor_name: str, - actor_type: str, - actor_group: int) -> None: + def create_object_backend(self, + object_name: str, + object_type: str, + object_group: int) -> None: """ - Specific Actor creation instructions. + Specific Object creation instructions. - :param actor_name: Name of the Actor. - :param actor_type: Type of the Actor. - :param actor_group: Group of the Actor. + :param object_name: Name of the Object. + :param object_type: Type of the Object. + :param object_group: Group of the Object. """ - self.actors[actor_group][actor_name] = VedoActor(actor_type=actor_type, - actor_name=actor_name, - actor_group=actor_group) + self.objects[object_group][object_name] = VedoObject(object_type=object_type, + object_name=object_name, + object_group=object_group) def launch_visualizer(self, nb_clients: int) -> None: """ - Start the Visualizer: create all Actors and render them. + Start the Visualizer: create all Objects and render them. :param nb_clients: Number of Factories to connect to. """ - # 1. Create the list of actors to render - actors = [] - for group in self.actors.keys(): - actors.append([]) - for actor in self.actors[group].values(): - actors[-1].append(actor.instance) + # 1. Create the list of objects to render + objects = [] + for group in self.objects.keys(): + objects.append([]) + for v_object in self.objects[group].values(): + objects[-1].append(v_object.instance) # 2. Create a non-interactive Plotter instance - self.__plotter = show(actors, + self.__plotter = show(objects, new=True, - N=len(actors), + N=len(objects), sharecam=True, interactive=False, title='SSD', @@ -147,25 +147,25 @@ def update_visualizer(self, :param idx_factory: Index of the Factory to update. """ - self.update_actors(step=step, - idx_factory=idx_factory) + self.update_objects(step=step, + idx_factory=idx_factory) self.__plotter.render() - def update_actor_backend(self, - actor: VedoActor) -> None: + def update_object_backend(self, + v_object: VedoObject) -> None: """ - Specific Actor update instructions. + Specific Object update instructions. - :param actor: Actor object. + :param v_object: Object object. """ removed = False - if do_remove(actor, actor.updated_fields): - self.__plotter.remove(actor.instance, at=actor.group) + if do_remove(v_object, v_object.updated_fields): + self.__plotter.remove(v_object.instance, at=v_object.group) removed = True - actor.update() + v_object.update() if removed: - self.__plotter.add(actor.instance, at=actor.group) + self.__plotter.add(v_object.instance, at=v_object.group) def exit(self, force_quit: bool = True) -> None: diff --git a/src/Core/Rendering/Replay.py b/src/Core/Rendering/replay.py similarity index 81% rename from src/Core/Rendering/Replay.py rename to src/Core/Rendering/replay.py index fea7a3a..6aa5d68 100644 --- a/src/Core/Rendering/Replay.py +++ b/src/Core/Rendering/replay.py @@ -1,5 +1,5 @@ -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseReplay import BaseReplay +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_replay import BaseReplay class Replay: @@ -28,15 +28,15 @@ def __init__(self, # Create the Visualizer self.__replay: BaseReplay if backend.lower() == 'vedo': - from SSD.Core.Rendering.backend.Vedo.VedoReplay import VedoReplay + from SSD.Core.Rendering.backend.vedo.vedo_replay import VedoReplay self.__replay = VedoReplay(database=database, fps=fps) else: - from SSD.Core.Rendering.backend.Open3d.Open3dReplay import Open3dReplay + from SSD.Core.Rendering.backend.open3d.open3d_replay import Open3dReplay self.__replay = Open3dReplay(database=database, fps=fps) - def launch(self): + def launch(self) -> None: """ Initialize the Visualizer: create all Actors and render them in a Plotter. """ diff --git a/src/Core/Rendering/UserAPI.py b/src/Core/Rendering/user_api.py similarity index 98% rename from src/Core/Rendering/UserAPI.py rename to src/Core/Rendering/user_api.py index 40d0600..24e142f 100644 --- a/src/Core/Rendering/UserAPI.py +++ b/src/Core/Rendering/user_api.py @@ -3,9 +3,9 @@ from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR from struct import pack -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.Visualizer import Visualizer -from SSD.Core.Rendering.backend.DataTables import DataTables +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.visualizer import Visualizer +from SSD.Core.Rendering.backend.rendering_table import RenderingTable class UserAPI: @@ -41,7 +41,7 @@ def __init__(self, self.__non_storing = non_storing # Information about all Tables - self.__tables: List[DataTables] = [] + self.__tables: List[RenderingTable] = [] self.__current_id: int = 0 self.__idx: int = idx_instance self.__step: int = 1 @@ -59,7 +59,7 @@ def get_database(self) -> Database: return self.__database - def get_database_path(self) -> Tuple[str]: + def get_database_path(self) -> Tuple[str, str]: """ Get the path to the Database. """ @@ -187,7 +187,7 @@ def __add_object(self, self.__current_id += 1 # Create the Table and register the object - table = DataTables(database=self.__database, table_name=table_name).create_columns() + table = RenderingTable(database=self.__database, table_name=table_name).create_columns() table.send_data(data=data, update=False) self.__tables.append(table) return self.__current_id - 1 diff --git a/src/Core/Rendering/Visualizer.py b/src/Core/Rendering/visualizer.py similarity index 93% rename from src/Core/Rendering/Visualizer.py rename to src/Core/Rendering/visualizer.py index b0e319e..9349130 100644 --- a/src/Core/Rendering/Visualizer.py +++ b/src/Core/Rendering/visualizer.py @@ -4,8 +4,8 @@ from sys import executable, argv from inspect import stack, getmodule -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.backend.BaseVisualizer import BaseVisualizer +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.backend.base_visualizer import BaseVisualizer class Visualizer: @@ -38,14 +38,14 @@ def __init__(self, # Create the Visualizer self.__visualizer: BaseVisualizer if backend.lower() == 'vedo': - from SSD.Core.Rendering.backend.Vedo.VedoVisualizer import VedoVisualizer + from SSD.Core.Rendering.backend.vedo.vedo_visualizer import VedoVisualizer self.__visualizer = VedoVisualizer(database=database, database_dir=database_dir, database_name=database_name, remove_existing=remove_existing, fps=fps) else: - from SSD.Core.Rendering.backend.Open3d.Open3dVisualizer import Open3dVisualizer + from SSD.Core.Rendering.backend.open3d.open3d_visualizer import Open3dVisualizer self.__visualizer = Open3dVisualizer(database=database, database_dir=database_dir, database_name=database_name, @@ -61,7 +61,7 @@ def get_database(self) -> Database: return self.__visualizer.database - def get_database_path(self) -> Tuple[str]: + def get_database_path(self) -> Tuple[str, str]: """ Get the path to the Database. """ From 4b774ef788fa77e38d5bcfe124cab2d9855e9c04 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 7 Feb 2024 17:21:42 +0100 Subject: [PATCH 5/7] Update Sofa package. --- examples/SOFA/rendering-offscreen/Caduceus.py | 24 ++++++++++++------- examples/SOFA/rendering-offscreen/plugins.txt | 20 ++++++++++++++++ examples/SOFA/rendering-offscreen/record.py | 2 +- examples/SOFA/rendering-offscreen/replay.py | 5 ++-- examples/SOFA/rendering/Liver.py | 24 ++++++++----------- examples/SOFA/rendering/plugins.txt | 13 ++++++++++ examples/SOFA/rendering/record.py | 2 +- examples/SOFA/rendering/replay.py | 5 ++-- examples/SOFA/storage/Caduceus.py | 22 ++++++++++------- examples/SOFA/storage/plugins.txt | 20 ++++++++++++++++ src/SOFA/Rendering/__init__.py | 6 ++--- src/SOFA/Rendering/{Replay.py => replay.py} | 14 +++++------ .../Rendering/{UserAPI.py => user_api.py} | 22 ++++++++--------- .../{Visualizer.py => visualizer.py} | 22 ++++++++--------- src/SOFA/Storage/__init__.py | 2 +- src/SOFA/Storage/{Database.py => database.py} | 12 +++++----- src/SOFA/__init__.py | 2 -- 17 files changed, 139 insertions(+), 78 deletions(-) create mode 100644 examples/SOFA/rendering-offscreen/plugins.txt create mode 100644 examples/SOFA/rendering/plugins.txt create mode 100644 examples/SOFA/storage/plugins.txt rename src/SOFA/Rendering/{Replay.py => replay.py} (60%) rename src/SOFA/Rendering/{UserAPI.py => user_api.py} (96%) rename src/SOFA/Rendering/{Visualizer.py => visualizer.py} (64%) rename src/SOFA/Storage/{Database.py => database.py} (94%) diff --git a/examples/SOFA/rendering-offscreen/Caduceus.py b/examples/SOFA/rendering-offscreen/Caduceus.py index 0408161..d6a61a9 100644 --- a/examples/SOFA/rendering-offscreen/Caduceus.py +++ b/examples/SOFA/rendering-offscreen/Caduceus.py @@ -1,7 +1,7 @@ from typing import Optional import Sofa -from SSD.SOFA import UserAPI +from SSD.SOFA.Rendering import UserAPI class Caduceus(Sofa.Core.Controller): @@ -17,37 +17,43 @@ def __init__(self, root, factory: Optional[UserAPI] = None, *args, **kwargs): # Root self.root.gravity.value = [0, -1000, 0] self.root.dt.value = 0.04 - required_plugins = ['Sofa.Component', 'Sofa.GL.Component'] + with open('plugins.txt', 'r') as file: + required_plugins = [plugin[:-1] if plugin.endswith('\n') else plugin for plugin in file.readlines() + if plugin != '\n'] self.root.addObject('RequiredPlugin', pluginName=required_plugins) self.root.addObject('VisualStyle', displayFlags='showVisual') self.root.addObject('FreeMotionAnimationLoop', parallelCollisionDetectionAndFreeMotion=True) - self.root.addObject('DefaultPipeline', depth=15, verbose=0, draw=0) + self.root.addObject('CollisionPipeline', depth=15, verbose=0, draw=0) self.root.addObject('BruteForceBroadPhase') self.root.addObject('BVHNarrowPhase') self.root.addObject('MinProximityIntersection', alarmDistance=1.5, contactDistance=1) - self.root.addObject('DefaultContactManager', response='FrictionContactConstraint') + self.root.addObject('CollisionResponse', response='FrictionContactConstraint') self.root.addObject('LCPConstraintSolver', tolerance=1e-3, maxIt=1000, initial_guess=False, build_lcp=False, printLog=0, mu=0.2) # Camera self.root.addObject('InteractiveCamera', position=[0, 30, 90], lookAt=[0, 30, 0]) self.root.addObject('LightManager') - self.root.addObject('SpotLight', position=[0, 80, 25], direction=[0, -1, -0.8], cutoff=30, exponent=1) - self.root.addObject('SpotLight', position=[0, 40, 100], direction=[0, 0, -1], cutoff=30, exponent=1) + self.root.addObject('SpotLight', name='light1', position=[0, 80, 25], direction=[0, -1, -0.8], cutoff=30, + exponent=1) + self.root.addObject('SpotLight', name='light2', position=[0, 40, 100], direction=[0, 0, -1], cutoff=30, + exponent=1) # Snake.Physics self.root.addChild('snake') self.root.snake.addObject('MeshOBJLoader', name='Snake', filename='mesh/snake_body.obj') self.root.snake.addObject('EulerImplicitSolver', rayleighMass=1, rayleighStiffness=0.03) + self.root.snake.addObject('MatrixLinearSystem', name='LinearSystem', template='CompressedRowSparseMatrixMat3x3') self.root.snake.addObject('CGLinearSolver', iterations=20, tolerance=1e-12, threshold=1e-18, - template='CompressedRowSparseMatrixMat3x3d') + template='CompressedRowSparseMatrixMat3x3d', linearSystem='@LinearSystem') self.root.snake.addObject('SparseGridRamificationTopology', name='Grid', src='@Snake', n=[4, 12, 3], nbVirtualFinerLevels=3, finestConnectivity=0) - self.root.snake.addObject('MechanicalObject', src='@Grid', scale=1, dy=2) + self.root.snake.addObject('MechanicalObject', name='GridMO', src='@Grid', scale=1, dy=2) self.root.snake.addObject('UniformMass', totalMass=1.) self.root.snake.addObject('HexahedronFEMForceField', youngModulus=30000, poissonRatio=0.3, method='large', updateStiffnessMatrix=False) - self.root.snake.addObject('UncoupledConstraintCorrection', useOdeSolverIntegrationFactors=False) + self.root.snake.addObject('UncoupledConstraintCorrection', defaultCompliance=184, + useOdeSolverIntegrationFactors=False) # Snake.Collision self.root.snake.addChild('collision') diff --git a/examples/SOFA/rendering-offscreen/plugins.txt b/examples/SOFA/rendering-offscreen/plugins.txt new file mode 100644 index 0000000..0b798b8 --- /dev/null +++ b/examples/SOFA/rendering-offscreen/plugins.txt @@ -0,0 +1,20 @@ +Sofa.Component.AnimationLoop +Sofa.Component.Collision.Detection.Algorithm +Sofa.Component.Collision.Detection.Intersection +Sofa.Component.Collision.Geometry +Sofa.Component.Collision.Response.Contact +Sofa.Component.Constraint.Lagrangian.Correction +Sofa.Component.Constraint.Lagrangian.Solver +Sofa.Component.IO.Mesh +Sofa.Component.LinearSolver.Iterative +Sofa.Component.LinearSystem +Sofa.Component.Mapping.Linear +Sofa.Component.Mass +Sofa.Component.ODESolver.Backward +Sofa.Component.SolidMechanics.FEM.Elastic +Sofa.Component.StateContainer +Sofa.Component.Topology.Container.Constant +Sofa.Component.Topology.Container.Grid +Sofa.Component.Visual +Sofa.GL.Component.Rendering3D +Sofa.GL.Component.Shader \ No newline at end of file diff --git a/examples/SOFA/rendering-offscreen/record.py b/examples/SOFA/rendering-offscreen/record.py index 46c511b..86ce252 100644 --- a/examples/SOFA/rendering-offscreen/record.py +++ b/examples/SOFA/rendering-offscreen/record.py @@ -1,6 +1,6 @@ import Sofa -from SSD.SOFA import UserAPI +from SSD.SOFA.Rendering import UserAPI from Caduceus import Caduceus USE_GUI = True diff --git a/examples/SOFA/rendering-offscreen/replay.py b/examples/SOFA/rendering-offscreen/replay.py index 0b6f93d..1924f84 100644 --- a/examples/SOFA/rendering-offscreen/replay.py +++ b/examples/SOFA/rendering-offscreen/replay.py @@ -1,11 +1,12 @@ from os.path import exists +from os import system -from SSD.SOFA import Replay +from SSD.SOFA.Rendering import Replay # Check Database existence if not exists('caduceus.db'): - raise FileNotFoundError("You must create the Database using `python3 record.py` before to replay it.") + system('python3 record.py') # Launch Visualizer Replay(database_name='caduceus', fps=50).launch() diff --git a/examples/SOFA/rendering/Liver.py b/examples/SOFA/rendering/Liver.py index ba7736f..5b68a6e 100644 --- a/examples/SOFA/rendering/Liver.py +++ b/examples/SOFA/rendering/Liver.py @@ -4,7 +4,7 @@ from numpy import zeros, array import Sofa -from SSD.SOFA import UserAPI +from SSD.SOFA.Rendering import UserAPI class Liver(Sofa.Core.Controller): @@ -20,14 +20,12 @@ def __init__(self, root, factory: Optional[UserAPI] = None, *args, **kwargs): # Root self.root.dt.value = 0.02 - required_plugins = ['Sofa.Component', 'Sofa.GL.Component'] + with open('plugins.txt', 'r') as file: + required_plugins = [plugin[:-1] if plugin.endswith('\n') else plugin for plugin in file.readlines() + if plugin != '\n'] self.root.addObject('RequiredPlugin', pluginName=required_plugins) self.root.addObject('VisualStyle', displayFlags='showVisual') - self.root.addObject('DefaultPipeline', verbose=0) - self.root.addObject('BruteForceBroadPhase') - self.root.addObject('BVHNarrowPhase') - self.root.addObject('DiscreteIntersection') - self.root.addObject('DefaultContactManager') + self.root.addObject('DefaultAnimationLoop') # Liver.Physics self.root.addChild('liver') @@ -37,19 +35,18 @@ def __init__(self, root, factory: Optional[UserAPI] = None, *args, **kwargs): self.root.liver.addObject('TetrahedronSetTopologyContainer', name='Grid', src='@Liver') self.root.liver.addObject('TetrahedronSetGeometryAlgorithms', template='Vec3d') self.root.liver.addObject('MechanicalObject', name='GridMO', src='@Liver') - self.root.liver.addObject('DiagonalMass', massDensity=1.) + self.root.liver.addObject('DiagonalMass', massDensity=0.1) self.root.liver.addObject('TetrahedralCorotationalFEMForceField', template='Vec3d', method='large', youngModulus=50000, poissonRatio=0.4, computeGlobalMatrix=False) - self.root.liver.addObject('FixedConstraint', indices=[3, 39, 64]) + self.root.liver.addObject('FixedProjectiveConstraint', indices=[3, 39, 64]) # Liver.Surface self.root.liver.addChild('surface') self.root.liver.surface.addObject('SphereLoader', name='Spheres', filename='mesh/liver.sph') self.root.liver.surface.addObject('MechanicalObject', name='SurfaceMO', position='@Spheres.position') - self.root.liver.surface.addObject('SphereCollisionModel', listRadius='@Spheres.listRadius') self.root.liver.surface.addObject('BarycentricMapping', input='@..', output='@.') self.cff = self.root.liver.surface.addObject('ConstantForceField', name='CFF', indices=[33], - force=[0., 0., 0.], showArrowSize=5e-4) + forces=[[0., 0., 0.]], showArrowSize=5e-4) # Liver.Visual self.root.liver.addChild('visual') @@ -91,10 +88,9 @@ def onAnimateBeginEvent(self, _): # Change the force value every 50 time steps if self.step % 50 == 0: - f = uniform(-1, 1, (3,)) + f = uniform(-1, 1, (1, 3)) f = (f / norm(f)) * 5e3 - self.cff.force.value = f - + self.cff.forces.value = f self.step += 1 def onAnimateEndEvent(self, _): diff --git a/examples/SOFA/rendering/plugins.txt b/examples/SOFA/rendering/plugins.txt new file mode 100644 index 0000000..5bfcd9d --- /dev/null +++ b/examples/SOFA/rendering/plugins.txt @@ -0,0 +1,13 @@ +Sofa.Component.Collision.Geometry +Sofa.Component.Constraint.Projective +Sofa.Component.IO.Mesh +Sofa.Component.LinearSolver.Iterative +Sofa.Component.Mapping.Linear +Sofa.Component.Mass +Sofa.Component.MechanicalLoad +Sofa.Component.ODESolver.Backward +Sofa.Component.SolidMechanics.FEM.Elastic +Sofa.Component.StateContainer +Sofa.Component.Topology.Container.Dynamic +Sofa.Component.Visual +Sofa.GL.Component.Rendering3D \ No newline at end of file diff --git a/examples/SOFA/rendering/record.py b/examples/SOFA/rendering/record.py index 184538d..4fe30f5 100644 --- a/examples/SOFA/rendering/record.py +++ b/examples/SOFA/rendering/record.py @@ -1,6 +1,6 @@ import Sofa -from SSD.SOFA import UserAPI +from SSD.SOFA.Rendering import UserAPI from Liver import Liver USE_GUI = True diff --git a/examples/SOFA/rendering/replay.py b/examples/SOFA/rendering/replay.py index 19988a8..9872653 100644 --- a/examples/SOFA/rendering/replay.py +++ b/examples/SOFA/rendering/replay.py @@ -1,11 +1,12 @@ from os.path import exists +from os import system -from SSD.SOFA import Replay +from SSD.SOFA.Rendering import Replay # Check Database existence if not exists('liver.db'): - raise FileNotFoundError("You must create the Database using `python3 record.py` before to replay it.") + system('python3 record.py') # Launch Visualizer Replay(database_name='liver').launch() diff --git a/examples/SOFA/storage/Caduceus.py b/examples/SOFA/storage/Caduceus.py index 7d771df..a74a3aa 100644 --- a/examples/SOFA/storage/Caduceus.py +++ b/examples/SOFA/storage/Caduceus.py @@ -1,7 +1,7 @@ from numpy import ndarray import Sofa -from SSD.SOFA import Database +from SSD.SOFA.Storage import Database class Caduceus(Sofa.Core.Controller): @@ -17,37 +17,43 @@ def __init__(self, root, database=False, *args, **kwargs): # Root self.root.gravity.value = [0, -1000, 0] self.root.dt.value = 0.04 - required_plugins = ['Sofa.Component', 'Sofa.GL.Component'] + with open('plugins.txt', 'r') as file: + required_plugins = [plugin[:-1] if plugin.endswith('\n') else plugin for plugin in file.readlines() + if plugin != '\n'] self.root.addObject('RequiredPlugin', pluginName=required_plugins) self.root.addObject('VisualStyle', displayFlags='showVisual') self.root.addObject('FreeMotionAnimationLoop', parallelCollisionDetectionAndFreeMotion=True) - self.root.addObject('DefaultPipeline', depth=15, verbose=0, draw=0) + self.root.addObject('CollisionPipeline', depth=15, verbose=0, draw=0) self.root.addObject('BruteForceBroadPhase') self.root.addObject('BVHNarrowPhase') self.root.addObject('MinProximityIntersection', alarmDistance=1.5, contactDistance=1) - self.root.addObject('DefaultContactManager', response='FrictionContactConstraint') + self.root.addObject('CollisionResponse', response='FrictionContactConstraint') self.root.addObject('LCPConstraintSolver', tolerance=1e-3, maxIt=1000, initial_guess=False, build_lcp=False, printLog=0, mu=0.2) # Camera self.root.addObject('InteractiveCamera', position=[0, 30, 90], lookAt=[0, 30, 0]) self.root.addObject('LightManager') - self.root.addObject('SpotLight', position=[0, 80, 25], direction=[0, -1, -0.8], cutoff=30, exponent=1) - self.root.addObject('SpotLight', position=[0, 40, 100], direction=[0, 0, -1], cutoff=30, exponent=1) + self.root.addObject('SpotLight', name='light1', position=[0, 80, 25], direction=[0, -1, -0.8], cutoff=30, + exponent=1) + self.root.addObject('SpotLight', name='light2', position=[0, 40, 100], direction=[0, 0, -1], cutoff=30, + exponent=1) # Snake.Physics self.root.addChild('snake') self.root.snake.addObject('MeshOBJLoader', name='Snake', filename='mesh/snake_body.obj') self.root.snake.addObject('EulerImplicitSolver', rayleighMass=1, rayleighStiffness=0.03) + self.root.snake.addObject('MatrixLinearSystem', name='LinearSystem', template='CompressedRowSparseMatrixMat3x3') self.root.snake.addObject('CGLinearSolver', iterations=20, tolerance=1e-12, threshold=1e-18, - template='CompressedRowSparseMatrixMat3x3d') + template='CompressedRowSparseMatrixMat3x3d', linearSystem='@LinearSystem') self.root.snake.addObject('SparseGridRamificationTopology', name='Grid', src='@Snake', n=[4, 12, 3], nbVirtualFinerLevels=3, finestConnectivity=0) self.root.snake.addObject('MechanicalObject', name='GridMO', src='@Grid', scale=1, dy=2) self.root.snake.addObject('UniformMass', totalMass=1.) self.root.snake.addObject('HexahedronFEMForceField', youngModulus=30000, poissonRatio=0.3, method='large', updateStiffnessMatrix=False) - self.root.snake.addObject('UncoupledConstraintCorrection', useOdeSolverIntegrationFactors=False) + self.root.snake.addObject('UncoupledConstraintCorrection', defaultCompliance=184, + useOdeSolverIntegrationFactors=False) # Snake.Collision self.root.snake.addChild('collision') diff --git a/examples/SOFA/storage/plugins.txt b/examples/SOFA/storage/plugins.txt new file mode 100644 index 0000000..45b1def --- /dev/null +++ b/examples/SOFA/storage/plugins.txt @@ -0,0 +1,20 @@ +Sofa.Component.AnimationLoop +Sofa.Component.Collision.Detection.Algorithm +Sofa.Component.Collision.Detection.Intersection +Sofa.Component.Collision.Geometry +Sofa.Component.Collision.Response.Contact +Sofa.Component.Constraint.Lagrangian.Correction +Sofa.Component.Constraint.Lagrangian.Solver +Sofa.Component.IO.Mesh +Sofa.Component.LinearSolver.Iterative +Sofa.Component.LinearSystem +Sofa.Component.Mapping.Linear +Sofa.Component.Mass +Sofa.Component.ODESolver.Backward +Sofa.Component.SolidMechanics.FEM.Elastic +Sofa.Component.StateContainer +Sofa.Component.Topology.Container.Constant +Sofa.Component.Topology.Container.Grid +Sofa.Component.Visual +Sofa.GL.Component.Rendering3D +Sofa.GL.Component.Shader diff --git a/src/SOFA/Rendering/__init__.py b/src/SOFA/Rendering/__init__.py index 5084b7c..2074f04 100644 --- a/src/SOFA/Rendering/__init__.py +++ b/src/SOFA/Rendering/__init__.py @@ -1,3 +1,3 @@ -from .Replay import Replay -from .Visualizer import Visualizer -from .UserAPI import UserAPI +from .replay import Replay +from .user_api import UserAPI +from .visualizer import Visualizer diff --git a/src/SOFA/Rendering/Replay.py b/src/SOFA/Rendering/replay.py similarity index 60% rename from src/SOFA/Rendering/Replay.py rename to src/SOFA/Rendering/replay.py index 098420d..158e361 100644 --- a/src/SOFA/Rendering/Replay.py +++ b/src/SOFA/Rendering/replay.py @@ -1,7 +1,7 @@ -from SSD.Core.Rendering.Replay import Replay as _Replay +from SSD.Core.Rendering.replay import Replay as CoreReplay -class Replay(_Replay): +class Replay(CoreReplay): def __init__(self, database_name: str, @@ -17,8 +17,8 @@ def __init__(self, :param fps: Max frame rate. """ - _Replay.__init__(self, - database_name=database_name, - database_dir=database_dir, - backend=backend, - fps=fps) + CoreReplay.__init__(self, + database_name=database_name, + database_dir=database_dir, + backend=backend, + fps=fps) diff --git a/src/SOFA/Rendering/UserAPI.py b/src/SOFA/Rendering/user_api.py similarity index 96% rename from src/SOFA/Rendering/UserAPI.py rename to src/SOFA/Rendering/user_api.py index 46d4363..b1ddf21 100644 --- a/src/SOFA/Rendering/UserAPI.py +++ b/src/SOFA/Rendering/user_api.py @@ -2,12 +2,12 @@ from numpy import array, ndarray, tile import Sofa -from SSD.Core.Storage.Database import Database -from SSD.Core.Rendering.UserAPI import UserAPI as _UserAPI +from SSD.Core.Storage.database import Database +from SSD.Core.Rendering.user_api import UserAPI as CoreUserAPI from SSD.SOFA.utils import error_message -class UserAPI(Sofa.Core.Controller, _UserAPI): +class UserAPI(Sofa.Core.Controller, CoreUserAPI): def __init__(self, root: Sofa.Core.Node, @@ -34,14 +34,14 @@ def __init__(self, """ Sofa.Core.Controller.__init__(self, *args, **kwargs) - _UserAPI.__init__(self, - database=database, - database_dir=database_dir, - database_name=database_name, - remove_existing=remove_existing, - non_storing=non_storing, - exit_on_window_close=exit_on_window_close, - idx_instance=idx_instance) + CoreUserAPI.__init__(self, + database=database, + database_dir=database_dir, + database_name=database_name, + remove_existing=remove_existing, + non_storing=non_storing, + exit_on_window_close=exit_on_window_close, + idx_instance=idx_instance) # Add the Factory controller to the scene graph self.root: Sofa.Core.Node = root diff --git a/src/SOFA/Rendering/Visualizer.py b/src/SOFA/Rendering/visualizer.py similarity index 64% rename from src/SOFA/Rendering/Visualizer.py rename to src/SOFA/Rendering/visualizer.py index 151a2c1..e1cc316 100644 --- a/src/SOFA/Rendering/Visualizer.py +++ b/src/SOFA/Rendering/visualizer.py @@ -1,10 +1,10 @@ from typing import Optional -from SSD.SOFA.Storage.Database import Database -from SSD.Core.Rendering.Visualizer import Visualizer as _Visualizer +from SSD.SOFA.Storage.database import Database +from SSD.Core.Rendering.visualizer import Visualizer as CoreVisualizer -class Visualizer(_Visualizer): +class Visualizer(CoreVisualizer): def __init__(self, backend: str = 'vedo', @@ -26,11 +26,11 @@ def __init__(self, :param fps: Max frame rate. """ - _Visualizer.__init__(self, - backend=backend, - database=database, - database_dir=database_dir, - database_name=database_name, - remove_existing=remove_existing, - fps=fps, - nb_clients=nb_clients) + CoreVisualizer.__init__(self, + backend=backend, + database=database, + database_dir=database_dir, + database_name=database_name, + remove_existing=remove_existing, + fps=fps, + nb_clients=nb_clients) diff --git a/src/SOFA/Storage/__init__.py b/src/SOFA/Storage/__init__.py index 07f7250..ef3f969 100644 --- a/src/SOFA/Storage/__init__.py +++ b/src/SOFA/Storage/__init__.py @@ -1 +1 @@ -from .Database import Database +from .database import Database diff --git a/src/SOFA/Storage/Database.py b/src/SOFA/Storage/database.py similarity index 94% rename from src/SOFA/Storage/Database.py rename to src/SOFA/Storage/database.py index 6c7815d..b448791 100644 --- a/src/SOFA/Storage/Database.py +++ b/src/SOFA/Storage/database.py @@ -1,11 +1,11 @@ from typing import Any, Dict, Tuple import Sofa -from SSD.Core.Storage.Database import Database as _Database +from SSD.Core.Storage.database import Database as CoreDatabase from SSD.SOFA.utils import error_message -class Database(Sofa.Core.Controller, _Database): +class Database(Sofa.Core.Controller, CoreDatabase): def __init__(self, root: Sofa.Core.Node, @@ -23,9 +23,9 @@ def __init__(self, """ Sofa.Core.Controller.__init__(self, *args, **kwargs) - _Database.__init__(self, - database_dir=database_dir, - database_name=database_name) + CoreDatabase.__init__(self, + database_dir=database_dir, + database_name=database_name) # Add the Database controller to the scene graph self.root: Sofa.Core.Node = root @@ -141,7 +141,7 @@ def add_data(self, # Otherwise, create a new line else: self.__dirty[table_name] = True - _Database.add_data(self, table_name=table_name, data=data) + CoreDatabase.add_data(self, table_name=table_name, data=data) def print_architecture(self): """ diff --git a/src/SOFA/__init__.py b/src/SOFA/__init__.py index 46b2f7d..e69de29 100644 --- a/src/SOFA/__init__.py +++ b/src/SOFA/__init__.py @@ -1,2 +0,0 @@ -from .Rendering import * -from .Storage import * From feba054c6f641bdf536fa9af32c981a8bc95a481 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 7 Feb 2024 17:21:50 +0100 Subject: [PATCH 6/7] Update documentation. --- docs/src/Core/Rendering/api.rst | 14 +++----------- docs/src/Core/Storage/api.rst | 4 +--- docs/src/SOFA/api.rst | 18 ++++-------------- docs/src/SOFA/rendering.rst | 4 ++-- docs/src/SOFA/storage.rst | 4 ++-- 5 files changed, 12 insertions(+), 32 deletions(-) diff --git a/docs/src/Core/Rendering/api.rst b/docs/src/Core/Rendering/api.rst index 22a81fb..82d1836 100644 --- a/docs/src/Core/Rendering/api.rst +++ b/docs/src/Core/Rendering/api.rst @@ -1,25 +1,17 @@ API === -UserAPI -------- -.. autoclass:: SSD.Core.Rendering.UserAPI.UserAPI +.. autoclass:: SSD.Core.Rendering.user_api.UserAPI :special-members: __init__ :members: -Visualizer ----------- - -.. autoclass:: SSD.Core.Rendering.Visualizer.Visualizer +.. autoclass:: SSD.Core.Rendering.visualizer.Visualizer :special-members: __init__ :members: -Replay ------- - -.. autoclass:: SSD.Core.Rendering.Replay.Replay +.. autoclass:: SSD.Core.Rendering.replay.Replay :special-members: __init__ :members: diff --git a/docs/src/Core/Storage/api.rst b/docs/src/Core/Storage/api.rst index 66bfc5a..c73af39 100644 --- a/docs/src/Core/Storage/api.rst +++ b/docs/src/Core/Storage/api.rst @@ -1,10 +1,8 @@ API === -Database --------- -.. autoclass:: SSD.Core.Storage.Database.Database +.. autoclass:: SSD.Core.Storage.database.Database :special-members: __init__ :members: diff --git a/docs/src/SOFA/api.rst b/docs/src/SOFA/api.rst index 4998f36..4ad2a79 100644 --- a/docs/src/SOFA/api.rst +++ b/docs/src/SOFA/api.rst @@ -4,10 +4,8 @@ API Storage ------- -Database -"""""""" -.. autoclass:: SSD.SOFA.Storage.Database.Database +.. autoclass:: SSD.SOFA.Storage.database.Database :special-members: __init__ :members: @@ -15,25 +13,17 @@ Database Rendering --------- -UserAPI -""""""" -.. autoclass:: SSD.SOFA.Rendering.UserAPI.UserAPI +.. autoclass:: SSD.SOFA.Rendering.user_api.UserAPI :special-members: __init__ :members: -Visualizer -"""""""""" - -.. autoclass:: SSD.SOFA.Rendering.Visualizer.Visualizer +.. autoclass:: SSD.SOFA.Rendering.visualizer.Visualizer :special-members: __init__ :members: -Replay -"""""" - -.. autoclass:: SSD.SOFA.Rendering.Replay.Replay +.. autoclass:: SSD.SOFA.Rendering.replay.Replay :special-members: __init__ :members: diff --git a/docs/src/SOFA/rendering.rst b/docs/src/SOFA/rendering.rst index 4c91d13..cacb18b 100644 --- a/docs/src/SOFA/rendering.rst +++ b/docs/src/SOFA/rendering.rst @@ -46,7 +46,7 @@ It is also possible to create and update objects manually such as in the ``Core` .. code-block:: python import Sofa - from SSD.SOFA import UserAPI + from SSD.SOFA.Rendering import UserAPI # Create the root node root = Sofa.Core.Node('root') @@ -87,7 +87,7 @@ It is also possible to create and update objects manually such as in the ``Core` .. code-block:: python - from SSD.SOFA import UserAPI + from SSD.SOFA.Rendering import UserAPI def createScene(root): diff --git a/docs/src/SOFA/storage.rst b/docs/src/SOFA/storage.rst index ee1717f..30484ce 100644 --- a/docs/src/SOFA/storage.rst +++ b/docs/src/SOFA/storage.rst @@ -38,7 +38,7 @@ Adding callbacks is very simple, since you only have to specify: .. code-block:: python import Sofa - from SSD.SOFA import Database + from SSD.SOFA.Storage import Database # Create the root node root = Sofa.Core.Node('root') @@ -76,7 +76,7 @@ Adding callbacks is very simple, since you only have to specify: .. code-block:: python - from SSD.SOFA import Database + from SSD.SOFA.Storage import Database def createScene(root): From 23d59c2b1e6352c89bba7e0bc066d90d4266ae20 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 3 Apr 2024 15:13:20 +0200 Subject: [PATCH 7/7] Versioning. --- .gitignore | 2 ++ README.md | 2 +- examples/Core/rendering/README.md | 1 + examples/Core/storage/utils_db.py | 1 - setup.py | 8 ++++---- src/Core/Rendering/backend/vedo/vedo_replay.py | 2 +- src/cli.py | 4 ++-- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 01364af..09aab75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Excluded files *.db +*.csv +*.json # Excluded folders .idea diff --git a/README.md b/README.md index 0acabc6..da59880 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![logo](docs/src/_static/images/logo.svg) The **SSD** project provides Python3 tools allowing users to easily develop **data storage** and **visualization** -strategies for their **numerical simulations** with az minimal lines of code. +strategies for their **numerical simulations** with a minimal lines of code. This project has two main objectives: * Easy **storage** management system for **any data** from a numerical simulation; diff --git a/examples/Core/rendering/README.md b/examples/Core/rendering/README.md index c5a406f..1b0c736 100644 --- a/examples/Core/rendering/README.md +++ b/examples/Core/rendering/README.md @@ -8,5 +8,6 @@ It is recommended to inspect and run the python scripts following this order: * ``replay.py``: how to replay a simulation from a *Database*. * ``offscreen.py``: an example of off-screen rendering. * ``several_factories.py``: how to manage several *Factories* with a single *Visualizer*. +* ``several_factories_offsceen.py``: how to manage several *Factories* with a single *Visualizer* in off-screen mode. Run the examples using ``python3 .py `` with backend being either 'vedo' (by default) or 'open3d'. diff --git a/examples/Core/storage/utils_db.py b/examples/Core/storage/utils_db.py index d6941d7..59abab2 100644 --- a/examples/Core/storage/utils_db.py +++ b/examples/Core/storage/utils_db.py @@ -1,5 +1,4 @@ import os -from os import chdir from numpy.random import uniform from SSD.Core.Storage import Database, merge, rename_tables, rename_fields, remove_table, remove_field, export diff --git a/setup.py b/setup.py index cb5f4aa..4be8703 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ # Installation setup(name='SimulationSimpleDatabase', - version='22.12.4', + version='24.1', description='A simplified API to use SQL Databases with numerical simulation.', long_description=long_description, long_description_content_type='text/markdown', @@ -37,9 +37,9 @@ packages=packages, package_dir=packages_dir, package_data={f'{PROJECT}.examples.Core.rendering': ['armadillo.obj']}, - install_requires=['numpy >= 1.26.3', + install_requires=['numpy >= 1.26.4', 'peewee >= 3.17.0', - 'vedo >= 2023.5.0', - 'matplotlib >= 3.8.2', + 'vedo >= 2024.5.1', + 'matplotlib >= 3.8.3', 'open3d >= 0.16.0'], entry_points={'console_scripts': ['SSD=SSD.cli:execute_cli']}) diff --git a/src/Core/Rendering/backend/vedo/vedo_replay.py b/src/Core/Rendering/backend/vedo/vedo_replay.py index a8fcdc1..14f92e8 100644 --- a/src/Core/Rendering/backend/vedo/vedo_replay.py +++ b/src/Core/Rendering/backend/vedo/vedo_replay.py @@ -75,7 +75,7 @@ def launch_visualizer(self) -> None: axes=4) # 3. Add a timer callback and set the Plotter in interactive mode - self.__plotter.add_callback('Timer', self.update_thread) + self.__plotter.add_callback('Timer', self.update_thread, enable_picking=False) self.__plotter.timer_callback('create', dt=int(self.fps * 1e3)) self.__plotter.add_button(self.__reset, states=['start']) self.__plotter.interactive() diff --git a/src/cli.py b/src/cli.py index f49c237..c9e8b0c 100644 --- a/src/cli.py +++ b/src/cli.py @@ -74,7 +74,7 @@ def print_available_examples(examples): example_names = sorted(list(examples.keys())) example_per_repo = {} for example_name in example_names: - if type(examples[example_name]) == str: + if isinstance(type(examples[example_name]), str): root, repo = examples[example_name].split('.')[0], examples[example_name].split('.')[1] else: root, repo = examples[example_name][0].split('.')[0], examples[example_name][0].split('.')[1] @@ -174,7 +174,7 @@ def execute_cli(): visualizer.append(backend.lower()) # Run the example - if type(examples[example]) == str: + if isinstance(examples[example], str): root, repo, script, _ = examples[example].split('.') # Check SOFA installation if root == 'SOFA' and not is_SOFA_installed():