diff --git a/doc/_static/css/theme_zeiss.css b/doc/_static/css/theme_zeiss.css new file mode 100644 index 0000000..59ebf42 --- /dev/null +++ b/doc/_static/css/theme_zeiss.css @@ -0,0 +1,171 @@ +/****************************************************************************** + * Override some styles to match ZEISS branding + * + * See https://brand.zeiss.com/ + * + * Copyright 2023, Carl Zeiss GOM Metrology GmbH + ******************************************************************************/ + +/* Based on Read the Docs theme */ +@import 'theme.css'; + + +/****************************************************************************** + * Fonts + ******************************************************************************/ + +/* Use font "ZEISS Frutiger Next W1G" */ +@font-face { + font-family: "Frutiger Next"; + src: url("../fonts/ZEISS-Frutiger-Next-W02-Regular.woff2") format("woff2"), + url("../fonts/ZEISS-Frutiger-Next-W02-Regular-Italic.woff2") format("woff2"), + url("../fonts/ZEISS-Frutiger-Next-W02-Medium.woff2") format("woff2"), + url("../fonts/ZEISS-Frutiger-Next-W02-Bold.woff2") format("woff2"); +} + +:root { + --font-family-frutiger: "Frutiger Next", "Helvetica Neue", Helvetica, Verdana, Arial, sans-serif; +} + +html, body, .rst-content .toctree-wrapper>p.caption, h1, h2, h3, h4, h5, h6, legend { + font-family: var(--font-family-frutiger); + color: #000000; /* was #404040 */ +} + + +/****************************************************************************** + * Links + ******************************************************************************/ + +/* Changed from #2980b9 -> ZEISS Azure */ +a { + color: #0072EF; +} + +/* Changed from #9b59b6 -> ZEISS Saphire */ +a:visited { + color: #4C6BB1; +} + + +/****************************************************************************** + * Menu section text, changed from #55a5d9 -> ZEISS Sky Blue, from 85% -> 100% + ******************************************************************************/ + +/* Menu section text, changed from #55a5d9 -> ZEISS Sky Blue, from 85% -> 100% */ +.wy-menu-vertical p.caption { + color: #6AB0E2; + font-size: 100%; +} + +/* Menu text, changed from #404040 to ZEISS Black */ +.wy-menu-vertical li.toctree-l2 a, .wy-menu-vertical li.toctree-l3 a, .wy-menu-vertical li.toctree-l4 a, .wy-menu-vertical li.toctree-l5 a, .wy-menu-vertical li.toctree-l6 a, .wy-menu-vertical li.toctree-l7 a, .wy-menu-vertical li.toctree-l8 a, .wy-menu-vertical li.toctree-l9 a, .wy-menu-vertical li.toctree-l10 a { + color: #000000; +} + +/* Menu text, changed from #404040 -> ZEISS Black*/ +.wy-menu-vertical li.current>a, .wy-menu-vertical li.on a { + color: #000000; +} + +/* Changed from #298069 -> ZEISS Ultradark Gray */ +.wy-side-nav-search { + background: #32373E; +} + +/* Changed from #343131 -> ZEISS Ultradark Gray */ +.wy-nav-side { + background: #32373E; +} + +/* Menu level 1, changed from #d9d9d9 -> ZEISS White */ +.wy-menu-vertical a { + color: #FFFFFF; +} + +/* Menu level 1, changed from #fcfcfc -> ZEISS Light Gray */ +.wy-menu-vertical li.current>a { + background: #B4C0CA; +} + +/* Menu level 2, changed from c9c9c9 -> ZEISS Ultralight Gray */ +.wy-menu-vertical li.toctree-l2.current>a { + background: #DCE3E9; +} + +/* Menu level 3, changed from #c9c9c9 -> ZEISS Semiwhite Gray */ +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + background: #F2F5F8; +} + + +/* Changed from #d6d6d6/#c9c9c9 -> ZEISS Azure */ +.wy-menu-vertical li.current a:hover, .wy-menu-vertical li.toctree-l2.current li.toctree-l3>a:hover { + background: #0072EF; +} + +/* Changed from #fcfcfc */ +.wy-nav-content-wrap, .wy-body-for-nav, .wy-nav-content, .wy-dropdown-menu { + background: #ffffff; +} + + +/****************************************************************************** + * Admonitions + ******************************************************************************/ + +/* Changed from #6ab0de -> ZEISS Saphire */ +.rst-content .note .admonition-title, .rst-content .seealso .admonition-title { + background: #4C6BB1; +} + +/* Changed from #e7f2fa -> ZEISS Ultralight Gray */ +.rst-content .note, .rst-content .seealso { + background: #DCE3E9; +} + +/* Changed from #1abc9c -> ZEISS Green */ +.rst-content .important .admonition-title, .rst-content .hint .admonition-title, .rst-content .caution .admonition-title { + background: #1E8565; +} + +/* Changed from #dbfaf4 -> ZEISS Ultralight Gray */ +.rst-content .important, .rst-content .hint, .rst-content .caution { + background: #DCE3E9; +} + +/* Changed from #f0b37e -> ZEISS Bright Orange Neon */ +.rst-content .warning .admonition-title, .rst-content .attention .admonition-title { + background: #E71E1E; +} + +/* Changed from # #ffedcc -> ZEISS Ultralight Gray */ +.rst-content .warning, .rst-content .attention { + background: #DCE3E9; +} + + +/****************************************************************************** + * Literal text / code examples + ******************************************************************************/ + +/* Changed from #e74c3c -> ZEISS Purple Red */ +.rst-content code.literal { + color: #A70240; +} + +/* Changed from #f8f8f8 -> ZEISS Semiwhite Gray */ +pre { + background: #F2F5F8; +} + +/* Changed from #3D7B7B (increased contrast) */ +.highlight .c1 { + color: #2D6B6B; +} + +/* Changed from #AA22FF (increased contrast) */ +.highlight .ow { + color: #9A12EF; + font-weight: bold; +} diff --git a/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Bold.woff2 b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Bold.woff2 new file mode 100644 index 0000000..b90718e Binary files /dev/null and b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Bold.woff2 differ diff --git a/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Medium.woff2 b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Medium.woff2 new file mode 100644 index 0000000..1f3ddbf Binary files /dev/null and b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Medium.woff2 differ diff --git a/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular-Italic.woff2 b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular-Italic.woff2 new file mode 100644 index 0000000..d12eb50 Binary files /dev/null and b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular-Italic.woff2 differ diff --git a/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular.woff2 b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular.woff2 new file mode 100644 index 0000000..44ffd0b Binary files /dev/null and b/doc/_static/fonts/ZEISS-Frutiger-Next-W02-Regular.woff2 differ diff --git a/doc/_static/fonts/ZEISSFrutigerNextUI-Bold.woff2 b/doc/_static/fonts/ZEISSFrutigerNextUI-Bold.woff2 new file mode 100644 index 0000000..d9d2699 Binary files /dev/null and b/doc/_static/fonts/ZEISSFrutigerNextUI-Bold.woff2 differ diff --git a/doc/_static/zeiss-logo-rgb.png b/doc/_static/zeiss-logo-rgb.png new file mode 100644 index 0000000..d8015dd Binary files /dev/null and b/doc/_static/zeiss-logo-rgb.png differ diff --git a/doc/conf.py b/doc/conf.py index 2e0f660..160d897 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,13 +7,13 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Add-On Documentation' -copyright = '2022, Carl Zeiss GOM Metrology GmbH' +copyright = '2023, Carl Zeiss GOM Metrology GmbH' author = 'Carl Zeiss GOM Metrology GmbH' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['myst_parser', 'sphinx_rtd_theme', 'sphinx_favicon', 'sphinx.ext.githubpages'] +extensions = ['myst_parser', 'sphinx_rtd_theme', 'sphinx_favicon', 'sphinx.ext.githubpages', 'sphinx_sitemap'] source_suffix = ['.rst', '.md'] templates_path = ['_templates'] @@ -29,13 +29,25 @@ myst_heading_anchors = 4 +# -- Options for sitemap ----------------------------------------------------- +# https://sphinx-sitemap.readthedocs.io/en/latest/getting-started.html +html_baseurl = 'https://zeissiqs.github.io/gom-software-python-api/2022/' +sitemap_url_scheme = "{link}" + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output #html_theme = 'alabaster' html_theme = "sphinx_rtd_theme" +# "Read the docs" appends " — documentation" to the page heading - +# this changes the title to "" +html_title = project html_static_path = ['_static'] +# -- Override some "sphinx_rtd_theme" styles to match ZEISS branding --------------- +# https://docs.readthedocs.io/en/stable/guides/adding-custom-css.html +html_style = "css/theme_zeiss.css" + favicons = [ { "rel": "icon", @@ -44,3 +56,6 @@ "type": "image/png", } ] + +# Source: https://brand.zeiss.com/cmsPublic/brandportal/basic-design-elements/logo-tagline.html +html_logo = "_static/zeiss-logo-rgb.png" diff --git a/doc/howtos/localization/assets/appdata_access.png b/doc/howtos/localization/assets/appdata_access.png new file mode 100644 index 0000000..f2e338a Binary files /dev/null and b/doc/howtos/localization/assets/appdata_access.png differ diff --git a/doc/howtos/localization/assets/edit_mode.png b/doc/howtos/localization/assets/edit_mode.png new file mode 100644 index 0000000..098ecaa Binary files /dev/null and b/doc/howtos/localization/assets/edit_mode.png differ diff --git a/doc/howtos/localization/assets/language_preferences.png b/doc/howtos/localization/assets/language_preferences.png new file mode 100644 index 0000000..1038f3f Binary files /dev/null and b/doc/howtos/localization/assets/language_preferences.png differ diff --git a/doc/howtos/localization/assets/start_from_menu.png b/doc/howtos/localization/assets/start_from_menu.png new file mode 100644 index 0000000..b23619d Binary files /dev/null and b/doc/howtos/localization/assets/start_from_menu.png differ diff --git a/doc/howtos/localization/assets/update_xliff.png b/doc/howtos/localization/assets/update_xliff.png new file mode 100644 index 0000000..c5be5ce Binary files /dev/null and b/doc/howtos/localization/assets/update_xliff.png differ diff --git a/doc/howtos/localization/assets/xliff_files.png b/doc/howtos/localization/assets/xliff_files.png new file mode 100644 index 0000000..a6badd2 Binary files /dev/null and b/doc/howtos/localization/assets/xliff_files.png differ diff --git a/doc/howtos/localization/localization.md b/doc/howtos/localization/localization.md new file mode 100644 index 0000000..2c14d3b --- /dev/null +++ b/doc/howtos/localization/localization.md @@ -0,0 +1,145 @@ +# Localization of packages + +## Writing translatable scripts + +### User-defined script dialogs + +When adding a user defined script dialog to a script, since software version 2022 the resulting code is in JSON compatible format and will contain translation entries for all translatable texts automatically. The user does not have to care for these entries manually. They are kept consistently when the dialog is edited again and will lead to translation file entries (see below). + +``` Python +DIALOG=gom.script.sys.create_user_defined_dialog (dialog={ + "content": [ + [ + { + "columns": 1, + "monospace": False, + "name": "log", + "rows": 1, + "save_dialog_title": { + "id": "", + "text": "Save Log File", + "translatable": True + }, + "scroll_automatically": True, + "show_save": False, + "tooltip": { + "id": "", + "text": "", + "translatable": True + }, + ... + ) +``` + +### Text in scripts + +Texts in script have to be tagged as translatable via using the `tr ()`  function. During translation file generation, these texts will be processed and later replaced at runtime with the available translations. + + print (tr ('This text will be translated')) + + +## Translating scripts + +### Generating translatable XLIFF files + +💡 Scripts are using standard XLIFF files to access translations in different languages. + +#### Install package 'Internationalization' + +* Install the package 'Internationalization'. +* Possible via package Manager or via downloading it from the connect directly plus drag/drop onto the application. + +#### Switch package into 'Edit' mode + +* Select the package the translations should be added to. +* Switch it into 'Edit' mode: + + ![Switch into edit mode](assets/edit_mode.png) + +#### Execute script 'Update XLIFF files' + +![Start from menu](assets/start_from_menu.png) + +* Execute script 'Update XLIFF files' from package 'Internationalization'. +* Select the package with the translations which shall be generated or updated (1) +* Set the comma separated list of language identifiers for which translation files will be generated (2) +* Check the displayed number of translated texts for plausibility (3) +* Press 'Update' (4) to generate translatable XLIFF files (4) + + ![Update XLIFF](assets/update_xliff.png) + +* Afterwards, the package's XLIFF translatable files will be present in the package's languages folder: + + ![XLIFF files](assets/xliff_files.png) + +#### Translate XLIFF files + +* Export the XLIFF files via 'Export Resource...' on the right mouse menu. +* Translate the XLIFF files. This can be done either manually or by importing them into a translation software, possibly via a translation service provider. +* Import the XLIFF files back into the package via 'Import Resource...' on the right mouse menu. + +``` XML + + + + + Save Log File + Protokolldatei speichern + + + Curve inspection + Kurveninspektion + + + Processing... + In Bearbeitung... + + + + +``` + +## Switching package languages + +### Enable language + +💡 The package language is the same as the globally set application language. + +#### Selecting an package/application language + +* Select the appropriate language the the applications preferences dialog. + + ![Update XLIFF](assets/language_preferences.png) + +* If a matching XLIFF file is present in an package, the translations from this file are used automatically. +* This might require an application restart due to caching issues. + +## FAQ + +### Is there a shortcut for exporting/importing the XLIFF files ? + +* If there are quite many of these files and the process has to be done regularly, the resource files can be accessed right on file system. +* Each package in 'Edit' mode mirrors its content into `%APPDATA%/gom//gom_package_scripts/`. +* The XLIFF files can be edited right there of copies/pasted from there as long as the package remains in 'Edit' mode. + +### Are the translation entries persistent when updated via the 'Update XLIFF files' script ? + +* As long as the original texts (the texts in the 'id' attribute of the 'trans-unit' tag of the XLIFF files) are not changing, already translated entries are left untouched and will persist. +* This is the case when the original text in the script does not change, like the text in a dialog button or the original text in a scripts 'tr ()' function. +* Can this scheme be automated, for example in a build queue ? +* The 'Update XLIFF files' script itself is designed to be interactive. +* But: The source is available and the logic within can be used as a base to implement a customized XLIFF files updater to automatic execution. +* The goal of the scheme is to have translated XLIFF files with a name scheme as shown above in the package's 'language' directory. + +### After the application language is set, the package is not displaying the translations for that language ? + +* You might have to restart the application after switching the application language in the preferences. +* Please double check, too,  if the package supports that specific language at all. + + diff --git a/doc/howtos/python_api_introduction/assets/built-in_progressbar.png b/doc/howtos/python_api_introduction/assets/built-in_progressbar.png new file mode 100644 index 0000000..88793cf Binary files /dev/null and b/doc/howtos/python_api_introduction/assets/built-in_progressbar.png differ diff --git a/doc/howtos/python_api_introduction/assets/dialog_custom_elem_select.py b/doc/howtos/python_api_introduction/assets/dialog_custom_elem_select.py new file mode 100644 index 0000000..84c41c3 --- /dev/null +++ b/doc/howtos/python_api_introduction/assets/dialog_custom_elem_select.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import gom + +DIALOG4=gom.script.sys.create_user_defined_dialog (dialog={ + "content": [ + [ + { + "columns": 1, + "name": "label_6", + "rows": 1, + "text": { + "id": "", + "text": "New", + "translatable": True + }, + "tooltip": { + "id": "", + "text": "", + "translatable": True + }, + "type": "label", + "word_wrap": False + }, + { + "columns": 1, + "fast_filter": False, + "name": "input_new", + "rows": 1, + "supplier": "custom", + "tooltip": { + "id": "", + "text": "", + "translatable": True + }, + "type": "input::point3d" + } + ] + ], + "control": { + "id": "OkCancel" + }, + "embedding": "", + "position": "", + "size": { + "height": 112, + "width": 200 + }, + "sizemode": "", + "style": "", + "title": { + "id": "", + "text": "Demo \"New selection element\"", + "translatable": True + } +}) + +def dialog_event_handler (widget): + pass + +# filter system planes +def element_filter( element ): + try: + if element.type == 'plane': + return True + except Exception as e: + pass + return False + +DIALOG4.handler = dialog_event_handler +DIALOG4.input_new.filter = element_filter + +RESULT=gom.script.sys.show_user_defined_dialog (dialog=DIALOG4) + +print("Chosen system plane:", RESULT.input_new.name) + + diff --git a/doc/howtos/python_api_introduction/creating_wizard_dialogs.md b/doc/howtos/python_api_introduction/creating_wizard_dialogs.md new file mode 100644 index 0000000..04eb17f --- /dev/null +++ b/doc/howtos/python_api_introduction/creating_wizard_dialogs.md @@ -0,0 +1,309 @@ +# Creating wizard dialogs + +- [What is a wizard dialog](#what-is-a-wizard-dialog) +- [Creating a simple wizard dialogs](#creating-a-simple-wizard-dialog) +- [Changing the button text](#changing-the-button-text) +- [Creating wizards with different layouts per step](#creating-wizards-with-different-layouts-per-step) +- [Final scripts](#final-scripts) + +## What is a wizard dialog + +A wizard dialog is a sequence of steps, which guides the user through a process flow. The wizard dialog has a back button to go to the previous step, a next button to proceed with the next step and a Close button to complete the wizard dialog. The wizards layout is created through the scripting dialog editor like any other dialog and its transition logic for going through the steps is specified by a handler function. + +💡 Note that it is only possible to change the content of the wizards' widgets in the handler function. This means particularly, that it is not possible to remove or add widgets after the definition of the wizards' layout. + +## Creating a simple wizard dialog + +The screenshots below show a wizard with three steps, which will be created in this tutorial. + +![Step 1](assets/wizard_dialog_step_1.png) ![Step 2](assets/wizard_dialog_step_2.png) + +![Step 3](assets/wizard_dialog_step_3.png) ![Step 4](assets/wizard_dialog_step_4.png) + +The wizard has to following properties: + +* The wizard's layout consists of an image widget, a text widget, an input widget and the default wizard control elements (**Next**, **Back** , **Close**). +* On script startup the dialog already contains the text and image for the first instruction set. +* If the **Next** or **Back** button is pressed, the corresponding instruction text and image must be displayed. +* To avoid loading the following images from an external source (which would have to be distributed together with the script), the images are encoded in dummy dialogs and read from these sources. +* The dummy dialogs are organized in an array. + +Firstly, the wizard dialog and a dummy dialog for each wizard step is created. A dummy dialog holds the content of the wizards widgets for its respective step. The screenshots above show these dummy dialogs. + +```Python +# +# create main wizard dialog: +# +DIALOG=gom.script.sys.create_user_defined_dialog (dialog={'...'}) + +# +# Dummy dialogs containing the images of the corresponding steps encoded in JSON +# +STEP_1=gom.script.sys.create_user_defined_dialog (dialog={'...'}) + +STEP_2=gom.script.sys.create_user_defined_dialog (dialog={'...'}) + +STEP_3=gom.script.sys.create_user_defined_dialog (dialog={'...'}) + +STEP_4=gom.script.sys.create_user_defined_dialog (dialog={'...'}) + +# +# The dummy dialogs are organized in an array for a simple step transition logic +# +steps = [STEP_1, STEP_2, STEP_3, STEP_4] +``` + +Note that the wizard dialog and the dummy dialogs have to be non-blocking dialogs. Note also, that the content of STEP_1 and DIALOG are identically. This is necessary, since the content of DIALOG will be overridden in the handler function. Let us create the handler function for managing the step transition: + +```Python +count = 0 + +# +# handler function for step transition +# +def handler_func (widget): + global count + + # save input to dummy dialog: + steps[count].noteInput.value = DIALOG.noteInput.value + + # + # 'next' button has been pressed + # + if widget == DIALOG.control.next: + if count + 1 < len (steps): + count += 1 + DIALOG.noteInput.value = steps[count].noteInput.value + DIALOG.text.text = steps[count].text.text + DIALOG.image.data = steps[count].image.data + + # + # 'prev' button has been pressed + # + elif widget == DIALOG.control.prev: + if count > 0: + count -= 1 + DIALOG.noteInput.value = steps[count].noteInput.value + DIALOG.text.text = steps[count].text.text + DIALOG.image.data = steps[count].image.data + + # + # Update enabled state of the dialog control elements + # + DIALOG.control.prev.enabled = count > 0 + DIALOG.control.next.enabled = count + 1 < len (steps) + DIALOG.control.close.enabled = count + 1 >= len (steps) +``` + +Note that `noteInput` is the object name of the text entry field assigned within the scripting dialog editor. +Finally we register the handler function to the dialog and display the wizard dialog: + +```Python +DIALOG.handler = handler_func +RESULT=gom.script.sys.show_user_defined_dialog (dialog=DIALOG) +``` + +## Changing the button text + +You can easily change the text of the **Next** and **Back** buttons: + +```Python +DIALOG.control.next.text = "randomButtonText" +DIALOG.control.prev.text = "anotherRandomButtonText" +``` + +## Creating wizards with different layouts per step + +Despite the limitations mentioned above, you may use a different layout for each step in the wizard by using a more sophisticated step transition logic. Instead of changing the content of the displayed wizard dialog, you may display another wizard at each step. Let us go through the code necessary for doing that. +First of all we have to define appropriate dialogs: + +```Python + +DIALOG=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) +STEP_2=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) +STEP_3=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) +STEP_4=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) +``` + +This time, STEP_2, STEP_3 and STEP_4 won´t be simple dummy dialogs used as containers, they have to be wizard dialogs instead. Now let us define the handler function: + +```Python +steps = [DIALOG, STEP_2, STEP_3, STEP_4] +inputStrings = ['', '', '', ''] +current_step = -1 +new_step = 0 + +def handler_func (widget): + global current_step, new_step + global current_dialog + + if widget == current_dialog.control.next: + if current_step + 1 < len (steps): + new_step = current_step + 1 + gom.script.sys.close_user_defined_dialog (dialog=current_dialog) + elif widget == current_dialog.control.prev: + if current_step > 0: + new_step = current_step - 1 + gom.script.sys.close_user_defined_dialog (dialog=current_dialog) + + + current_dialog.control.prev.enabled = current_step > 0 + current_dialog.control.next.enabled = current_step + 1 < len (steps) + current_dialog.control.close.enabled = current_step + 1 >= len (steps) + +``` + +The basic idea is to close the current dialog if the user hits the **Next** button to proceed to the next dialog step or the **Back** button to return to the previous one. The next code snippet will make it clear: + +```Python +while current_step != new_step : + current_step = new_step + current_dialog = steps[current_step] + current_dialog.handler = handler_func + RESULT=gom.script.sys.show_user_defined_dialog (dialog=current_dialog) + inputStrings[current_step] = RESULT.noteInput +``` + +The program will enter the while loop, if the the wizard step changed (or due to initialization) and launch a new dialog. As long as the user clicks **Next** or **Back** in the current dialog, the while loop won't be left and "the" wizard will be displayed. If the user leaves the dialog through **Close**, the while loop will be left and "the" wizard terminates. `noteInput` is the object name of the text entry field within the scripting dialog editor as before. Please note that the `.value` suffix has to be omitted here. + +## Final scripts + +The following scripts combine the code snippets above. The first one is the single layout wizard, the second one the multi layout wizard. + +### Single Layout Wizard + +[SingleLayoutWizard.py](assets/SingleLayoutWizard.py) + +```Python +# -*- coding: utf-8 -*- + +# Script: SingleLayoutWizard.py + +import gom + +# +# create main wizard dialog: +# +DIALOG=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +# +# Dummy dialogs containing the images of the corresponding steps encoded in JSON +# +STEP_1=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_2=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_3=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_4=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +# +# The dummy dialogs are organized in an array for a simple step transition logic +# +steps = [STEP_1, STEP_2, STEP_3, STEP_4] + +count = 0 + +# +# Registered handler function +# +def handler_func (widget): + global count + + # save input to dummy dialog: + steps[count].noteInput.value = DIALOG.noteInput.value + + # + # 'next' button has been pressed + # + if widget == DIALOG.control.next: + if count + 1 < len (steps): + count += 1 + DIALOG.noteInput.value = steps[count].noteInput.value + DIALOG.text.text = steps[count].text.text + DIALOG.image.data = steps[count].image.data + # + # 'prev' button has been pressed + # + elif widget == DIALOG.control.prev: + if count > 0: + count -= 1 + DIALOG.noteInput.value = steps[count].noteInput.value + DIALOG.text.text = steps[count].text.text + DIALOG.image.data = steps[count].image.data + + # + # Update enabled state of the dialog control elements + # + DIALOG.control.prev.enabled = count > 0 + DIALOG.control.next.enabled = count + 1 < len (steps) + DIALOG.control.close.enabled = count + 1 >= len (steps) + +DIALOG.handler = handler_func + +RESULT=gom.script.sys.show_user_defined_dialog (dialog=DIALOG) + +# Access dialog input strings: +print( steps[0].noteInput.value ) +print( steps[1].noteInput.value ) +print( steps[2].noteInput.value ) +print( steps[3].noteInput.value ) +``` + +### Multi Layout Wizard + +[MultiLayoutWizard.py](assets/MultiLayoutWizard.py) + +```Python +# -*- coding: utf-8 -*- + +# Script: MultiLayoutWizard + +import gom + +DIALOG=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_2=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_3=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +STEP_4=gom.script.sys.create_user_defined_dialog (dialog={...Boring dialog definition...}) + +current_step = -1 +new_step = 0 +steps = [DIALOG, STEP_2, STEP_3, STEP_4] +inputStrings = ['', '', '', ''] + +def handler_func (widget): + global current_step, new_step + global current_dialog + + if widget == current_dialog.control.next: + if current_step + 1 < len (steps): + new_step = current_step + 1 + gom.script.sys.close_user_defined_dialog (dialog=current_dialog) + elif widget == current_dialog.control.prev: + if current_step > 0: + new_step = current_step - 1 + gom.script.sys.close_user_defined_dialog (dialog=current_dialog) + + + current_dialog.control.prev.enabled = current_step > 0 + current_dialog.control.next.enabled = current_step + 1 < len (steps) + current_dialog.control.close.enabled = current_step + 1 >= len (steps) + +while current_step != new_step : + current_step = new_step + current_dialog = steps[current_step] + current_dialog.handler = handler_func + RESULT=gom.script.sys.show_user_defined_dialog (dialog=current_dialog) + print(RESULT.noteInput) + inputStrings[current_step] = RESULT.noteInput + + +# Print out the acquired input: +print(inputStrings[0]) +print(inputStrings[1]) +print(inputStrings[2]) +print(inputStrings[3]) +``` diff --git a/doc/howtos/python_api_introduction/python_api_introduction.md b/doc/howtos/python_api_introduction/python_api_introduction.md index 544688c..28baa6b 100644 --- a/doc/howtos/python_api_introduction/python_api_introduction.md +++ b/doc/howtos/python_api_introduction/python_api_introduction.md @@ -1,18 +1,281 @@ -# Python API introduction +--- +myst: + html_meta: + "description": "Introduction to the Python API for extending GOM Inspect 2022 with Packages/Add-ons" + "keywords": "Metrology, GOM Inspect, Python API, GOM API, Scripting, Packages, Add-ons, How-tos" +--- + +# GOM Inspect Python API introduction + +Welcome to the GOM Inspect Python API introduction. This is your starting point into Add-on development for GOM Inspect. Here you find out what you can do with Add-ons, how they work and how you create them. + +See [Introduction to Python Scripting](https://techguide.gom.com/en/gom-software-2022/article/introduction_to_python_scripting.html) if you are new to Python or the GOM Software Python interface. + +## Creating projects + +You can create a new, empty project as follows: + +``` Python +# Create a new project +gom.script.sys.create_project () +``` ```{note} -The basic introduction for the Python API used to be part of the GOM FAQ: +💡 This way, a project in the default workflow (part-based) is created. For the legacy workflow (part-less), please refer to [Legacy projects \(part-less\)](#legacy-projects-part-less) +``` + +## Creating parts + +The evaluation elements are usually placed under a "part". Accordingly, an empty part can be created as follows: + +``` Python +# Creates a new part in your project with the name 'Part 1' +PART_OBJECT = gom.script.part.create_new_part (name='Part 1') +``` + +An existing part can be accessed via name or index in the new category: + +``` Python +# Access the part object via name +PART_OBJECT = gom.app.project.parts['Part 1'] + +# or via index +PART_OBJECT = gom.app.project.parts[0] + +# you can access all parts via iterating over the parts attribute +for p in gom.app.project.parts: + print (p) +``` + +## Adding elements to a part + +If an actual mesh or CAD or elements are imported into a part project, different options are available. For scripting, the import is always separated into two different steps. + +1. Importing the elements into the *Elements in clipboard* +2. Add the imported elements to a created part + +Example: + +``` Python +gom.script.part.create_new_part (name='Part 1') + +# import element into 'Element in clipboard' +gom.script.sys.import_g3d ( + files=['D:/gom_part.g3d'], + import_mode='clipboard' +) + +# Move it to a created part +gom.script.part.add_elements_to_part ( + delete_invisible_elements=True, + elements=[gom.app.project.clipboard.actual_elements['gom_part']], + import_mode='new_elements', + part=gom.app.project.parts['Part 1']) + +``` + +In the same way, a CAD can be imported into a part project. + +Constructing new elements based on elements which already belong to a part are automatically added to that part. + +## Accessing CAD and actual mesh + +A **mesh element** of a part can be accessed in the following ways: + +``` Python +# This is usually the most elegant way and is recommended. +# Replacing your mesh will keep the correct parametric if you use +# the actual values reference (like formerly the 'Actual master' in the part-less workflow). +PART_OBJECT = gom.app.project.parts['Part 1'] +MESH_PROXY = gom.ActualReference (PART_OBJECT) + +# If you really like to access the representing CAD Element (which is not always existing, e.g. for planning) +# you can access this element via +MESH_ELEMENT = gom.ActualReference(PART_OBJECT).actual + +``` + +The **CAD** of a part can be addressed in the following ways: + +``` Python +# This is usually the most elegant way and is recommended. +# Replacing your CAD will keep the correct parametric if you use +# the nominal values reference (like formerly the 'All CAD Group proxy' in the part-less workflow) +PART_OBJECT = gom.app.project.parts['Part 1'] +CAD_PROXY = gom.NominalReference (PART_OBJECT) + +# If you really like to access the representing CAD Element (which is not always existing, e.g. for planning) +# you can access this element via +CAD_ELEMENT = gom.NominalReference(PART_OBJECT).nominal +``` + +## Accessing an object's part + +This information can be retrieved via an attribute of the object. Here is an example: + +``` Python +PART_OBJECT = gom.app.project.inspection['Point 1'].part +``` + +## Accessing all elements and alignments of a part + +One possibility is to iterate over all elements in the different categories (nominal, actual, inspection, alignments) and to check the part attribute. Usually, for most applications this can be achieved easier using an `ElementSelection` (some elements are hidden and not listed, but usually you do not need access to these elements). Here is an example: + +``` Python +for x in gom.ElementSelection ({'category': ['key', 'elements', 'part', gom.app.project.parts['Part']]}): + print (x) +``` + +## Accessing elements and alignments of *Element in clipboard* + +Also, these elements can be easily accessed via an `ElementSelection`: + +``` Python +for x in gom.ElementSelection ({'category': ['key', 'elements', 'is_element_in_clipboard', 'True']}): + print (x) +``` + +## Alignments + +Alignments are still accessible via `gom.app.project.alignments` (like in the legacy workflow). The same is valid for `gom.app.project.nominal_elements`, `gom.app.project.actual_elements` and `gom.app.project.inspection`. You have to keep in mind that you will get all elements or alignments of all parts. You can use the `part` token to establish to which part an alignment belongs to or you use an `ElementSelection` (see examples above): + +``` Python +for x in gom.ElementSelection ({'category': ['key', 'elements', 'part', gom.app.project.parts['Part'], 'explorer_category', 'alignment']}): + print (x) +``` + +There is one special handling for multiple parts and scripting with the *Original alignment*. To distinguish these alignments for different parts, the scripting name contains the part name and is modified into: + +``` Python +gom.app.project.alignments['::Original alignment'] +``` + +## Legacy projects (part-less) + +```{note} +⚠️ You can skip this section unless you are dealing with legacy projects! +``` + +As shown above, the following code creates a project in the part-based workflow, which allows to analyze multiple parts: + +``` Python +# Create a project (default; part-based workflow) +gom.script.sys.create_project () -[Python API introduction - GOM FAQ](https://connect.gom.com/display/GKB/Expert+Knowledge+-+Scripting+in+GOM+Software) +# For compatibility, the kind of project can be stated explicitely: +# Create a project (part-based workflow) +gom.script.sys.create_project (type=2018) +``` + +You can still create a project in the legacy workflow, which is restricted to a single part: + +``` Python +# Create a project (part-less workflow) +# Note: This allows to analyze a single part only! +gom.script.sys.create_project (type=2016) +``` + +Use the following code to determine the type of an existing project: + +``` Python +if gom.app.project.is_part_project: + print ('Is part project: True') +else: + print ('Is part project: False') +``` + +In former projects, the CAD or the actual mesh have been accessed via their proxies: + +*All CADGroup proxy* + +*Actual Master* + +### Measurement series and Measuring environment + +The access and information about the VMR, measuring setups and measurement series are much more separated in the part-based workflow as compared to the part-less workflow. New categories and `ElementSelection` categories were introduced. If you have further questions regarding scripting purposes, please contact the ZEISS support. + +Here just the most important information follows. The measurements have their own category in the part-based workflow. A measurement can be accessed via the attribute `measurement_series` and no longer via `actual_elements`. + +``` Python +MEASUREMENT = gom.app.project.measurement_series['Scan 1'] + +MEASUREMENTS = gom.ElementSelection ({'category': ['key', 'elements', 'explorer_category', 'measurements', 'object_family', 'measurement_series']}) +``` + +### Reports + +Reports did not change so much in the part-based workflow. Reports are not assigned to a part, they show the situation of measurement series, VMR or multiple parts at the same time. Some slight changes must be taken into account: The keyword `alignment` could now return names of more than one alignment (comma separated, i.e. a Python list) due to the fact that multiple parts (if visible in the report page) with their own alignment must be respected. + +### CAD structure and contents + +For scripting with CADs in part-based projects, some additional information are necessary. Most importantly: + +Only one CAD Group per part is supported. + + +The *CAD Group* (in a part-based workflow) supports now the access via an internal file structure to represent structures stored in CAD files like *CATProduct*. These entities are imported in the part-less workflow as separated *CAD Groups*. + +The following simple code examples show how to access bodies or files of a CAD Group. + +⚠️ **Keep in mind:** This access gives you information about the bodies or files. You can use this also for the deletion of bodies. Visibility operations are suppressed by intention. Usage for construction purposes is not recommended due to the fact that exchanging CAD structures would **not** work as expected. + +``` Python +# this example code list all bodies of all CADs for all parts +cad = filter (lambda c: c.type == "cad", gom.app.project.nominal_elements) +for c in cad: + print (c.name, c.type) + for b in c.bodies: + print (b.name) +``` + +This is an example to list files by `cat_part`: -Content will be migrated to here shortly. +``` Python +cad = filter (lambda c: c.type == "cad", gom.app.project.nominal_elements) + +# for all cads in all parts list the file structure of each CAD of one part and furthermore all bodies of each file +for c in cad: + print (c.name) + print ('Number of files: ' + str (len (c.file))) + for file in c.file: + print (file) + for b in file.bodies: + print (' {}'.format (b.name)) ``` +### CAD assembly structure in the *Elements in clipboard* + +These information are usually not necessary for scripting. The most important fact is that the assembly structure does not represent our CAD structure. The assembly structure shows much more internal details. + +### Script compatibility + +#### Actual master and All CAD Group proxy +For the single part workflow old scripts using *Actual Master* and the *All CAD Group proxy* in creation commands should usually work. + +⚠️ **Keep in mind:** The functionality of these proxies is no longer present in the part-based workflow. Therefore functionalities like *Define Actual Master* will not work anymore. If the scripting tries to resolve the elements for the *Actual Master* a mapping takes place and the *ActualValues-Reference* of the unique part is returned. The same happens for the *All CAD Group proxy* and the *NominalValues-Reference*. + +#### Measurement series +The script compatibility for the current measurement list is given in most cases. Normal information is usually accessible via the same attributes as before. + +#### Tesselate Geometrical Element +The obsolete command `gom.script.mesh.tesselate_geometrical_element` creates an element of type mesh. + +In the part-based workflow this command has been replaced with the command `gom.script.primitive.tesselate_geometrical_element`, which creates elements of type surface. + ## Access element properties The "elements" of the GOM Software, i.e. what you see in the element explorer of your project, hold several properties and data which you can access using the Python API. -First of all, you need a reference to an element, which you can get either by [using a script dialog](https://connect.gom.com/display/GKB/Scripting+-+User-Defined+Dialogs) or simply by referencing it by name. +First of all, you need a reference to an element, which you can get either by [using a script dialog](script_dialogs_introduction.md) or simply by referencing it by name. If you have installed the `Python API Examples` add-on from the store and loaded the `gom_part_test_project` (see the [Python API Examples Documentation](../../python_examples/index.md)), you can reference the mesh like this: @@ -75,12 +338,78 @@ In this way, you can get simple properties of a different stage. If you need acc ## Element data interfaces -```{note} -This documentation used to be part of the GOM FAQ: +In a script, each element present in the application (project, inspection elements, reports, ...) can be referenced by a named identifier. Each identifier consists of a type and some index, which can be a name or an integer number. + +### Concept +This is already the case for access via the data interface instead of the token interface: The returned value has the format `(stages, )`. + +```{code-block} python +:caption: Example 1 - Mesh + +# Mesh in 8 stage trend project with 238654 points, each with one (x, y, z) triple +> print (gom.app.project.parts['Part'].actual.data.coordinate.shape) +(8, 238654, 3) +``` + +```{code-block} python +:caption: Example 2 - Scalar value + +# Single scalar value in a 8 stages trend project +> print (gom.app.project.inspection['My Check'].data.result_dimension.deviation.shape) +(8, 1) +``` + +```{code-block} python +:caption: Example 3 - Image + +# Image in 8 stage trend project with 4000x3000 pixels, each with one (r, g, b, a) tuple +> print (gom.app.project.measurement_list['Scan'].measurement['M1'].images['Left camera'].data.rgb.shape) +(8, 3000, 4000, 4) +``` + +⚠️ When accessing the image, the complete data set is *not* transferred immediately from C++ to Python! Instead this happens just in the moment when it is converted into a numpy array! + +```{code-block} python +:caption: Example 4 - Transferring image data + +# Fetch gom.Array with image data in all stages (not transferred !) +> image = gom.app.project.measurement_list['Scan'].measurement['M1'].images['Left camera'].data.rgb +> print (image.shape, type (image)) +(8, 3000, 4000, 4), gom.Array () +# 45 MB Image data if on stage, transferred in < 100 ms +> data = np.array (image[0]) +> print (data.shape) +(3000, 4000, 4) +``` + + diff --git a/doc/howtos/python_api_introduction/script_dialogs_introduction.md b/doc/howtos/python_api_introduction/script_dialogs_introduction.md index 8bbc378..c59936d 100644 --- a/doc/howtos/python_api_introduction/script_dialogs_introduction.md +++ b/doc/howtos/python_api_introduction/script_dialogs_introduction.md @@ -41,13 +41,15 @@ --> ```{code-block} python - :caption: Example: Script with separate dialog file + :caption: Example: Script with separate dialog file + RESULT=gom.script.sys.execute_user_defined_dialog (file=':dialog.gdlg') ``` ```{code-block} python - :caption: Example: Script with embedded dialog + :caption: Example: Script with embedded dialog + RESULT=gom.script.sys.execute_user_defined_dialog (dialog={ "content": [ @@ -292,21 +294,21 @@ Continuous text widget | ![](assets/edit_text.png) | ![](assets/widget_text.png) | -[//]: # (* The keywords displayed in text field widgets can originate from different source:) - -[//]: # ( * Global application keywords) - -[//]: # ( * project related keywords) - -[//]: # ( * local script variables.) - -[//]: # (⚠️ Local script variables can be displayed in text fields by inserting them via the 'insert expression' dialog.) - -[//]: # ( * Local script variables are invalid until the variable assignment is reached. They cannot be displayed statically in the text) - -[//]: # (field editor prior to script execution, so an invalid value will most certainly be displayed instead.) +%* The keywords displayed in text field widgets can originate from different source: +% +% * Global application keywords +% +% * project related keywords +% +% * local script variables. +% +%⚠️ Local script variables can be displayed in text fields by inserting them via the 'insert expression' dialog. +% +% * Local script variables are invalid until the variable assignment is reached. They cannot be displayed statically in the text +% field editor prior to script execution, so an invalid value will most certainly be displayed instead. +% +% To Do: Check how to insert local variables -[//]: # (To Do: Check how to insert local variables) | Property | Type | Example | | ------------------- | ---- | ---------------------------------------------------------- | @@ -417,7 +419,7 @@ RESULT=gom.script.sys.execute_user_defined_dialog (dialog={ { "columns": 1, "data": "AAAAAYlQTkcNChoKAAAADUlIRFIAAAQAAAACQAgCAAAAnPeDgptZSsdt...", - "file_name": "C:/Users/IQMPRINK/Downloads/zeiss-inspect_python.jpg", + "file_name": "zeiss-inspect_python.jpg", "height": 144, "keep_aspect": True, "keep_original_size": False, @@ -667,9 +669,8 @@ userInput = RESULT.decimalWidget | precision | double |
# Set precision to 2 decimals
DIALOG.input.precision = 2
| | unit | str |
# Set unit ID
DIALOG.input.unit = 'LENGTH'
| -[//]: # ( No visible effect ) - -[//]: # ( background_style - str - Set style sheet based background color - red, green, blue ) +% No visible effect: +% background_style - str - Set style sheet based background color - red, green, blue #### Text entry field ![](assets/widget_text_entry.png) @@ -721,9 +722,9 @@ print( RESULT.sliderWidget ) # some text | step | double |
# Set step size to 15
DIALOG.input.step = 15
| | orientation | str |
print(DIALOG.input.orientation)
⚠️ read-only | -[//]: # ( ticks are not drawn ) +% ticks are not drawn: +% tick_interval - double - Interval of ticks drawn -[//]: # ( tick_interval - double - Interval of ticks drawn ) #### Checkbox widget @@ -770,9 +771,8 @@ File widget | file_types | list |
# Show only specified file types; each list item must consist of \[\, \\]
DIALOG.inputFile.file_types = \[\['*.g3d', 'Mesh data'\], \['*.stp', 'CAD data'\]\]
⚠️ ``limited`` must be set to ``True`` in order to apply the filter! | | limited | bool |
# Limit file selection to 'file_types'
DIALOG.inputFile.limited = True
| -[//]: # (Clarify this) - -[//]: # (selection_type - str - File selector type; any, directory, executable, file, multi_file ) +% Clarify this: +% selection_type - str - File selector type; any, directory, executable, file, multi_file #### Date widget @@ -924,9 +924,7 @@ RESULT=gom.script.sys.show_user_defined_dialog (dialog=DIALOG4) print("Chosen system plane:", RESULT.input_new.name) ``` -The complete code of the example is attached to this document. - -[//]: # (To Do: attach example) +Please find the complete example here: [dialog_custom_elem_select.py](assets/dialog_custom_elem_select.py) #### Selection list widget @@ -1012,9 +1010,9 @@ if selectedChoice == 'ONE': Abort button widget : The Abort button widget aborts the current action. It is disabled if no action is currently executed. It behaves in the same manner as the abort button in the lower right corner of the ZEISS Inspect software. -[//]: # (It behaves in the same manner as the abort button in the lower right corner of Atos **???** software.) +![](assets/built-in_progressbar.png) -[//]: # (To Do: Add enabled abort button. Check if the button still exists in ZEISS Inspect.) +% To Do: Add enabled abort button. Check if the button still exists in ZEISS Inspect. #### Tolerances widget @@ -1367,6 +1365,7 @@ DIALOG.handler = handler_function RESULT=gom.script.sys.show_user_defined_dialog (dialog=DIALOG) ``` + A complete example with a handler function can be found in the file [scriptingEditorExampleDialog.py](assets/scriptingEditorExampleDialog.py). The argument passed to the event handler is either the dialog widget (e.g. a button) which triggered the event handler or a string. The following table lists all possible strings: | Value | Description | diff --git a/doc/howtos/scripted_elements/assets/user_defined_tokens-1.png b/doc/howtos/scripted_elements/assets/user_defined_tokens-1.png new file mode 100644 index 0000000..d4a19ec Binary files /dev/null and b/doc/howtos/scripted_elements/assets/user_defined_tokens-1.png differ diff --git a/doc/howtos/scripted_elements/assets/user_defined_tokens-2.png b/doc/howtos/scripted_elements/assets/user_defined_tokens-2.png new file mode 100644 index 0000000..8ea94da Binary files /dev/null and b/doc/howtos/scripted_elements/assets/user_defined_tokens-2.png differ diff --git a/doc/howtos/scripted_elements/scripted_elements_toc.md b/doc/howtos/scripted_elements/scripted_elements_toc.md index c9b00cc..be1a1e1 100644 --- a/doc/howtos/scripted_elements/scripted_elements_toc.md +++ b/doc/howtos/scripted_elements/scripted_elements_toc.md @@ -10,4 +10,5 @@ The following how-tos will introduce the concept of scripted elements, i.e. elem scripted_elements_introduction scripted_actuals scripted_checks + tokens_on_scripted_elements ``` diff --git a/doc/howtos/scripted_elements/tokens_on_scripted_elements.md b/doc/howtos/scripted_elements/tokens_on_scripted_elements.md new file mode 100644 index 0000000..3f973b6 --- /dev/null +++ b/doc/howtos/scripted_elements/tokens_on_scripted_elements.md @@ -0,0 +1,63 @@ +# Tokens on scripted elements + +```{note} + This section assumes that you are familiar with the basic concept of Scripted elements. If not, take a look at the [Introduction](scripted_elements_introduction.md), [Scripted actuals](scripted_actuals.md) and [Scripted checks](scripted_checks.md) sections. +``` +## Introduction + +This page describes how user-defined tokens can be set to user-defined elements (UDEs). + +## Instructions + +### How user-defined tokens are set + +In the calculation function that generated the results, the `context.data[]` field can be read and written. + +In the following example, a volume defects element was created with following snippet inside the `calculation()` function: + +``` Python +# Simulating user-defined token data +ids = np.array ([ i for i in range (166]) +for s in context.stages: + context.data[s] = { "ude_defect_ids" : ids, "ude_test_string" : "hello world" } +``` + +Generally, the `context.data[]` field can be used to store intermediate data between the `dialog()` and the `calc()` function, e.g. if you – for preview reasons – pre-calculated some values, you can reuse them in the calc() function. + +Starting with SW2021 (Final) however, if you set the `context.data[s]` to hold a Python dictionary – i.e. a key-value map – for each stage `s`, then this data will be interpreted as 'token' data. These are generally data entities set to a GOM element, which can be retrieved using a key-string (the token). + +With this user-defined token mechanism, you are free to set any data structure as token data to the element you are creating as UDE, as long as it is serializable by the GOM scripting framework. You can use therefore: basic Python data types, numpy arrays, the GOM data types such as GOM.Vec3D, and finally lists and dictionaries of the previously mentioned types. + +In the example given above, we create a numpy array with IDs, one for each defect, and set this together with an exemplary test string as token data to the element. + +```{note} +**Note: Naming of user-defined tokens** + +⚠️ The keys in the key-value map of user-defined tokens **must** start with the prefix `ude_`. If this is not the case, the token will not be set. +``` + +### Usage of user-defined tokens in scripts + +Let's see how this data can be retrieved after the element was successfully created: + +1. Open the script editor and create a new script. In the new script, press "F2" or right-click → Insert → Element Value. + + In the appearing dialog, search for the volume defects element, and you will find the user-defined data in the "User-defined keywords" section. + + ![](assets/user_defined_tokens-1.png) + + For simple data types, you will get a preview (see Screenshot). For more complex types, including numpy arrays, there is no preview. The further usage works nevertheless. + +2. If you click "OK", the syntax how to retrieve the data is inserted in your new script. Of course, it is also possible to obtain the data from the element, if you have got a reference to it in a dialog selector or similar. + +3. You can now use the data in follow-up scripts. + + ![](assets/user_defined_tokens-2.png) + +### Usage in user-defined checks + +Just as all other tokens on the element, you can access the data in user-defined checks. + +For simple data types, this is straightforward. To make this work for indexed data, as in the example of "defect_ids", you have to make sure that the array data you set to the token has the same length as the indices of the element. + +In the example above, the defect element gets 166 meshes, which will result in indices from 0 to 165. diff --git a/doc/index.md b/doc/index.md index 923023b..2752a28 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,6 +1,13 @@ -# Add-On Documentation +--- +myst: + html_meta: + "description": "Introduction to the Python API for extending GOM Inspect 2022 with Packages/Add-ons" + "keywords": "Metrology, GOM Inspect, Python API, GOM API, Scripting, Packages, Add-ons, How-tos" +--- -Welcome to the Add-On documentation. With Add-Ons, you will be able to customize and extend the functionality of your GOM Inspect software. +# GOM Inspect Add-On Development Documentation + +Welcome to the GOM Inspect Add-On development documentation. With Add-Ons, you will be able to customize and extend the functionality of your GOM Inspect software. You can include several template configurations from the software, as well as completely new workflows programmed in python. ```{note} @@ -26,10 +33,12 @@ If you are new to add-ons, we recommend following our how-to guides to get you s howtos/environments_for_python_scripts/environments_for_python_scripts howtos/python_api_introduction/python_api_introduction howtos/python_api_introduction/script_dialogs_introduction + howtos/python_api_introduction/creating_wizard_dialogs howtos/python_api_introduction/using_script_resources howtos/scripted_elements/scripted_elements_toc howtos/adding_workspaces_to_packages/adding_workspaces_to_packages howtos/using_vscode_editor/using_vscode_editor + howtos/localization/localization howtos/testing_addons/testing_addons ``` diff --git a/doc/python_api/python_api.md b/doc/python_api/python_api.md index 655520b..4c7c747 100644 --- a/doc/python_api/python_api.md +++ b/doc/python_api/python_api.md @@ -1,10 +1,12 @@ -# Python API functions - -## Basics - -```{note} -This document describes the emerging GOM Python API. -``` +--- +myst: + html_meta: + "description": "GOM Inspect 2022 Add-on Python API Specification" + "keywords": "Metrology, GOM Inspect, Python API, GOM API, Scripting, Packages, Add-ons, Specification, Documentation" +--- +# GOM Inspect Python API documentation + +Welcome to the GOM Inspect Python API documentation. Here you can find a detailed documentation of a subset of the Add-on programming specification. Please bear in mind, that recording commands with the script editor can be used to add new functions to your script. ```{important} The now preliminary API is currently under heavy development and will change massively in the near future. diff --git a/doc/python_api/scripted_elements_api.md b/doc/python_api/scripted_elements_api.md index e776afe..3aa0f24 100644 --- a/doc/python_api/scripted_elements_api.md +++ b/doc/python_api/scripted_elements_api.md @@ -8,44 +8,344 @@ If you don't know about the concept yet, take a look at the [Scripted elements i ## The `dialog` function -```{note} -This content was previously part of the GOM Connect. See [here](https://connect.gom.com/display/GPF/Scripted+Elements+API+Reference). -% TODO: -Content will be migrated shortly. +💡 **Notes:** + +* The main purpose of this function is to use display a script dialog and allow the user to enter element parameters. +* All tokens of other elements can be accessed within this function. + +### Signature + +```python +def dialog(context, params): ``` -Notes: +#### Parameter `context` -* The main purpose of this function is to use display a script dialog and allow the user to enter element parameters. -* All tokens of other elements can be accessed within this function. +The context contains the following members: -% Parameter: `context`: ... +| Name | Role | Remarks | +| -------------------------- | -------------------------------------------------- | -------------------------------------------------------------- | +|
.data
| UDE data storage access | see "calculate" Function | +|
.stages
| Current stage | List containing stage index of current stage | +|
.total_stages
| Number of stages | Valid stage indices are 0 to total_stages - 1 | +|
.calc
| Function to calculate preview | See below | +|
.result
| Directly set preview | see "calculate" Function | +|
.is_new_element
| Flag if element creation | True on (edit) creation, false on recalc | +|
.name
| Name of created element | Read/write attribute
Ignored on recalc and script execution | +|
.error
| Last error text from preview calculation | Empty if no error occurred | +|
.edit_element
| Reference to edited element | Only available during element editing | +|
.recalc_element
| Reference to element used in project recalculation | | +#### Parameter `params` -## The `calculation` function +The params contain a map of parameter values. It is only filled on element edit and contains the current parameters of the element. -```{note} -This content was previously part of the GOM Connect. See [here](https://connect.gom.com/display/GPF/Scripted+Elements+API+Reference). -% TODO: -Content will be migrated shortly. -``` +#### Return value + +The function must return a map containing the new element parameters. + +If no map is returned the edit/creation is regarded as aborted by the user. + +### Calculating a preview + +To calculate a preview, the `context.calc()` function can be invoked. It takes the following parameters: + +| Name | Role | +| ----------------- | ---------------------------------------------------------------------------------- | +|
params
| A map of parameters to be used for preview calculation | +|
stages
| Optional: A list of stage indices to calculate a preview in | +|
dialog
| Optional: A script dialog to message when preview calculation was successful.
The dialog event handler will receive the event string 'calculated' | + +A call to this function will return immediately. The calculation is invoked asynchronously in a separate python instance. -Notes: +## The `calculation` function + +💡 **Notes:** * It is not possible to call script commands or read tokens from within this function. (Do not call `gom.app.project....`) * The function should loop over all stages to be calculated and set a computation result for each stage. -% `context`: ... +### Signature + +```python +def calculation(context, params): +``` + +#### Parameter `context` + +The context contains the following members: + +| Name | Role | Remarks | +| ------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------- | +|
.data[stage]
| UDE data storage access | see below | +|
.stages
| Current indices | List containing stage indices to calculate | +|
.total_stages
| Number of stages | Valid stage indices are 0 to total_stages - 1 | +|
.result[stage]
| Directly set preview | see below | +|
.is_new_element
| Flag if element creation | True on (edit) creation, false on recalc | +|
.name
| Name of created element | Read/write attribute
Ignored on recalc and script execution | +|
.error[stage]
| Used to assign an error text | Will set the element to not computed in the given stage | +|
.edit_element
| Reference to edited element | Only available during element editing | +|
.recalc_element
| Reference to element being computed | Only available during non-interactive calculation | +|
.progress_stages_computing
| Number of stages which have started to compute | Used to display progress information | +|
.progress_stages_total
| Number of stages which have to be computed | Used to display progress information | + +##### Attribute `context.data[]` + +The context.data is a list allowing to read or write additional data. The list is indexed with a stage index. The additional data is stored within the project, so the gom application must be able to serialize the provided data. + +```Python +context.data[0] = value +value = context.data[0] +``` + +##### Attribute `context.result[]` + +This is a write only list used to set computation results. The context.result[] should be set for each stage index listed in context.stages. +The format to write must match the type of the script element. For available result types, see [Scripted actuals - Return values](#scripted-actuals---return-values) and [Scripted checks - Return values](#scripted-checks---return-values). + +#### Return value + +On success the function must return True, otherwise False. + +% `context`: ... ## Scripted actuals - Return values -```{note} -This content was previously part of the GOM Connect. See [here](https://connect.gom.com/display/GPF/Scripted+Actual+Elements). -% TODO: -Content will be migrated shortly. +### Point + +:Element Type: Plain 3D point +:Result: Point coordinate + +```{code-block} python +result = (x,y,z) +result = gom.Vec3D ``` +### Distance + +:Element Type: Two point distance +:Result: Start and end point of distance + +```{code-block} python +result = { 'point1': (x,y,z), 'point2': (x,y,z) } +result = { 'point1': gom.Vec3D, 'point2': gom.Vec3D } +``` + +### Value Element + +:Element Type: Plain value (only real values supported) +:Result: any double value + +```{code-block} python +result = x +``` + +### Circle + +:Element Type: 2D Circle with direction +:Result: A center point, direction vector and radius (double) + +```{code-block} python +result = { 'center' : gom.Vec3D, 'direction' : gom.Vec3D, 'radius' : double } +``` + +### Curve + +:Element Type: 3D polyline +:Result: A curve can be made up by an array of subcurves. Each subcurve is a polyline. A closed curve will be created, if first point = last point. + +```{code-block} python +result = [ { 'points': [gom.Vec3D, gom.Vec3D, ...] } ] +``` + +### Surface Curve + +:Element Type: 3D polyline with normals +:Result: Like a curve with additional normal data, i.e. each surface curve can be made up by an array of subcurves. + +% ```{code-block} python +% # This does not work! +% result = [ { 'points': [ gom.Vec3D, gom.Vec3D, ... ], 'normals': [(x,y,z)] } ] +% ``` + +% :::{caution} +% **Workaround:** set the result to +```{code-block} python +result = { + 'default': [ + {'points': [gom.Vec3d, gom.Vec3d, ...], 'normals': [gom.Vec3d, gom.Vec3d, ...]}, + {...}, + ... + ] +} +``` +% ::: + +### Section + +:Element Type: 3D polyline with normals +:Result: Parameters 'plane', 'cylinder' and 'cone' are optional. They denote the creation geometry. You can only use one of them. Argument is a corresponding trait. + +```{code-block} python +result = { + 'curves': [{'points': [(x, y, z), ...], 'normals': [(x, y, z), ...]}, ...], + 'plane' : {'normal' : (x, y, z), 'distance' : float}, + 'cylinder': ..., + 'cone' : ... +} +``` + +### Point Cloud + +:Element Type: Set of 3D points +:Result: A set of points. The 'normals 'attribute is optional. + +```{code-block} python +result = { 'points' : [ gom.Vec3D, gom.Vec3D, ... ] , 'normals' : [ gom.Vec3D, gom.Vec3D, ... ] } +``` + +### Surface + +:Element Type: Mesh +:Result: Defines a triangulation. The vertices attribute points defines all points. The triangle attribute defines triangles between these points using indices into the vertex list. + +```{code-block} python +result = { 'vertices': [ (x,y,z) ], 'triangles': [ (v0,v1,v2) ] } +``` + +### Cone + +:Element Type: Cone +:Result: Accepts any Cone Trait + +```{code-block} python +result = {'default' : {'point1': gom.Vec3d, 'radius1': float, 'point2': gom.Vec3d, 'radius2': float} } +``` + +% see SW2024-2241 +:::{caution} +Due to the internal represenstation of a Cone Element, the direction of the vector `point1` -> `point2` is always from the smaller to the larger circle (`radius1` < `radius2`). + +If you specify `radius1` > `radius2` in the creation parameters, [`point1`; `radius1`] and [`point2`; `radius2`] are swapped automatically. +::: + +### Cylinder + +:Element Type: Cylinder +:Result: Accepts any Cylinder Trait + +% ```{code-block} python +% result = Reference +% +% # This does not work! +% result = { 'point': gom.Vec3d, 'radius': float, 'direction': gom.Vec3d, 'inner' : bool } +%``` + +% :::{caution} +% **Workaround:** set the result to +```{code-block} Python +result = Reference + +result = {'default' : {'point': gom.Vec3d, 'radius': float, 'direction': gom.Vec3d, 'inner' : bool} } +``` +% ::: + +% See https://jira.gom.com/browse/AD-163 +% ### Plane +% +%:Element Type: Plane +%:Result: Accepts any Plane Trait +% +%% ```{code-block} python +%% result = Reference +%% +%% # This does not work! +%% result = { 'point1': gom.Vec3d, 'radius1': float, 'point2': gom.Vec3d, 'radius2': float } +%% ``` +% +%% :::{caution} +%% The creation of planes currently does not work. +%% +%% **Workaround:** set the result to +%```{code-block} python +%result = Reference +% +%result = {'default' : {'distance': gom.Vec3d, 'normal': gom.Vec3d} } +%``` +%::: + +### Volume defects + +:Element Type: Volume defects +:Result: A list of meshes defined by vertices and triangles.

The vertices attribute is a [python array] – one entry for each defect – of numpy arrays (np.array) of Vec3d.

The triangle attribute defines triangles between the points of each mesh using indices to the vertex lists.

The 'outer_hull' parameter can optionally be set to a reference of a mesh element of the project. This mesh will be copied and used as an outer hull for the defect element. **Alternatively**, 'outer_hull_vertices' and 'outer_hull_triangles' can be given as explicit outer hull mesh definition. + +```{code-block} python +result = { + 'vertices': [ np.array((x,y,z), (x,y,z), ... ), np.array((x,y,z), (x,y,z), ...), ... ], + 'triangles': [ np.array((v0,v1,v2), (v0,v1,v2), ... ), np.array((v0,v1,v2), (v0,v1,v2), ...), ... ], + 'outer_hull' : gom.Reference # optional OR + 'outer_hull_vertices': np.array((x,y,z),...), 'outer_hull_triangles': np.array((v0,v1,v2),...) +} +``` + +### 2D Volume Defects + +:Element Type: 2D volume defects element of curves

needed for the P201 package +:Result: Requires a list/array of lists/arrays of Vec3ds.

A list of points represents the polygon (curve) of one 2d volume defect. The list of lists of points represents all 2d volume defects that shall be included in the element. + +```{code-block} python +result = { + 'curves': [ [gom.Vec3d, gom.Vec3d, ...], + [gom.Vec3d, gom.Vec3d, ...], + ... ] +} +``` + +### Volume + +:Element Type: New volume data +:Result: Accepts a numpy array with voxel data and a transformation.

The numpy array's shape denotes the resulting volume shape. The 'dtype' can be one of (UINT16, BYTE, INT16, INT16, INT32, UINT32, FLOAT, DOUBLE).

The transformation can be a gom.Mat4x4 (affine transformation) or a gom.Vec3d (scaling along x/y/z axis only) + +```{code-block} python +result = { 'voxel_data' : np.array (), 'transformation' : (gom.Mat4x4 | gom.Vec3d) } +``` + +### Volume material map + +:Element Type: Attach material labels to volume element +:Result: Creates a new volume element copy with attached material labels.

First parameter is a numpy array of type UINT8 of the size of the volume. The values are the material index per voxel. Background has Index 0.

The second parameter is a list of floating point grey values that are the representative grey values of the background and the materials.

The third parameter is a reference to the volume element, to which material labels should be attached. + +```{code-block} python +result = { + 'material_labels_draft' : np.array (), + 'material_grey_values_draft' : [background, material 1, ...], + 'volume_reference_draft' : Reference +} +``` + +### Volume Section + +:Element Type: Volume Section +:Result: Accepts a numpy array with pixel data and a transformation.

The numpy array's shape denotes the resulting volume section shape. The 'dtype' must be `numpy.float32`.

The transformation is a gom.Mat4x4 (affine transformation) + +```{code-block} python +result = { 'pixel_data' : np.array (), 'transformation' : gom.Mat4x4 } +``` + +% New in SW2023 +%### Volume Region +% +%:Element Type: Volume Region +%:Result: Accepts a numpy array of the region data. The 'dtype' must be UINT_8. This array can be smaller than the volume grid.

The offset parameter defines %the location of the first voxel in the numpy array of the volume region.

This scripted element requires specifying a reference to a volume element. This can %be a volume or linked volume element.% + +%```{code-block} python +%result = { +% 'volume_element': Reference, +% 'offset': gom.Vec3d, +% 'data': np.array () +%} +%``` % ### Element type: Point @@ -62,10 +362,36 @@ Content will be migrated shortly. ## Scripted checks - Return values -```{note} -This content was previously part of the GOM Connect. See [here](https://connect.gom.com/display/GPF/Scripted+Checks). -% TODO: -Content will be migrated shortly. +Scripted Checks extend the concept of scripted actual elements to be able to calculate custom inspections based on python scripts. + +### Supported Element Types + +The following element types are currently supported: + + +### Scalar + +:Element Type: Scalar check:
A single scalar value is assigned to an element +:Result: A nominal and actual double value are set, and a reference to element which was checked. + +```{code-block} python +result = {"nominal" : double, "actual" : double, "reference" : gom.Reference } ``` +### Scalar Surface + +:Element Type: Surface check:
A scalar deviation value is assigned to each point of a mesh. +:Result: A list of deviation values for each point of a mesh. The mesh is also set as "reference" parameter.

The number of points of the mesh and the "deviation_values" array must match. + +```{code-block} python +result = { "deviation_values" : np.array(dtype=np.float32), "reference" : gom.Reference } +``` +### Scalar Curve + +:Element Type: Curve check:
A scalar deviation value is assigned to each point on a curve. +:Result: A list of nominal and actual values for each point of a curve. The deviation values are calculated automatically as a difference between both.

The curve is also set as "reference" parameter.

The number of points of the curve and the value arrays must match. + +```{code-block} python +result = { "actual_values" : double, 'nominal_values': double, "reference" : gom.Reference} +``` diff --git a/doc/python_examples/index.md b/doc/python_examples/index.md index 647aaf9..b438978 100644 --- a/doc/python_examples/index.md +++ b/doc/python_examples/index.md @@ -1,6 +1,12 @@ +--- +myst: + html_meta: + "description": "Examples for using the GOM Inspect 2023 Add-on Python API" + "keywords": "Metrology, GOM Inspect, Python API, GOM API, Scripting, Packages, Add-ons, Examples" +--- # Overview -Welcome to the Python API Examples. Here you can find the documentation of the examples which are provided by the +Welcome to the GOM Inspect Python API Examples. Here you can find the documentation of the examples which are provided by the `Python API Examples` Add-On. You can reuse and adapt these code examples to your specific use case and learn the best-practices we recommend. ## How the examples are structured @@ -54,14 +60,14 @@ The following project files are included: ## Examples by topic -| Folder | Description | -| ---------------------------------------- | ------------------------------------------------------------ | -| [data_interfaces](data_interfaces.rst) | Access to data of GOM "elements" | -| [dialog_widgets](dialog_widgets.rst) | Examples how use custom dialogs and handle user input events | -| [misc](misc.rst) | Miscellaneous examples | -| [script_icons](script_icons.rst) | Set icons for scripts or buttons | -| [script_resources](script_resources.rst) | How to access binary data of your add-on (resources) | -| [scripted_actuals](scripted_actuals.rst) | Building actual elements with custom python code | -| [scripted_checks](scripted_checks.rst) | Building custom checks with python code | +| Folder | Description | +| --------------------------------------- | ------------------------------------------------------------ | +| [data_interfaces](data_interfaces.md) | Access to data of GOM "elements" | +| [dialog_widgets](dialog_widgets.md) | Examples how use custom dialogs and handle user input events | +| [misc](misc.md) | Miscellaneous examples | +| [script_icons](script_icons.md) | Set icons for scripts or buttons | +| [script_resources](script_resources.md) | How to access binary data of your add-on (resources) | +| [scripted_actuals](scripted_actuals.md) | Building actual elements with custom python code | +| [scripted_checks](scripted_checks.md) | Building custom checks with python code | \ No newline at end of file