diff --git a/README.md b/README.md index 7d5b717..a5038b4 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,5 @@ This package provides an API to write and execute scripts in a running ZEISS INSPECT instance. Please read the [ZEISS INSPECT API documentation](https://zeiss.github.io/IQS/) for details. + +The [ZEISS INSPECT API wheel](https://pypi.org/project/zeiss-inspect-api/) is hosted on PyPI. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f818e5b..b939330 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.342" +version = "2027.0.0.2328" authors = [ { name="Carl Zeiss GOM Metrology GmbH", email="info.optical.metrology@zeiss.com" }, ] diff --git a/src/gom/__encoding__.py b/src/gom/__encoding__.py index 5b367b0..1f9609d 100644 --- a/src/gom/__encoding__.py +++ b/src/gom/__encoding__.py @@ -80,6 +80,16 @@ def supports_numpy(): @staticmethod def supports_shared_memory(): + ''' + Check if shared memory is supported on the current platform. + ''' + + # + # Shared memory is supported on Windows only currently, because the underlying implementation differs. + # On Windows, the kernel keeps a reference count of the open handles to a segment while on POSIX the + # memory is managed by the process itself. So ownership passing is harder on POSIX and would require + # additional synchronization mechanisms. + # return sys.platform.startswith('win') @staticmethod @@ -98,7 +108,7 @@ def read_from_shared_memory(key, shape, dtype, context): byte_size = size * np.dtype(dtype).itemsize with mmap.mmap(-1, byte_size, tagname=key, access=mmap.ACCESS_READ) as m: - result = np.frombuffer(bytes(m[:]), dtype=dtype, count=size).reshape(shape) + result = np.frombuffer(bytes(m[:]), dtype=dtype, count=size).reshape(shape).copy() context.add(key) @@ -106,6 +116,15 @@ def read_from_shared_memory(key, shape, dtype, context): @staticmethod def create_shared_memory_segment(data): + ''' + @brief Create shared memory segment + + This function creates a shared memory segment which can be used to pass large amounts of data + between processes. + + @param data Data to be stored in the shared memory segment + @return Tuple of (segment, key) for the created shared memory segment + ''' segment = None key = None @@ -117,6 +136,8 @@ def create_shared_memory_segment(data): Encoder.shared_memory_id_counter += 1 segment = mmap.mmap(-1, len(b), tagname=key, access=mmap.ACCESS_WRITE) + if segment.size() < len(b): + raise RuntimeError(f'Cannot create shared memory segment of {len (b)} bytes') segment.write(b) segment.flush() @@ -309,11 +330,11 @@ def encodeValue(self, buffer, obj, context): raise RuntimeError( '\'{obj}\' has unsupported data type \'{type}\'and cannot be encoded'.format(obj=obj, type=obj.dtype)) - if Encoder.supports_shared_memory(): + if Encoder.supports_shared_memory() and obj.nbytes > 10 * 1024: self.encodeBool(buffer, True) segment, key = Encoder.create_shared_memory_segment(obj) - context.add(segment) + context.add(key, segment) self.encodeStr(buffer, key) else: @@ -669,7 +690,7 @@ def encode_traits(obj, context): if Encoder.supports_shared_memory(): segment, key = Encoder.create_shared_memory_segment(obj) - context.add(segment) + context.add(key, segment) return {JsonEncoder.TYPE_DEFINITION_KEY: JsonEncoder.TYPE_PACKAGE, 'shape': list(obj.shape), 'type': type, diff --git a/src/gom/__init__.py b/src/gom/__init__.py index 98eac3b..7ad53ea 100644 --- a/src/gom/__init__.py +++ b/src/gom/__init__.py @@ -76,8 +76,27 @@ def tr(text, id=None): translated = text try: + origin = "" + try: + # + # Extract the caller information to find the true origin of the translation call + # If it is an script from within an app, origin will be the qualified name of that script + # Otherwise it will usually be some filepath (external script) + # + + # + # sys._getframe is more performant than using the function overhead from the inspect module + # however, it is considered an implementation detail of CPython and not guaranteed to exist, so we keep a fallback + # + if hasattr(sys, '_getframe'): + origin = sys._getframe(1).f_code.co_filename + else: + origin = inspect.currentframe().f_back.f_code.co_filename + except: + pass + translated = gom.__common__.__connection__.request(Request.TRANSLATE, { - 'text': text, 'id': id if id else ''})['translation'] + 'text': text, 'id': id if id else '', 'origin': origin})['translation'] except: pass diff --git a/src/gom/__logging__.py b/src/gom/__logging__.py new file mode 100644 index 0000000..0ad0f6f --- /dev/null +++ b/src/gom/__logging__.py @@ -0,0 +1,201 @@ +# +# __logging__.py - Network related classes for connecting with the C++ application part +# +# (C) 2025 Carl Zeiss GOM Metrology GmbH +# +# Use of this source code and binary forms of it, without modification, is permitted provided that +# the following conditions are met: +# +# 1. Redistribution of this source code or binary forms of this with or without any modifications is +# not allowed without specific prior written permission by GOM. +# +# As this source code is provided as glue logic for connecting the Python interpreter to the commands of +# the GOM software any modification to this sources will not make sense and would affect a suitable functioning +# and therefore shall be avoided, so consequently the redistribution of this source with or without any +# modification in source or binary form is not permitted as it would lead to malfunctions of GOM Software. +# +# 2. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or +# promote products derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import enum +import inspect +import logging +import os +import uuid + +from datetime import datetime, timezone + + +def get_default_log_dir(): + """ + Determines and creates the default directory for log files based on the operating system and user privileges. + + On Windows, it uses the %ProgramData%\gom\log directory (defaulting to C:\ProgramData\gom\log if the environment variable is missing). + On POSIX systems, it uses /var/log/gom if running as root, or ~/.local/share/gom/log for non-root users. + + Returns: + str: The absolute path to the default log directory. + """ + if os.name == "nt": + # On Windows, get %ProgramData% (typically C:\ProgramData) + program_data = os.environ.get("ProgramData") + if not program_data: + # Fallback if environment variable is missing + program_data = r"C:\ProgramData" + log_dir = os.path.join(program_data, "gom", "log") + else: + # On POSIX, use /var/log/gom or ~/.local/share/gom/log if not root + if os.geteuid() == 0: + log_dir = "/var/log/gom" + else: + log_dir = os.path.expanduser("~/.local/share/gom/log") + + os.makedirs(log_dir, exist_ok=True) + + return log_dir + + +class MillisecondFormatter(logging.Formatter): + """ + A custom logging.Formatter that formats log record timestamps with millisecond precision. + + Overrides the formatTime method to allow formatting of timestamps with milliseconds. + If a date format string (datefmt) is provided and contains '%f', it will be replaced + with the milliseconds component (first three digits of microseconds) of the timestamp. + + Args: + record (logging.LogRecord): The log record whose creation time is to be formatted. + datefmt (str, optional): A date format string. If provided and contains '%f', it will + be replaced with milliseconds. + + Returns: + str: The formatted time string with millisecond precision. + """ + + def formatTime(self, record, datefmt=None): + dt = datetime.fromtimestamp(record.created, tz=timezone.utc) + if datefmt: + s = dt.strftime(datefmt) + # Replace %f with milliseconds (first 3 digits of microseconds) + s = s.replace('%f', f"{dt.microsecond // 1000:03d}") + return s + return super().formatTime(record, datefmt) + + +class ProtocolLogger: + """ + ProtocolLogger is a logging utility designed to record protocol-related events in a structured log file, matching the format and domain conventions of a corresponding C++ logging system. + + Attributes: + domain (str): The logging domain, set to 'scripting.core.protocol' to match the C++ domain. + + Classes: + EventType (enum.Enum): Enumeration of protocol event types, including CONNECTED, DISCONNECTED, REQUEST, RESPONSE, and ERROR. + + Methods: + __init__(log_dir=None): + Initializes the ProtocolLogger instance. + Determines the log directory (uses a default if not provided), constructs a log file name with a UTC timestamp and process ID, and sets up a file handler with a custom formatter for millisecond precision and ISO 8601 timestamps. + + log(event_type, event_id, message): + Logs a protocol event with the specified type, identifier, and message. + Automatically includes the caller's filename and line number, formats the timestamp to match C++ conventions, and records the thread ID. + Supports event_id as a UUID or string. + """ + + domain = 'scripting.core.protocol' # Must match the C++ domain + + class EventType(enum.Enum): + """ + Enumeration of possible event types for logging purposes. + + Attributes: + CONNECTED (str): Indicates a successful connection event. + DISCONNECTED (str): Indicates a disconnection event. + REQUEST (str): Represents a request event. + RESPONSE (str): Represents a response event. + ERROR (str): Represents an error event. + """ + CONNECTED = "Connected" + DISCONNECTED = "Disconnected" + REQUEST = "Request" + RESPONSE = "Response" + ERROR = "Error" + + def __init__(self, log_dir=None): + + # Determine log directory + if log_dir is None: + log_dir = get_default_log_dir() + + # Compute log file name with start timestamp and process id + start_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S") + pid = os.getpid() + log_filename = f"python_{start_time}_{pid}.log" + log_path = os.path.join(log_dir, log_filename) + + self.logger = logging.getLogger(ProtocolLogger.domain) + handler = logging.FileHandler(log_path, encoding="utf-8") + formatter = MillisecondFormatter( + '%(asctime)s TID%(thread)d INFO [%(name)s] %(event_type)s %(event_id)s %(message)s (%(filename)s:%(lineno)d)', + "%Y-%m-%dT%H:%M:%S.%fZ" + ) + handler.setFormatter(formatter) + self.logger.handlers = [] + self.logger.addHandler(handler) + self.logger.setLevel(logging.INFO) + self.log_path = log_path + + def log(self, event_type, event_id, message): + + if isinstance(event_id, uuid.UUID): + event_id = str(event_id) + + # Get caller's filename and line number from the call stack + frame = inspect.currentframe() + outer_frames = inspect.getouterframes(frame) + if len(outer_frames) > 1: + caller_frame = outer_frames[1] + filename = os.path.basename(caller_frame.filename) + lineno = caller_frame.lineno + else: + filename = "unknown" + lineno = 0 + extra = { + 'event_type': event_type.value if isinstance(event_type, enum.Enum) else str(event_type), + 'event_id': event_id + } + + # Patch asctime to match C++ format (ISO 8601 with ms, Z) + record = self.logger.makeRecord( + self.logger.name, logging.INFO, filename, lineno, + message, (), None, None, extra) + record.asctime = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" + try: + import threading + record.thread = threading.get_ident() + except ImportError: + record.thread = 1 + record.event_type = event_type + record.event_id = event_id + record.filename = filename + record.lineno = lineno + record.name = self.logger.name + self.logger.handle(record) + + +# Example usage: +if __name__ == "__main__": + logger = ProtocolLogger() + logger.log(ProtocolLogger.EventType.REQUEST.value, uuid.uuid4(), "Register") + print(f"Log written to: {logger.log_path}") diff --git a/src/gom/__network__.py b/src/gom/__network__.py index 07d633b..2934586 100644 --- a/src/gom/__network__.py +++ b/src/gom/__network__.py @@ -39,6 +39,10 @@ import gom.__tools__ from gom.__common__ import Request +from gom.__logging__ import ProtocolLogger + +logger = None +# logger = ProtocolLogger() class EncoderContext: @@ -47,17 +51,17 @@ class EncoderContext: ''' def __init__(self): - self.handles = [] + self.handles = {} - def add(self, handle): - self.handles.append(handle) + def add(self, key, handle): + self.handles[key] = handle def __enter__(self): - self.handles = [] + self.handles = {} return self def __exit__(self, exception, value, traceback): - for handle in self.handles: + for key, handle in self.handles.items(): handle.close() self.handles = [] @@ -79,6 +83,7 @@ def __enter__(self): return self def __exit__(self, exception, value, traceback): + if self.keys: gom.__common__.__connection__.request(Request.RELEASE, {'keys': self.keys}) self.keys = [] @@ -139,6 +144,9 @@ def __init__(self, uri): self._ws = websocket.WebSocket() self._ws.connect(uri) + if logger: + logger.log(ProtocolLogger.EventType.CONNECTED, uuid.UUID(int=0), repr(uri)) + def send(self, message, context): ''' Send a message to the server @@ -161,14 +169,22 @@ def request(self, command, params): if threading.get_ident() != self._thread_id: raise RuntimeError('Using GOM API features from within different threads is no allowed.') - with EncoderContext() as context: + # + # The encoder context remains + # + with EncoderContext() as encoder_context: + + request_uuid = uuid.uuid4() + request_id = str(request_uuid) + + if logger: + logger.log(ProtocolLogger.EventType.REQUEST.value, request_uuid, command.name) - request_id = str(uuid.uuid4()) self.send({Connection.Attribute.TYPE: Connection.Attribute.Type.REQUEST, Connection.Attribute.APIKEY: gom.__config__.api_access_key, Connection.Attribute.ID: request_id, Connection.Attribute.VALUE: command.value, - Connection.Attribute.PARAMS: params}, context) + Connection.Attribute.PARAMS: params}, encoder_context) # # Although the protocol itself is synchroneous, this function is reentrant. So the @@ -177,19 +193,40 @@ def request(self, command, params): # action instead. # # The 'self._replies' dictionary is filled with the received replies which will be - # consumed by the matching instances then one by one. Each entry contains of a pair + # consumed by the matching instances then one by one. Each entry consists of a pair # (reply type, reply). The 'reply' format depends on that specific type. # while not request_id in self._replies: received = self._ws.recv() - with DecoderContext() as context: - reply = self._encoder.decode(received, context) + with DecoderContext() as decoder_context: + reply = self._encoder.decode(received, decoder_context) message_type = reply[Connection.Attribute.TYPE] message_id = reply[Connection.Attribute.ID] + if logger: + logger.log(ProtocolLogger.EventType.RESPONSE.value, message_id, message_type) + + # + # If the message id is invalid, an error happened during message decoding before the + # real id could be extracted. This is a fatal error in the protocal which cannot be + # recovered from - the script must be terminated then. + # + if str(message_id) == str(uuid.UUID(int=0)): + error = f'Invalid message id {message_id} received. Protocol is broken, terminating script.' + + if 'error' in reply: + error += f" Error details: {reply['error']}" + + self._replies[request_id] = ( + message_type, (error, + reply[Connection.Attribute.DESCRIPTION] if Connection.Attribute.DESCRIPTION in reply else None, + reply[Connection.Attribute.CODE] if Connection.Attribute.CODE in reply else None, + reply[Connection.Attribute.LOG] if Connection.Attribute.LOG in reply else None, + reply[Connection.Attribute.VALUE] if Connection.Attribute.VALUE in reply else None)) + # # Type 'ERROR': Request failed. Error code/error_log are returned. # @@ -236,7 +273,7 @@ def request(self, command, params): Connection.Attribute.ID: message_id, Connection.Attribute.STATE: True, Connection.Attribute.VALUE: result - }, context) + }, encoder_context) except BaseException as e: gom.log.error(traceback.format_exc()) @@ -260,7 +297,7 @@ def request(self, command, params): Connection.Attribute.STATE: False, Connection.Attribute.VALUE: [ e_name, traceback.format_exc(), pickle.dumps((e_type, e_text))] - }, context) + }, encoder_context) finally: gom.__state__.call_function_active -= 1 diff --git a/src/gom/api/extensions/sequence/__init__.py b/src/gom/api/extensions/sequence/__init__.py index 638eb69..e4a5335 100644 --- a/src/gom/api/extensions/sequence/__init__.py +++ b/src/gom/api/extensions/sequence/__init__.py @@ -30,8 +30,8 @@ @brief Scripted sequence elements This module contains the base class for scripted sequence elements. A scripted sequence element -combines a sequence of commands into one group. The group is treated as one single combined element -with parts. The resulting cluster of elements can then be edited again as a altogether group, of the +combines a sequence of commands into one sequence. The sequence is treated as one single combined element +with parts. The resulting cluster of elements can then be edited again as a altogether sequence, of the single elements within can be edited separately. ''' @@ -41,7 +41,7 @@ from abc import abstractmethod -class ScriptedSequenceElement (ScriptedElement): +class ScriptedSequence (ScriptedElement): ''' This class is used to define a scripted sequence element ''' @@ -60,45 +60,144 @@ def __init__(self, id: str, description: str): super().__init__(id=id, category='scriptedelement.sequence', description=description, - callables={'create': self.create}) + callables={'create': self.create, + 'edit': self.edit}) @abstractmethod - def create(self, context, args): + def create(self, context, name, args): ''' - Function called to create the scripted sequence - - This function is called to create or edit the element of a scripted sequence. The - parameters set in the `dialog` function are passed here as a parameter. - - In principle, this function is like a sub script calling the single create commands. - Behind the scenes, the calls are handled a bit different than regular script command - calls to be able to build a component object at the end. In detail, the following rules - apply: - - - The order of elements must remain the same during object lifetime. So no parameter or - external condition may change the element order. - - The number of elements must remain the same during object lifetime. No parameter or - condition may affect the number of created elements. - - There may not be other glue code commands in the sequence. Only creation commands are allowed - here. In principle, other glue code is allowed, including API calls. - - These limitations are required because behind the scenes, the scripting engine processes the - creation requests depending on the mode of sequence command execution: - - - For a simple creation process (like a scripted sequence creation command), the command list is - executed like any other script. - - When an existing creation sequence is edited, the command list is **not** executed regularly. - Instead, the command parameters are collected and will be passed to the already existing - elements to adapt these. - - For preview computation, a combination of both modes is used: The objects are created in a first - step, but marked as 'preview' and will not be part of the regular dependency graph or project. - Afterwards, like in the 'edit' case, the parameters are collected and passed to the already - existing preview elements then to update these. - - @param context The context of the sequence element - @param args The arguments passed to the sequence element, usually from the configuration dialog - @return Dictionary describing the created sequence element. The fields here are: - 'elements' - List of all created elements (including the leading element) - 'leading' - 'Leading' element which represents the whole sequence + Function called to create a sequence of elements + + **Sequence creation** + + This function is called to create a sequence of elements initially. It can use the regular scripted + creation commands to create the elements of that sequence and determine which of these elements is + the 'leading' element of the sequence. The leading element is the one which represents the whole + sequence in the sense that editing the sequence again is initialzed by editing the leading element or + deleting the leading element deletes the whole sequence. + + Example: + + ``` + def create (self, context, name, args): + + # + # Extract parameters from the dialog + # + distance = args['distance'] + + # + # Create sequence via the regular creation commands. Here, two points and a distance + # between these points is created, with the distance being the leading element. + # + POINT_1=gom.script.primitive.create_point ( + name=self.generate_element_name (name, 'First point'), + point={'point': gom.Vec3d (0.0, 0.0, 0.0)}) + + POINT_2=gom.script.primitive.create_point ( + name=self.generate_element_name (name, 'Second point'), + point={'point': gom.Vec3d (distance, 0.0, 0.0)}) + + DISTANCE=gom.script.inspection.create_distance_by_2_points ( + name = name, + point1=POINT_1, + point2=POINT_2) + + # + # Return created sequence elements and the leading element of that sequence + # + return {'elements': [POINT_1, POINT_2, DISTANCE], 'leading': DISTANCE} + ``` + + **Element naming** + + Element names must be unique within a project. Also, the elements belonging to the same sequence should be + identifiable via their names. To assure this, the element names should be computed via the API function + `generate_element_name()`. Please see documentation of this function for details. + + @param context The context of the element + @param name Name of the leading element, extracted from the dialog. + @param args The arguments passed to the sequence, usually from the configuration dialog + @return Dictionary describing the created element. The fields here are: + `elements` - List of all created elements (including the leading element) + `leading` - 'Leading' element which represents the whole sequence ''' pass + + @abstractmethod + def edit(self, context, elements, args): + ''' + Function called to edit the scripted sequence + + This function is called when a scripted sequence is edited. It will receive the current sequence elements + together with the current sequence creation dialog values and must reconfigure the sequence elements accordingly. + + Example: + + ``` + def edit (self, context, elements, args): + + # + # The 'elements' parameter is a list containing the elements in the + # same order as returned by the 'create()' function + # + POINT_1, POINT_2, DISTANCE = elements + + # + # Actual dialog parameters + # + distance = args['distance'] + + gom.script.sys.edit_creation_parameters ( + element=POINT_2, + point={'point': gom.Vec3d (distance, 0.0, 0.0)}) + ``` + + @param context The context of the sequence + @param elements List of current elements of the sequence in the same order as returned by the `create()` function + @param args Creation arguments from the dialog + ''' + pass + + def generate_element_name(self, leading_name, basename): + ''' + Generates a unique name for an element of the scripted sequence. + + This function generates a unique name for an element of the scripted sequence. The name is based + on the leading element of the sequence, plus a base name and a running number. + + **Example** + + For a sequence with id `Distance 1` and a base name `Point`, the generated names will be + `Distance 1 ● Point 1`, `Distance 1 ● Point 2`, ... + + When implemented, the `create()` function of the scripted sequence should use this function + to generate the names of the single elements: + + ```python + def create(self, context, name, args): + + distance = args['distance'] # Distance from dialog + + POINT_1 = gom.script.primitive.create_point( + name=self.generate_element_name(name, 'First point'), + point={'point': gom.Vec3d(0.0, 0.0, 0.0)}) + + POINT_2 = gom.script.primitive.create_point( + name=self.generate_element_name(name, 'Second point'), + point={'point': gom.Vec3d(distance, 0.0, 0.0)}) + + DISTANCE = gom.script.inspection.create_distance_by_2_points( + name=name, + point1=POINT_1, + point2=POINT_2) + + return {'elements': [POINT_1, POINT_2, DISTANCE], 'leading': DISTANCE} + ``` + @param leading_name Name of the leading element of the sequence. This is usually the name as + specified in the creation dialog. + @param basename Base name for the element, like `Point` or `Line`. This name part will be + extended by a running number to make it unique. + @return Generated unique name + ''' + return f'{leading_name} ● {basename}' diff --git a/src/gom/api/infoitem/__init__.py b/src/gom/api/infoitem/__init__.py new file mode 100644 index 0000000..b8e4a96 --- /dev/null +++ b/src/gom/api/infoitem/__init__.py @@ -0,0 +1,402 @@ +# +# API declarations for gom.api.infoitem +# +# @brief API for creating, configuring, and displaying info items +# +# This API provides functions and classes to create, configure, and display info items +# within the application's graphical user interface. Info items can be shown as simple text +# or as structured content with headings, descriptions, and keyboard/mouse shortcuts. +# The API supports querying available categories and alignments, and allows dynamic control +# over the visibility, warning state, and content of each info item. Both plain text and +# structured info item types are supported, enabling flexible presentation of contextual +# information to the user. +# +# ![Info Item Example](images/info_item_api_1.png) +# +# Info items can be positioned in five different alignments: top, center, bottom, top_right, and top_left. +# + +import gom +import gom.__api__ + +from typing import Any +from uuid import UUID + +class Text (gom.__api__.Object): + ''' + @brief Class for displaying simple text info items + + This class represents an info item that displays plain text within the application's graphical user interface. + It provides methods to set the text content, control visibility, configure warning and always-visible states, + and retrieve the current configuration. The text info item can be aligned in various positions and can use a larger + font if specified. + + **Example:** + ``` + import gom + import gom.api.infoitem + + item_text = gom.api.infoitem.create_text('INFO_WARNING', 'top_left') + item_text.set_text("Hello World") + item_text.set_always_visible(True) + item_text.show() + ``` + ''' + + def __init__ (self, instance_id): + super ().__init__ (instance_id) + + def set_warning(self, state:bool) -> None: + ''' + @brief Set the warning state for this text info item + + Sets whether this info item should be displayed in a warning state. When set to true, + the item will be visually highlighted as a warning in the user interface. + + ![Info Item Structured Example](images/info_item_api_text_1.png) + + @param state If true, the item is marked as a warning; otherwise, it is shown normally. + ''' + return self.__call_method__('set_warning', state) + + def set_always_visible(self, state:bool) -> None: + ''' + @brief Set the always-visible state for this text info item + + Sets whether this info item should always be visible, regardless of its priority or other conditions. + When set to true, the item will remain visible in the user interface at all times. + + @param state If true, the item is always visible; otherwise, it follows normal visibility rules. + ''' + return self.__call_method__('set_always_visible', state) + + def get_configuration(self) -> dict: + ''' + @brief Return the current configuration of this text info item as a dictionary + + This method returns the current configuration and state of the text info item as a dictionary. + The configuration includes properties such as category, alignment, font size, warning state, + always-visible state, and the current text content. This is mainly intended for debugging or + inspection purposes. + + @return Dictionary containing the configuration of the text info item + ''' + return self.__call_method__('get_configuration') + + def show(self) -> None: + ''' + @brief Show this text info item in the user interface + + Displays the text info item in the application's graphical user interface using the current text content. + ''' + return self.__call_method__('show') + + def clear(self) -> None: + ''' + @brief Clear the content and hide this text info item + + Removes the text content from the info item and hides it from the user interface. + ''' + return self.__call_method__('clear') + + def set_text(self, text:str) -> None: + ''' + @brief Set the text content for this info item + + Sets the text that will be displayed by this info item. If the item is currently visible, + the displayed content is updated immediately. + + **Example:** + ``` + item_text.set_text("New message") + ``` + + @param text The new text to display in the info item + ''' + return self.__call_method__('set_text', text) + + +class Structured (gom.__api__.Object): + ''' + @brief Class for displaying structured info items + + This class represents an info item that displays structured content within the application's graphical user + interface. Structured info items can include a heading (with optional icon and help id), a description, and one or + more keyboard/mouse shortcuts. The class provides methods to set and update each of these fields, as well as to + control visibility and warning state. Structured info items can be aligned in various positions and can use a larger + font if specified. + + ![Info Item Structured Example](images/info_item_api_structured_1.png) + + **Example:** + ``` + import gom + import gom.api.infoitem + + item_structured = gom.api.infoitem.create_structured('INFO_GENERAL', 'bottom') + item_structured.set_heading(text="My Heading") + item_structured.set_description("Description text") + item_structured.add_single_shortcut(keys="ctrl+alt", mouse="left_button", description="Shortcut description") + item_structured.show() + ``` + ''' + + def __init__ (self, instance_id): + super ().__init__ (instance_id) + + def set_warning(self, state:bool) -> None: + ''' + @brief Set the warning state for this structured info item + + Sets whether this info item should be displayed in a warning state. When set to true, + the item will be visually highlighted as a warning in the user interface. + + ![Info Item Structured Example](images/info_item_api_structured_2.png) + + @param state If true, the item is marked as a warning; otherwise, it is shown normally. + ''' + return self.__call_method__('set_warning', state) + + def set_always_visible(self, state:bool) -> None: + ''' + @brief Set the always-visible state for this structured info item + + Sets whether this info item should always be visible, regardless of its priority or other conditions. + When set to true, the item will remain visible in the user interface at all times. + + @param state If true, the item is always visible; otherwise, it follows normal visibility rules. + ''' + return self.__call_method__('set_always_visible', state) + + def get_configuration(self) -> dict: + ''' + @brief Return the current configuration of this structured info item as a dictionary + + This method returns the current configuration and state of the structured info item as a dictionary. + The configuration includes properties such as category, alignment, font size, warning state, + always-visible state, heading, description, and shortcuts. This is mainly intended for debugging or + inspection purposes. + + @return Dictionary containing the configuration of the structured info item + ''' + return self.__call_method__('get_configuration') + + def show(self) -> None: + ''' + @brief Show this structured info item in the user interface + + Displays the structured info item in the application's graphical user interface using the current content. + ''' + return self.__call_method__('show') + + def clear(self) -> None: + ''' + @brief Clear the content and hide this structured info item + + Removes the content from the info item and hides it from the user interface. + ''' + return self.__call_method__('clear') + + def set_heading(self, icon:str='', text:str='', help_id:str='') -> None: + ''' + @brief Set the heading for this structured info item + + Sets the heading icon, text, and help id for the structured info item. + + The `icon` parameter supports multiple input formats + - Internal icon identifier from GIcon::Cache + - Complete AddOnUrl pointing to an icon file + - Path to an external file + - Base64-encoded QImage or QPixmap string + + **Examples:** + ```python + # Use an internal icon name + item_structured.set_heading(icon="zui_holy_placeholder", text="My Heading") + + # Use an AddOnUrl + icon = "acp:////.../testicon.svg" + item_structured.set_heading(icon=icon, text="My Heading") + + # Use an external file path + icon = "C:/icons/myicon.png" + item_structured.set_heading(icon=icon, text="My Heading") + + # Use a base64-encoded string + icon = "iVBORw0KG....AAAAAK7QJANhcmnoAAAAAElFTkSuQmCC" + item_structured.set_heading(icon=icon, text="My Heading") + ``` + + @param icon The icon to display in the heading + @param text The heading text + @param help_id The help id associated with the heading + ''' + return self.__call_method__('set_heading', icon, text, help_id) + + def set_description(self, description:str) -> None: + ''' + @brief Set the description for this structured info item + + Sets the description text for the structured info item. + + **Example:** + ``` + item_structured.set_description("Description text") + ``` + + @param description The description text to display + ''' + return self.__call_method__('set_description', description) + + def add_single_shortcut(self, keys:str='', mouse:str='', description:str='') -> None: + ''' + @brief Add a single shortcut to this structured info item + + Adds a shortcut consisting of a key sequence and/or mouse button, with an optional description. + + The format and rules for the `keys` parameter are as follows: + - It may consist of zero or more modifier keys (`Ctrl`, `Shift`, `Alt`) and at most one regular key. + - For the complete list of keys, see the [Qt::Key enum documentation](https://doc.qt.io/qt-6/qt.html#Key-enum). Use + the string after "Key_", e.g., `"Key_F1"` → `"F1"`, `"Key_A"` → `"A"`. + - Modifiers and the regular key are combined with `+`, e.g., `"Ctrl+Alt+S"`. + - Key names are case-insensitive. + - You may specify only modifiers (e.g., `"Ctrl+Alt"`), or leave `keys` empty to use only a mouse button. + + The list of available mouse buttons can be obtained using `item_structured.get_mouse_buttons()`. + + **Example:** + ``` + item_structured.add_single_shortcut(keys="ctrl+alt", mouse="left_button", description="Single shortcut example") + ``` + + @param keys The key sequence + @param mouse The mouse button + @param description The description of the shortcut + ''' + return self.__call_method__('add_single_shortcut', keys, mouse, description) + + def add_double_shortcut(self, keys1:str='', mouse1:str='', keys2:str='', mouse2:str='', description:str='') -> None: + ''' + @brief Add a double shortcut to this structured info item + + Adds a shortcut consisting of two key sequence and/or mouse combinations, with an optional description. + + For detailed information about the format and rules for `keys1`, `keys2`, `mouse1`, and `mouse2`, + see the documentation for `add_single_shortcut`. + + **Example:** + ``` + item_structured.add_double_shortcut(keys1="ctrl", mouse1="left_button", keys2="shift", mouse2="right_button", description="Double shortcut example") + ``` + + @param keys1 The first key sequence + @param mouse1 The first mouse button + @param keys2 The second key sequence + @param mouse2 The second mouse button + @param description The description of the shortcut + ''' + return self.__call_method__('add_double_shortcut', keys1, mouse1, keys2, mouse2, description) + + def get_mouse_buttons(self) -> list[str]: + ''' + @brief Return the list of all available mouse buttons + + The structured info item supports the following mouse buttons: left_button, right_button, middle_button, and mouse_wheel. + ![Info Item Structured Example](images/info_item_api_structured_3.png) + + **Example:** + ``` + import gom + import gom.api.infoitem + + item_structured = gom.api.infoitem.create_structured('INFO_GENERAL', 'bottom') + print(item_structured.get_mouse_buttons()) + ``` + + @return List of info category names as strings + ''' + return self.__call_method__('get_mouse_buttons') + + +def get_categories() -> list[str]: + ''' + @brief Return the list of all available info categories + + **Example:** + ``` + import gom + import gom.api.infoitem + + print(gom.api.infoitem.get_categories()) + ``` + + @return List of info category names as strings + ''' + return gom.__api__.__call_function__() + +def get_alignments() -> list[str]: + ''' + @brief Return the list of all available info alignments + + **Example:** + ``` + import gom + import gom.api.infoitem + + print(gom.api.infoitem.get_alignments()) + ``` + + @return List of alignment names as strings + ''' + return gom.__api__.__call_function__() + +def create_text(category:str, alignment:str='', large_font:bool=False) -> Text: + ''' + @brief Create a new text info item + + Creates and returns a new info item that displays plain text. + + **Example:** + ``` + import gom + import gom.api.infoitem + + item_text = gom.api.infoitem.create_text('INFO_WARNING', 'top_left') + ``` + + The list of available categories can be obtained using `gom.api.infoitem.get_categories`, + and the list of available alignments can be obtained using `gom.api.infoitem.get_alignments`. + + @param category The info category for the item (required) + @param alignment The alignment for the item (optional, default: 'bottom') + @param large_font Whether to use a large font (optional, default: False) + @return The created text info item + ''' + return gom.__api__.__call_function__(category, alignment, large_font) + +def create_structured(category:str, alignment:str='', large_font:bool=False) -> Structured: + ''' + @brief Create a new structured info item + + Creates and returns a new info item that displays structured content which can include a heading, description, and + shortcuts. + + **Example:** + ``` + import gom + import gom.api.infoitem + + item_structured = gom.api.infoitem.create_structured('INFO_GENERAL', 'bottom') + ``` + + The list of available categories can be obtained using `gom.api.infoitem.get_categories`, + and the list of available alignments can be obtained using `gom.api.infoitem.get_alignments`. + + @param category The info category for the item (required) + @param alignment The alignment for the item (optional, default: 'bottom') + @param large_font Whether to use a large font (optional, default: False) + @return The created structured info item + ''' + return gom.__api__.__call_function__(category, alignment, large_font) + diff --git a/src/gom/api/introspection/__init__.py b/src/gom/api/introspection/__init__.py index 9f94d4a..a593f8e 100644 --- a/src/gom/api/introspection/__init__.py +++ b/src/gom/api/introspection/__init__.py @@ -32,14 +32,14 @@ def name(self) -> str: ''' return self.__call_method__('name') - def descripion(self) -> str: + def description(self) -> str: ''' @brief Returns the optional function description @version 1 @return Function description ''' - return self.__call_method__('descripion') + return self.__call_method__('description') def signature(self) -> list[str]: '''