diff --git a/pyproject.toml b/pyproject.toml index b5f689b..6af9ce0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "zeiss_inspect_api" -version = "2026.3.0.343" +version = "2026.3.0.984" authors = [ { name="Carl Zeiss GOM Metrology GmbH", email="info.optical.metrology@zeiss.com" }, ] diff --git a/src/gom/__common__.py b/src/gom/__common__.py index f57c818..365b199 100644 --- a/src/gom/__common__.py +++ b/src/gom/__common__.py @@ -50,7 +50,7 @@ class Constants: class Request (Enum): ''' - \brief Request id + @brief Request id This id must match the id in the C++ part ''' diff --git a/src/gom/__encoding__.py b/src/gom/__encoding__.py index 5b367b0..1dd5835 100644 --- a/src/gom/__encoding__.py +++ b/src/gom/__encoding__.py @@ -47,8 +47,8 @@ class Encoder: class PackageType (Enum): ''' - \brief Package content data type - \attention Must match with the enumeration in the server + @brief Package content data type + @attention Must match with the enumeration in the server ''' INVALID = 0 INT_8 = 1 @@ -133,8 +133,8 @@ class CdcEncoder (Encoder): class Type (Enum): ''' - \brief Data types handled by the CDC encoding scheme. - \attention Must match with the enumeration in the server + @brief Data types handled by the CDC encoding scheme. + @attention Must match with the enumeration in the server ''' NONE = 0 BOOLEAN = 1 @@ -160,12 +160,12 @@ class Type (Enum): def encode(self, obj, context): ''' - \brief Encode data package + @brief Encode data package Encodes an arbitrary Python object into binary payload package. - \param obj Python object - \param context Context for additional encoding information like used shared memory segments etc. - \return Binary data block + @param obj Python object + @param context Context for additional encoding information like used shared memory segments etc. + @return Binary data block ''' buffer = bytearray() self.encodeValue(buffer, obj, context) @@ -173,10 +173,10 @@ def encode(self, obj, context): def encodeValue(self, buffer, obj, context): ''' - \brief Encode object into CDC (compact data container) format + @brief Encode object into CDC (compact data container) format - \param buffer Array chunk buffer the generated data is appended to - \param obj Arbitrary Python object to be encoded + @param buffer Array chunk buffer the generated data is appended to + @param obj Arbitrary Python object to be encoded ''' if obj is None: self.encodeType(buffer, CdcEncoder.Type.NONE) @@ -347,10 +347,10 @@ def encodeStr(self, buffer, obj): def decode(self, data, context): ''' - \brief Decode data package + @brief Decode data package - \param data Binary data block - \return Represented Python object + @param data Binary data block + @return Represented Python object ''' class InStream: @@ -368,13 +368,13 @@ def read(self, n): def decodeValue(self, s, context): ''' - \brief Decode the next encoded Python object in the buffer + @brief Decode the next encoded Python object in the buffer If the encoded object is a container, the container content is decoded recursively. - \param s Data stream - \return Python object + @param s Data stream + @return Python object ''' obj_type = CdcEncoder.Type(s.read(1)[0]) @@ -550,7 +550,7 @@ def decodeStr(self, s): # class JsonEncoder (Encoder): ''' - \brief Payload data encoding/decoding in JSON format + @brief Payload data encoding/decoding in JSON format ''' # @@ -585,12 +585,12 @@ class JsonEncoder (Encoder): def encode(self, obj, context): ''' - \brief Encode data package + @brief Encode data package Encodes an arbitrary Python object into binary payload package. - \param obj Python object - \return Binary data block + @param obj Python object + @return Binary data block ''' return json.dumps(JsonEncoder.encode_traits(obj, context)).encode() @@ -601,23 +601,23 @@ def decode(self, data, context): This function decodes a binary payload data package into the represented Python object. - \param data Binary data block - \return Python object + @param data Binary data block + @return Python object ''' return JsonEncoder.decode_traits(json.loads(data.decode()), context) @staticmethod def encode_traits(obj, context): ''' - \brief Encode complex Python types into JSON compatible format + @brief Encode complex Python types into JSON compatible format In JSON there is no way to transmit other than the standard objects (bool, int, ..., list, map). So types like Item or dynamically registeres types must be converted into a map like representation before being encoded. - \param obj Python object to be encoded - \param context Encoding context for keeping addition information like the used shared memory segements - \return Python object with complex data types converted into a map like representation + @param obj Python object to be encoded + @param context Encoding context for keeping addition information like the used shared memory segements + @return Python object with complex data types converted into a map like representation ''' if hasattr(obj, '__json__'): @@ -697,8 +697,8 @@ def decode_traits(obj, context): into native Python objects. This function is then called to convert the dictionary objects which are representing dynamic types into these types. - \param obj Python object in decoded format - \return Python object with all intermediate types resolved + @param obj Python object in decoded format + @return Python object with all intermediate types resolved ''' result = None diff --git a/src/gom/__init__.py b/src/gom/__init__.py index 98eac3b..e064905 100644 --- a/src/gom/__init__.py +++ b/src/gom/__init__.py @@ -62,16 +62,16 @@ def tr(text, id=None): ''' - \brief Return translated version of the given test + @brief Return translated version of the given test This function is added to the global namespace of the executed script and can be used to receive translations from the packages *.xlf files. Being a global function, this function will be available in the executed script, too. - \param text Text to be translated - \param id Translation id of the text. Optional, used in the GOM internal translation process. - \return Translation in the current locale + @param text Text to be translated + @param id Translation id of the text. Optional, used in the GOM internal translation process. + @return Translation in the current locale ''' translated = text @@ -86,7 +86,7 @@ def tr(text, id=None): class RequestError (RuntimeError): ''' - \brief Exception type raised from an failed request + @brief Exception type raised from an failed request ''' def __init__(self, description, error_code, error_log): @@ -122,7 +122,7 @@ def __reduce__(self): class BreakError (Exception): ''' - \brief Exception raised if the running script is to be terminated + @brief Exception raised if the running script is to be terminated ''' def __init__(self, text=''): @@ -134,7 +134,7 @@ def __repr__(self): class Indexable (object): ''' - \brief Object representing a indexable proxy to some partially resolved item. + @brief Object representing a indexable proxy to some partially resolved item. Example: 'gom.app.project.inspection['Point cloud'].coordinate' does not provide a token itself. Instead, it can be used together with an index to access single @@ -189,7 +189,7 @@ def from_params(params): class Item (object): ''' - \brief An object of this class represents a single item in the applications item space + @brief An object of this class represents a single item in the applications item space Each Tom::ScriptObject has a unique item id, like 'I#!1234', which is used to link an item to the corresponding C++ object. @@ -297,7 +297,7 @@ def from_params(params): class Array (object): ''' - \brief Data array container representation + @brief Data array container representation An object of this type is being returned if the 'data' token is queried from an item, like in 'element.data.coordinate'. So after the 'data' part of the @@ -415,7 +415,7 @@ def numpy_imported_hook(module): class ResourceAccess (object): ''' - \brief Resource accessing class + @brief Resource accessing class This object represents the virtual script object 'gom.app.resource' which can be used to access script resources. The resource is returned as a 'bytes ()' array. @@ -446,7 +446,7 @@ def from_params(params): class Command (object): ''' - \brief Command or command namespace representing object. + @brief Command or command namespace representing object. This is anything starting with 'gom.script' or 'gom.interactive' ''' @@ -517,7 +517,7 @@ def create_overload_group(name): class Object (object): ''' - \brief Value representing a generic object instance without specialized script type interface + @brief Value representing a generic object instance without specialized script type interface ''' def __init__(self, params): diff --git a/src/gom/__network__.py b/src/gom/__network__.py index 07d633b..e91ff97 100644 --- a/src/gom/__network__.py +++ b/src/gom/__network__.py @@ -43,7 +43,7 @@ class EncoderContext: ''' - \brief Encoding context caring for shared memory segments + @brief Encoding context caring for shared memory segments ''' def __init__(self): @@ -65,7 +65,7 @@ def __exit__(self, exception, value, traceback): class DecoderContext: ''' - \brief Decoding context caring for shared memory segments + @brief Decoding context caring for shared memory segments ''' def __init__(self): diff --git a/src/gom/__test__.py b/src/gom/__test__.py index 48365ff..95c6f1c 100644 --- a/src/gom/__test__.py +++ b/src/gom/__test__.py @@ -33,7 +33,7 @@ class TestInterface: ''' - \brief Function collection for debugging and testing + @brief Function collection for debugging and testing ''' def reflect(self, value): diff --git a/src/gom/__tools__.py b/src/gom/__tools__.py index 7c1470f..7821493 100644 --- a/src/gom/__tools__.py +++ b/src/gom/__tools__.py @@ -94,7 +94,7 @@ def filter_exception_traceback(tb): class StdoutFlusher: ''' - \brief This class writes stdout events and flushes the text buffer immediately + @brief This class writes stdout events and flushes the text buffer immediately ''' def __init__(self): @@ -122,7 +122,7 @@ def __exit__(self, ext_type, exc_value, traceback): class StderrFlusher: ''' - \brief This class writes stderr events and flushes the text buffer immediately + @brief This class writes stderr events and flushes the text buffer immediately ''' def __init__(self): @@ -258,7 +258,7 @@ def find_spec(self, fullname, path, target=None): class EnvironmentListener (collections.abc.Mapping): ''' - \brief Listener for changes to the environment variables + @brief Listener for changes to the environment variables ''' def __enter__(self): @@ -341,7 +341,7 @@ def __str__(self): class ExitHandler: ''' - \brief This class wraps the standard 'sys.exit ()' functions + @brief This class wraps the standard 'sys.exit ()' functions ''' original_exit = None @@ -380,7 +380,7 @@ def emit(self, record): class Console: ''' - \brief Unfiltered stream for direct console output (for test script purposes) + @brief Unfiltered stream for direct console output (for test script purposes) ''' def write(self, text): @@ -392,7 +392,7 @@ def flush(self): class NumpyErrorMessageFacade: ''' - \brief Numpy error message facade + @brief Numpy error message facade This class is used to raise a self speaking error message in case of using numpy functions without prior importing the numpy library. This is needed because diff --git a/src/gom/api/extensions/__init__.py b/src/gom/api/extensions/__init__.py index e0d0896..458b253 100644 --- a/src/gom/api/extensions/__init__.py +++ b/src/gom/api/extensions/__init__.py @@ -64,6 +64,57 @@ class ScriptedElement (ABC, gom.__common__.Contribution): The category of an element type is used to find the application side counterpart which cares for the functionality implementation. For example, `scriptedelement.actual` links that element type the application counterpart which cares for scripted actual elements and handles its creation, editing, administration, ... + + **Storing custom element data** + + In principle, each scripted element type consists of functions which will get input via function parameters and + generate output by returning a value, usually a dictionary object with named entries. For example, if a scripted + nominal command creates a nominal point, the `compute ()` function could return a dictionary like this: + + ``` + { + 'position': (x, y, z), + 'normal': (nx, ny, nz) + } + ``` + + This is defined in details with the respective element type documentation. The returned values are then stored + in the project file and belong to the element. The keys of the returned dictionary are the data keys which are fixed + for each element type and element specific. + + In addition to these fixed data keys, each scripted element can also store custom data entries. These entries + are not predefined by the element type, but can be freely defined by the scripted element implementation. This is not a + function result, it is more like an additional data storage which is associated with the element instance, for example + for caching intermediate results, storing additional metadata, ... Thus, custom element data is accessed via the `context` + object passed to each function. The context custom data is staged, so each stage has its own custom data storage, and the current + stage is always the one used for data access + + Example: + ``` + def compute (self, context, values): + # Access custom data for the current stage. + if 'intermediate_result' in context.data: + intermediate_result = context.data['intermediate_result'] + + else: + # Compute intermediate result and write it to custom data for later reuse. + # The type is arbitrary, as long as it is serializable. Using a dictionary here + # is usually a good choice. + intermediate_result = ... # Compute intermediate result + context.data = {'intermediate_result': intermediate_result, + 'computed': True} + + # Compute final result + result = ... + return result + ``` + + The size of the data storage is not limited, but it should be kept in mind that all data must be serialized + and stored in the project file. Therefore, large data structures should be avoided here. Also, calls to + `context.data` can be relatively expensive due to serialization and deserialization, so frequent access + should be avoided. Also, extremely large data structures may not be storable at all due to internal limits. + + The custom data can be cleared per stage by setting an empty dictionary to `context.data = {}`. ''' class Event(str, Enum): @@ -111,9 +162,8 @@ def __init__(self, id: str, category: str, description: str, callables={}, prope Constructor @param id Unique contribution id, like `special_point` - @param category Scripted element type id, like `scriptedelement.actual` + @param category Scripted element category id, like `scriptedelement.actual` @param description Human readable contribution description - @param category Contribution category ''' assert id, "Id must be set" @@ -346,27 +396,27 @@ def apply_dialog (self, dlg, result): else: values[widget] = result[widget] - result = {ScriptedElement.Attribute.VALUES: values} + result = {ScriptedElement.Attribute.VALUES.value: values} if name is None: - result[ScriptedElement.Attribute.NAME] = '### element name not specified ###' + result[ScriptedElement.Attribute.NAME.value] = '### element name not specified ###' else: - result[ScriptedElement.Attribute.NAME] = name + result[ScriptedElement.Attribute.NAME.value] = name if tolerance is not None: - result[ScriptedElement.Attribute.TOLERANCE] = tolerance + result[ScriptedElement.Attribute.TOLERANCE.value] = tolerance return result @final - def event_handler(self, context, event_type, parameters): + def event_handler(self, context, event_type, parameters) -> bool: ''' Wrapper function for calls to `event ()`. This function is called from the application side and will convert the event parameters accordingly ''' return self.event(context, ScriptedElement.Event(event_type), parameters) - def event(self, context, event_type, parameters): + def event(self, context, event_type, parameters) -> bool: ''' Contribution event handling function. This function is called when the contributions UI state changes. The function can then react to that event and update the UI state accordingly. @@ -403,7 +453,7 @@ def compute_all(self, context, values): results_states = self.compute_stages(context, values) return self.finish(context, results_states) - def is_visible(self, context): + def is_visible(self, context) -> bool: ''' This function is called to check if the scripted element is visible in the menus. This is usually the case if the selections and other precautions are setup and the user then shall be enabled to create or edit the element. @@ -503,7 +553,7 @@ def add_log_message(self, context, level, message): else: gom.log.info(message) - def check_value(self, values: Dict[str, Any], key: str, value_type: type): + def check_value(self, values: Dict[str, Any], key: str, expected: type): ''' Check a single value for expected properties @@ -518,10 +568,10 @@ def check_value(self, values: Dict[str, Any], key: str, value_type: type): v = values[key] t = type(v) if type(v) != int else float - if t != value_type: - raise TypeError(f"Expected a value of type '{t}' for '{key}', but got '{type(v)}'") + if t != expected: + raise TypeError(f"Expected a value of type '{expected}' for '{key}', but got '{type(v)}'") - def check_list(self, values: Dict[str, Any], key: str, value_type: type, length: int): + def check_list(self, values: Dict[str, Any], key: str, expected: type, length: int): ''' Check tuple result for expected properties @@ -539,11 +589,11 @@ def check_list(self, values: Dict[str, Any], key: str, value_type: type, length: if length and len(values[key]) != length: raise TypeError(f"Expected a tuple or a list of {length} values for '{key}'") - if value_type == float: + if expected == float: for v in values[key]: if type(v) != float and type(v) != int: raise TypeError(f"Expected values of type 'int/float' for '{key}', but got '{type(v)}'") - elif value_type == gom.Vec3d: + elif expected == gom.Vec3d: for v in values[key]: if type(v) != gom.Vec3d: if type(v) != list or len(v) != 3: @@ -551,8 +601,8 @@ def check_list(self, values: Dict[str, Any], key: str, value_type: type, length: raise TypeError(f"Expected values of type 'Vec3d' for '{key}', but got '{type(v)}'") else: for v in values[key]: - if type(v) == value_type: - raise TypeError(f"Expected values of type '{value_type}' for '{key}', but got '{type(v)}'") + if type(v) != expected: + raise TypeError(f"Expected values of type '{expected}' for '{key}', but got '{type(v)}'") def check_target_element(self, values: Dict[str, Any]): ''' diff --git a/src/gom/api/extensions/diagrams/__init__.py b/src/gom/api/extensions/diagrams/__init__.py index 41f298b..9aa2772 100644 --- a/src/gom/api/extensions/diagrams/__init__.py +++ b/src/gom/api/extensions/diagrams/__init__.py @@ -190,7 +190,7 @@ def event(self, element_name: str, element_uuid: str, event_data: Any) -> Dict[s @param element_uuid String containing the element uuid for internal identification @param event_data Contains current mouse coordinates and button presses - @return Dictionary with finish_event(