From 6c8efafae21490df54570de64630e6d81cc93300 Mon Sep 17 00:00:00 2001 From: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:22:59 -0700 Subject: [PATCH 1/6] Generating Python API docs. This PR adds support for generating Python API documentation in HTML format using Sphinx from the MaterialX Python that are built in the `lib/` directory. A new CMake build option named `MATERIALX_BUILD_PYTHON_DOCS` allows developers to turn generating Python API documentation on. When `MATERIALX_BUILD_PYTHON_DOCS` is set to `ON`, `MATERIALX_BUILD_PYTHON` is set to `ON` as well, ensuring we have Python modules for which to build the Python API docs. The core functionality of generating Python API documentation lives in a new directory named `documents/PythonAPI/`. It is controlled with a new `CMakeLists.txt` file in that directory, which defines a new target named `MaterialXDocsPython`, similar to the existing target `MaterialXDocs` that generates API documentation for the MaterialX C++ API. To facilitate the curation and addition of docstrings in the implementation files within `source/PyMaterialX/`, this PR adds a new helper macro named `PYMATERIALX_DOCSTRING` that allows developers of Python modules to define docstrings using the following pattern: ```cpp PYMATERIALX_DOCSTRING(R"docstring( ...markdown text here... )docstring"); ``` Revised docstrings for modules and classes are to be added in subsequent PRs separately. Documentation in markdown format from the existing `DeveloperGuide` is integrated into the new Python API documentation by way of symlinking the four main `.md` files into the `documents/PythonAPI/sources/` directory from which Sphinx generates the resulting HTML documentation. Warnings that are issued when generating the documentation via Sphinx are to be addressed in a separate PR for the markdown files: https://github.com/AcademySoftwareFoundation/MaterialX/pull/2037 To build the docs from scratch on macOS, I've used the following build script, naming it `build.sh` in the `MaterialX` checkout directory: ```bash echo build.sh: Updating Git submodules... git submodule update --init --recursive python3 -m venv /tmp/venv source /tmp/venv/bin/activate.csh echo build.sh: Installing dependencies... python3 -m pip install myst_parser # https://pypi.org/project/myst-parser/ echo build.sh: Making build directory and changing into it... mkdir build cd build echo build.sh: Configuring... cmake .. \ --fresh \ -DCMAKE_OSX_SYSROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX15.0.sdk \ -DMATERIALX_BUILD_PYTHON=ON \ -DMATERIALX_BUILD_VIEWER=ON \ -DMATERIALX_BUILD_GRAPH_EDITOR=ON \ -DMATERIALX_BUILD_DOCS=ON \ -DMATERIALX_BUILD_PYTHON_DOCS=ON \ -DMATERIALX_BUILD_TESTS=ON \ && \ echo build.sh: Building... \ && \ cmake --build . -j 8 \ && \ echo build.sh: Building target MaterialXDocs... \ && \ cmake --build . --target MaterialXDocs \ && \ echo build.sh: Building target MaterialXDocsPython... \ && \ cmake --build . --target MaterialXDocsPython \ && \ afplay /System/Library/Sounds/Blow.aiff deactivate ``` The build output currently ends with the following messages: ```python The parsed MaterialX Python API consists of: * 11 modules * 48 functions * 139 classes * 1175 methods * 6 exception types WARNING: 48 functions look like they do not have docstrings yet. WARNING: 1019 methods look like they do not have docstrings yet. WARNING: 32 functions look like their parameters have not all been named using `py::arg()`. WARNING: 499 methods look like their parameters have not all been named using `py::arg()`. build succeeded, 168 warnings. The HTML pages are in .. [100%] Built target MaterialXDocsPython ``` Split from #1567. Update #342. Signed-off-by: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> --- .gitignore | 1 + CMakeLists.txt | 9 + cmake/modules/FindSphinx.cmake | 18 + documents/DeveloperGuide/MainPage.md | 2 + documents/PythonAPI/CMakeLists.txt | 53 ++ documents/PythonAPI/conf.py.in | 464 ++++++++++++++++++ documents/PythonAPI/custom.css | 375 ++++++++++++++ documents/PythonAPI/sources/GraphEditor.md | 1 + documents/PythonAPI/sources/MainPage.md | 1 + .../PythonAPI/sources/ShaderGeneration.md | 1 + documents/PythonAPI/sources/Viewer.md | 1 + documents/PythonAPI/sources/index.rst | 86 ++++ .../PythonAPI/templates/autosummary/class.rst | 81 +++ .../templates/autosummary/module.rst | 69 +++ .../templates/sphinx-navigation.html | 12 + source/PyMaterialX/PyMaterialX.h | 13 +- 16 files changed, 1186 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/FindSphinx.cmake create mode 100644 documents/PythonAPI/CMakeLists.txt create mode 100644 documents/PythonAPI/conf.py.in create mode 100644 documents/PythonAPI/custom.css create mode 120000 documents/PythonAPI/sources/GraphEditor.md create mode 120000 documents/PythonAPI/sources/MainPage.md create mode 120000 documents/PythonAPI/sources/ShaderGeneration.md create mode 120000 documents/PythonAPI/sources/Viewer.md create mode 100644 documents/PythonAPI/sources/index.rst create mode 100644 documents/PythonAPI/templates/autosummary/class.rst create mode 100644 documents/PythonAPI/templates/autosummary/module.rst create mode 100644 documents/PythonAPI/templates/sphinx-navigation.html diff --git a/.gitignore b/.gitignore index 9d0b71a3c7..86181511f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build +documents/PythonAPI/sources/generated/ dist diff --git a/CMakeLists.txt b/CMakeLists.txt index 59afaca01e..68495bcf01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,10 +33,15 @@ endif() project(MaterialX VERSION ${MATERIALX_LIBRARY_VERSION}) +# Build options option(MATERIALX_BUILD_PYTHON "Build the MaterialX Python package from C++ bindings. Requires Python 3.6 or greater." OFF) option(MATERIALX_BUILD_VIEWER "Build the MaterialX Viewer." OFF) option(MATERIALX_BUILD_GRAPH_EDITOR "Build the MaterialX Graph Editor." OFF) option(MATERIALX_BUILD_DOCS "Create HTML documentation using Doxygen. Requires that Doxygen be installed." OFF) +option(MATERIALX_BUILD_PYTHON_DOCS "Create HTML documentation for the MaterialX Python API using Sphinx. Requires that Sphinx be installed. Sets MATERIALX_BUILD_PYTHON to ON." OFF) +if(MATERIALX_BUILD_PYTHON_DOCS) + set(MATERIALX_BUILD_PYTHON ON) +endif() option(MATERIALX_BUILD_GEN_GLSL "Build the GLSL shader generator back-end." ON) option(MATERIALX_BUILD_GEN_OSL "Build the OSL shader generator back-end." ON) @@ -158,6 +163,7 @@ message(STATUS "Setting namespace to '${MATERIALX_NAMESPACE}'") set (MATERIALX_LIBNAME_SUFFIX "" CACHE STRING "Specify a suffix to all libraries that are built") mark_as_advanced(MATERIALX_BUILD_DOCS) +mark_as_advanced(MATERIALX_BUILD_PYTHON_DOCS) mark_as_advanced(MATERIALX_BUILD_GEN_GLSL) mark_as_advanced(MATERIALX_BUILD_GEN_OSL) mark_as_advanced(MATERIALX_BUILD_GEN_MDL) @@ -507,6 +513,9 @@ endif() if(MATERIALX_BUILD_DOCS) add_subdirectory(documents) endif() +if(MATERIALX_BUILD_PYTHON_DOCS) + add_subdirectory(documents/PythonAPI) +endif() if(MATERIALX_BUILD_JS) add_subdirectory(source/JsMaterialX) diff --git a/cmake/modules/FindSphinx.cmake b/cmake/modules/FindSphinx.cmake new file mode 100644 index 0000000000..7ffbb61eec --- /dev/null +++ b/cmake/modules/FindSphinx.cmake @@ -0,0 +1,18 @@ +include(FindPackageHandleStandardArgs) + +find_package(Python3) +if(PYTHON3_FOUND) + get_filename_component(_PYTHON_EXECUTABLE_DIR "${PYTHON_EXECUTABLE}" DIRECTORY) + set(_SPHINX_SEARCH_PATHS + "${_PYTHON_EXECUTABLE_DIR}" + "${_PYTHON_EXECUTABLE_DIR}/bin" + "${_PYTHON_EXECUTABLE_DIR}/Scripts" + "${_PYTHON_EXECUTABLE_DIR}/../opt/sphinx-doc/bin") + message(STATUS "Looking for Sphinx in the following directories: ${_SPHINX_SEARCH_PATHS}") + find_program(SPHINX_EXECUTABLE + NAMES sphinx-build sphinx-build.exe + HINTS ${_SPHINX_SEARCH_PATHS}) + mark_as_advanced(SPHINX_EXECUTABLE) + + find_package_handle_standard_args(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE) +endif() diff --git a/documents/DeveloperGuide/MainPage.md b/documents/DeveloperGuide/MainPage.md index 155c58eb4d..153cf0ea5c 100644 --- a/documents/DeveloperGuide/MainPage.md +++ b/documents/DeveloperGuide/MainPage.md @@ -52,6 +52,8 @@ Select the `MATERIALX_BUILD_VIEWER` option to build the MaterialX Viewer. Insta To generate HTML documentation for the MaterialX C++ API, make sure a version of [Doxygen](https://www.doxygen.org/) is on your path, and select the advanced option `MATERIALX_BUILD_DOCS` in CMake. This option will add a target named `MaterialXDocs` to your project, which can be built as an independent step from your development environment. +To generate HTML documentation for the MaterialX Python API, make sure a version of [Sphinx](https://www.sphinx-doc.org/) is on your path, and select the advanced option `MATERIALX_BUILD_PYTHON_DOCS` in CMake. This option will add a target named `MaterialXDocsPython` to your project, which can be built as an independent step from your development environment. + ## Installing MaterialX Building the `install` target of your project will install the MaterialX C++ and Python libraries to the folder specified by the `CMAKE_INSTALL_PREFIX` setting, and will install MaterialX Python as a third-party library in your Python environment. Installation of MaterialX Python as a third-party library can be disabled by setting `MATERIALX_INSTALL_PYTHON` to `OFF`. diff --git a/documents/PythonAPI/CMakeLists.txt b/documents/PythonAPI/CMakeLists.txt new file mode 100644 index 0000000000..a469a05502 --- /dev/null +++ b/documents/PythonAPI/CMakeLists.txt @@ -0,0 +1,53 @@ +# Use `CMAKE_MODULE_PATH` to be able to find the Sphinx executables +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") + +find_package(Sphinx REQUIRED) + +set(SPHINX_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(SPHINX_RST_SOURCE_DIR ${SPHINX_SOURCE_DIR}/sources) +set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(SPHINX_HTML_OUTPUT_DIR ${SPHINX_OUTPUT_DIR}) +set(MATERIALX_PYTHON_DOCS_TARGET_DEPENDENCIES + MaterialXCore + PyMaterialXCore + PyMaterialXFormat + PyMaterialXGenShader + PyMaterialXGenGlsl + PyMaterialXGenOsl + PyMaterialXGenMdl + PyMaterialXGenMsl + PyMaterialXRender + PyMaterialXRenderGlsl + PyMaterialXRenderOsl + PyMaterialXRenderMsl) + +# Generate the Sphinx configuration file `conf.py` from `conf.py.in` +set(MATERIALX_PYTHONPATH ${CMAKE_BINARY_DIR}/lib) +set(MATERIALX_LOGO_FILENAME "MaterialXLogo_200x155.png") +configure_file(${SPHINX_SOURCE_DIR}/conf.py.in + ${SPHINX_OUTPUT_DIR}/conf.py) + +# Add a custom target to invoke `sphinx-build` to generate the Python API docs, +# which depends on the Python bindings to be built +add_custom_target(MaterialXDocsPython + ${SPHINX_EXECUTABLE} --fresh-env --conf-dir ${SPHINX_OUTPUT_DIR} + ${SPHINX_RST_SOURCE_DIR} + ${SPHINX_HTML_OUTPUT_DIR} + WORKING_DIRECTORY ${SPHINX_OUTPUT_DIR} + DEPENDS ${MATERIALX_PYTHON_DOCS_TARGET_DEPENDENCIES} + COMMENT "Building MaterialX Python API Documentation: ${SPHINX_HTML_OUTPUT_DIR}/index.html") + +# Add post-build commands to copy our logo and custom style sheet to the "_static" folder +add_custom_command(TARGET MaterialXDocsPython + POST_BUILD COMMAND + ${CMAKE_COMMAND} -E copy + ${SPHINX_SOURCE_DIR}/custom.css + ${SPHINX_HTML_OUTPUT_DIR}/_static/custom.css) +add_custom_command(TARGET MaterialXDocsPython + POST_BUILD COMMAND + ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/documents/Images/${MATERIALX_LOGO_FILENAME} + ${SPHINX_HTML_OUTPUT_DIR}/_static/) + +install(DIRECTORY ${SPHINX_OUTPUT_DIR} + DESTINATION "documents/PythonAPI" MESSAGE_NEVER) diff --git a/documents/PythonAPI/conf.py.in b/documents/PythonAPI/conf.py.in new file mode 100644 index 0000000000..1b3e1f445a --- /dev/null +++ b/documents/PythonAPI/conf.py.in @@ -0,0 +1,464 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import sphinx.ext.autosummary.generate +import sphinx.util.logging + + +logger = sphinx.util.logging.getLogger(__name__) + + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'MaterialX' +author = '{} Authors'.format(project) +copyright = '2024 {}'.format(author) +release = '${MATERIALX_LIBRARY_VERSION}' + + +# -- Path Manipulation ------------------------------------------------------- + +import os +import sys +sys.path.insert(0, os.path.abspath('${MATERIALX_PYTHONPATH}')) + + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +# Set a default role to format text wrapped in single backticks as Python code objects, +# possibly linked to the respective Python API documentation paragraph +default_role = 'py:obj' + +# List of Sphinx Python modules to make available +extensions = [ + 'myst_parser', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', +] + +# Syntax highlighting +pygments_style = 'monokai' + +# Set the path to Jinja template files for Autosummary to use: +# - `autosummary/module.rst` - template for modules +# - `autosummary/class.rst` - template for classes +# See https://www.sphinx-doc.org/en/master/development/html_themes/templating.html#templating +templates_path = ['${SPHINX_SOURCE_DIR}/templates'] + + +# -- AutosummaryRenderer Overrides --------------------------------------------------- + +# Override the `render()` method of the `AutosummaryRenderer` class in order to +# add a custom filter for extracting docstrings of attributes from their +# corresponding properties +_original_render = sphinx.ext.autosummary.generate.AutosummaryRenderer.render + + +def get_docstring(module_name: str, class_name: str, attribute_name: str) -> str: + module = sys.modules.get(module_name) + if module: + class_object = getattr(module, class_name) + if class_object: + attr = getattr(class_object, attribute_name) + if isinstance(attr, str): + prop = class_object.__dict__.get(attribute_name) + if prop: + return prop.__doc__.strip().replace("\n ", "\n ") + for base_class in class_object.__bases__: + prop = base_class.__dict__.get(attribute_name) + if prop: + return "Inherited from `{}`.\n\n {}".format( + base_class.__name__, + prop.__doc__.strip().replace("\n ", "\n ")) + + return "No docstring available for {}.{}.{}".format(module_name, class_name, attribute_name) + + +def has_member(module_name: str, class_name: str, member_name: str) -> bool: + module = sys.modules.get(module_name) + if module: + class_object = getattr(module, class_name) + if class_object: + member = class_object.__dict__.get(member_name) + if member: + return True + + return False + + +def render(self, template_name: str, context: dict) -> str: + self.env.globals['getDocstring'] = get_docstring + self.env.globals['has_member'] = has_member + return _original_render(self, template_name, context) + + +sphinx.ext.autosummary.generate.AutosummaryRenderer.render = render + + +# -- Autodoc Configuration --------------------------------------------------- + +add_module_names = False +autodoc_default_options = { + 'show-inheritance': True, +} + +_MaterialX_versioned_namespace = 'MaterialX_v${MATERIALX_LIBRARY_VERSION}'.replace(".", "_") +_current_class_name = None +_objects_found = {} +_undocumented_functions = [] +_undocumented_methods = [] +_functions_with_empty_line_in_docstring = [] +_methods_with_empty_line_in_docstring = [] +_functions_with_unnamed_parameters = [] +_methods_with_unnamed_parameters = [] +_DOCSTRING_MACRO_NAME = "PYMATERIALX_DOCSTRING" + + +def strip_module_names(text): + """ + Returns the given text with prefixes of known Python modules stripped, in + order to make the API documentation more readable. + """ + if not text: + return text + + # Check if the given text references a type named `Input`, and if so, leave + # the given text as-is. Otherwise, `Input` by itself could mean either + # `PyMaterialXCore.Input` or `PyMaterialXRenderGlsl.Input`, leading to a + # warning when processing the docstring: + # ``` + # docstring of PyMaterialXGenShader.PyCapsule.getNodeDefInput:1: + # WARNING: more than one target found for cross-reference 'Input': + # PyMaterialXCore.Input, PyMaterialXRenderGlsl.Input + # ``` + if ".Input)" in text or ".Input, " in text or text.endswith(".Input"): + return text + + return ( + text + .replace('{}::'.format(_MaterialX_versioned_namespace), '') + .replace('PyMaterialXCore.', '') + .replace('PyMaterialXFormat.', '') + .replace('PyMaterialXGenShader.', '') + .replace('PyMaterialXGenGlsl.', '') + .replace('PyMaterialXGenOsl.', '') + .replace('PyMaterialXGenMdl.', '') + .replace('PyMaterialXGenMsl.', '') + .replace('PyMaterialXRender.', '') + .replace('PyMaterialXRenderGlsl.', '') + .replace('PyMaterialXRenderOsl.', '') + .replace('PyMaterialXRenderMsl.', '') + + # Special case handling for "PyMaterialXRenderGlsl.Input", which + # appears in `Dict[str, GlslProgram::Input]` in one case + .replace('GlslProgram::Input', 'PyMaterialXRenderGlsl.Input') + + # Special case handling for "PyMaterialXRenderMsl.Input", which + # appears in `Dict[str, MslProgram::Input]` in one case + .replace('MslProgram::Input', 'PyMaterialXRenderMsl.Input') + ) + + +def autodoc_process_bases(app, name, obj, options, bases): + """ + Event handler for classes. + + Emitted when autodoc has read and processed a class to determine the base + classes. + + Is emitted only if the `show-inheritance` option is given. + + Implemented to store the given fully-qualified name of the class in a + module-global variable named `_current_class_name` for use in method + signatures in `autodoc_process_docstring()` and `autodoc_process_signature()` + below. + + This function works around a quirk in pybind11 where `__qualname__` of a + method starts with `PyCapsule.` rather than the name of the class. + See https://github.com/pybind/pybind11/issues/2059 __qualname__ for methods + + Args: + app - the Sphinx application object + name - the fully-qualified name of the class + obj - the class itself + options - the options given to the class directive + bases - the list of base classes that can be modified in-place to + change what Sphinx puts into the output + """ + global _current_class_name + + _current_class_name = name + + +def autodoc_process_docstring(app, what, name, obj, options, lines): + """ + Event handler for processed docstrings. + + Implemented in order to detect undocumented functions, and functions with + empty lines in docstrings, and flag them as Sphinx warnings when the build + finishes (see `build_finished()` below). + + Emitted when autodoc has read and processed a docstring. + + `lines` is a list of strings - the lines of the processed docstring - that + the event handler can modify in place to change what Sphinx puts into the + output. + + Args: + app - the Sphinx application object + what - the type of the object to which the docstring belongs (one of + "module", "class", "exception", "function", "method", "attribute") + name - the fully qualified name of the object + obj - the object itself + options - the options given to the directive: an object with attributes + `inherited_members`, `undoc_members`, `show_inheritance`, and + `no-index` that are `True` if the flag option of same name + was given to the auto directive + lines - the lines of the docstring, see above + """ + if obj not in _objects_found.setdefault(what, []): + _objects_found[what].append(obj) + + if what == "function": + sig = "{}()".format(name) + elif what == "method": + sig = "{}.{}()".format(_current_class_name, obj.__name__) + + if what == "function": + if (sig not in _undocumented_functions + and obj.__doc__.count("\n") < 2): + _undocumented_functions.append(sig) + + if (sig not in _functions_with_empty_line_in_docstring + and "\n\n\n" in obj.__doc__): + _functions_with_empty_line_in_docstring.append(sig) + + if what == "method": + if (sig not in _undocumented_methods + and not name.endswith(".__init__") + and obj.__doc__.count("\n") < 2): + _undocumented_methods.append(sig) + + if (sig not in _methods_with_empty_line_in_docstring + and "\n\n\n" in obj.__doc__): + _methods_with_empty_line_in_docstring.append(sig) + + +def autodoc_process_signature(app, what, name, obj, options, signature, + return_annotation): + """ + Event handler for object signatures. + + Emitted when autodoc has formatted a signature for an object. + + Can return a new tuple `(signature, return_annotation)` to change what + Sphinx puts into the output. + + Args: + app - the Sphinx application object + what - the type of the object to which the docstring belongs (one of + "module", "class", "exception", "function", "method", "attribute") + name - the fully qualified name of the object + obj - the object itself + options - the options given to the directive: an object with attributes + `inherited_members`, `undoc_members`, `show_inheritance`, and + `no-index` that are `True` if the flag option of same name + was given to the auto directive + signature - function signature, as a string of the form "(parameter_1, + parameter_2)", or `None` if introspection didn't succeed + and signature wasn't specified in the directive. + return_annotation - function return annotation as a string of the form + " -> annotation", or `None` if there is no return + annotation + """ + contains_unnamed_parameters = any(["arg{}".format(i) in signature + for i in range(10)]) + + # Check if an overloaded function contains unnamed parameters + if (not contains_unnamed_parameters + and obj.__doc__ + and "Overloaded function." in obj.__doc__): + contains_unnamed_parameters = any(["arg{}".format(i) in obj.__doc__ + for i in range(10)]) + + signature = strip_module_names(signature) + return_annotation = strip_module_names(return_annotation) + + if what == "function": + sig = "{}{}".format(name, signature) + elif what == "method": + sig = "{}.{}{}".format(_current_class_name, obj.__name__, signature) + else: + sig = "" + + if (what == "function" + and sig not in _functions_with_unnamed_parameters + and contains_unnamed_parameters): + _functions_with_unnamed_parameters.append(sig) + + if (what == "method" + and sig not in _methods_with_unnamed_parameters + and contains_unnamed_parameters): + _methods_with_unnamed_parameters.append(sig) + + return (signature, return_annotation) + + +def build_finished(app, exception): + """ + Emitted when a build has finished, before Sphinx exits, usually used for + cleanup. This event is emitted even when the build process raised an + exception, given as the `exception` argument. The exception is reraised in + the application after the event handlers have run. If the build process + raised no exception, `exception` will be `None`. This allows to customize + cleanup actions depending on the exception status. + """ + # Warn about possible issues in docstrings and signatures + if _undocumented_functions: + logger.info("\nFunctions with empty docstrings:\n {}" + .format("\n ".join(sorted(_undocumented_functions)))) + if _undocumented_methods: + logger.info("\nMethods with empty docstrings:\n {}" + .format("\n ".join(sorted(_undocumented_methods)))) + if _functions_with_empty_line_in_docstring: + logger.info("\nFunctions with empty lines in docstrings:\n {}" + .format("\n ".join(sorted(_functions_with_empty_line_in_docstring)))) + if _methods_with_empty_line_in_docstring: + logger.info("\nMethods with empty lines in docstrings:\n {}" + .format("\n ".join(sorted(_methods_with_empty_line_in_docstring)))) + if _functions_with_unnamed_parameters: + logger.info("\nFunctions with possibly unnamed parameters:\n {}" + .format("\n ".join(sorted(_functions_with_unnamed_parameters)))) + if _methods_with_unnamed_parameters: + logger.info("\nMethods with possibly unnamed parameters:\n {}" + .format("\n ".join(sorted(_methods_with_unnamed_parameters)))) + + # Show statistics about the API + statistics = "The parsed MaterialX Python API consists of:" + for what in ("module", "function", "class", "attribute", "method", "exception"): + if what in _objects_found: + statistics += ("\n * {} {}{}".format( + len(_objects_found[what]), + what, + "s" if len(_objects_found[what]) > 1 else "") + .replace("classs", "classes") + .replace("exceptions", "exception types")) + logger.info("\n{}\n".format(statistics)) + + # Show a summary of warnings about possible issues in docstrings and + # signatures + N = len(_undocumented_functions) + if N == 1: + logger.warning("1 function looks like it does not have a docstring yet.") + elif N > 1: + logger.warning("{} functions look like they do not have docstrings yet." + .format(N)) + + N = len(_undocumented_methods) + if N == 1: + logger.warning("1 method looks like it does not have a docstring yet.") + elif N > 1: + logger.warning("{} methods look like they do not have docstrings yet." + .format(N)) + + N = len(_functions_with_empty_line_in_docstring) + if N == 1: + logger.warning("1 function looks like its docstring contains an extra " + "empty line, perhaps not wrapped in `{}()`." + .format(_DOCSTRING_MACRO_NAME)) + elif N > 1: + logger.warning("{} functions look like their docstrings contain an " + "extra empty line, perhaps not wrapped in `()`." + .format(N, _DOCSTRING_MACRO_NAME)) + + N = len(_methods_with_empty_line_in_docstring) + if N == 1: + logger.warning("1 method looks like its docstring contains an extra " + "empty line, perhaps not wrapped in `{}()`." + .format(_DOCSTRING_MACRO_NAME)) + elif N > 1: + logger.warning("{} methods look like their docstrings contain an " + "extra empty line, perhaps not wrapped in `()`." + .format(N, _DOCSTRING_MACRO_NAME)) + + N = len(_functions_with_unnamed_parameters) + if N == 1: + logger.warning("1 function looks like its parameters have not all " + "been named using `py::arg()`.") + elif N > 1: + logger.warning("{} functions look like their parameters have not all " + "been named using `py::arg()`." + .format(N)) + + N = len(_methods_with_unnamed_parameters) + if N == 1: + logger.warning("1 method looks like its parameters have not all been " + "named using `py::arg()`.") + elif N > 1: + logger.warning("{} methods look like their parameters have not all " + "been named using `py::arg()`." + .format(N)) + + +def setup(app): + app.connect('autodoc-process-bases', autodoc_process_bases) + app.connect('autodoc-process-docstring', autodoc_process_docstring) + app.connect('autodoc-process-signature', autodoc_process_signature) + app.connect('build-finished', build_finished) + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +about = """ +MaterialX – An open standard for the exchange of rich material and +look-development content across applications and renderers. +

+ GitHub +   + + + MaterialX on Mastodon + + + + + +

+""" + +html_theme = 'alabaster' + +# For documentation on the available options that the Alabaster theme provides, +# see https://alabaster.readthedocs.io/en/latest/customization.html +html_theme_options = { + 'logo': '${MATERIALX_LOGO_FILENAME}', + 'description': about, + 'github_button': False, + 'github_user': 'AcademySoftwareFoundation', + 'github_repo': 'MaterialX', + 'extra_nav_links': { + 'MaterialX.org': 'https://materialx.org/', + 'MaterialX C++ API Docs': 'https://materialx.org/docs/api/index.html', + 'MaterialX Specification': 'https://materialx.org/Specification.html', + }, + # Style colors + 'sidebar_hr': 'var(--separator-color)', + 'sidebar_search_button': 'var(--separator-color)', +} + +# Use a custom navigation Jinja template instead of the default 'navigation.html' +# in order to set a maximum depth in the table of contents in the sidebar +html_sidebars = { + '**': [ + 'about.html', # standard + 'sphinx-navigation.html', # custom + 'searchbox.html', # standard + ] +} diff --git a/documents/PythonAPI/custom.css b/documents/PythonAPI/custom.css new file mode 100644 index 0000000000..1be363f34a --- /dev/null +++ b/documents/PythonAPI/custom.css @@ -0,0 +1,375 @@ +/* +Dark Mode colors and other variables copied from "doxygen-awesome.css". +We could potentially import the entire style sheet here, but we're only +interested in the color definitions and a select few other styles. +DoxygenAwesome is licensed under the MIT License. +*/ +html { + /* font-families. will affect all text on the website + * font-family: the normal font for text, headlines, menus + * font-family-monospace: used for preformatted text in memtitle, code, fragments + */ + --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; + --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + + /* font sizes */ + --page-font-size: 15.6px; + --navigation-font-size: 14.4px; + --toc-font-size: 13.4px; + --code-font-size: 14px; /* affects code, fragment */ + --title-font-size: 22px; + + /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ + --border-radius-large: 8px; + --border-radius-small: 4px; + --border-radius-medium: 6px; + + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + --code-background-brighter: #3a3c3f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #2e1917; + --warning-color-dark: #ad2617; + --warning-color-darker: #f5b1aa; + --note-color: #3b2e04; + --note-color-dark: #f1b602; + --note-color-darker: #ceb670; + --todo-color: #163750; + --todo-color-dark: #1982D2; + --todo-color-darker: #dcf0fa; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2a2536; + --bug-color-dark: #7661b3; + --bug-color-darker: #ae9ed6; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; + + /* Additional custom color variables */ + --highlight-color: #fbe54e; + --target-color: #204D79; +} + +/* +Apply the dark mode colors and other styles on top of the default Alabaster +styles +*/ +body, +div.body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); + font-family: var(--font-family); + font-size: var(--page-font-size); +} +div.sphinxsidebar { + background-color: var(--side-nav-background); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-large); +} +div.sphinxsidebar p { + margin-bottom: 14px; +} +div.sphinxsidebar p, +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + color: var(--page-foreground-color); + font-family: var(--font-family); +} +div.sphinxsidebar ul { + color: inherit !important; +} +div.sphinxsidebar input { + font-family: var(--font-family); +} +a { + color: var(--primary-color) !important; + text-decoration: none; +} +a:link, a:visited, a:hover, a:focus, a:active { + font-weight: 500; +} +a.reference, +div.sphinxsidebar a { + text-decoration: none; + border-bottom: none; +} +a.reference:hover, +div.sphinxsidebar a:hover { + border-bottom: none; + color: var(--page-foreground-color) !important; + text-decoration: none !important; +} +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 +{ + font-family: var(--font-family); +} +div.body h2 { + margin-bottom: 20px; +} +dl { + margin-bottom: 40px; +} +dl.simple, +div.body li { + margin-bottom: 3px; +} +dt:target { + background: var(--target-color); +} +span.highlighted { + background-color: var(--highlight-color); + color: black; +} +dt.sig { + background-color: var(--code-background); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + padding: 10px; + margin-bottom: 8px; +} +dt.sig:target { + border: 2px solid var(--target-color); + padding: 9px; +} +div.admonition { + background-color: var(--side-nav-background); + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); +} +pre { + background-color: var(--code-background); + border-radius: var(--border-radius-small); + padding: 10px; + padding-left: 10px !important; +} +code, +div.fragment, +pre.fragment { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + overflow: hidden; +} +code { + display: inline; + color: var(--code-foreground); + padding: 2px 6px; +} +tt, +tt.xref, +code, +code.xref, +a em, +a tt { + background-color: var(--code-background); + border-bottom: none; +} +code, +code a, +a em, +pre.fragment, +div.fragment, +div.fragment span, +div.fragment .line, +div.fragment .line a, +div.fragment .line span { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size) !important; + font-style: normal; +} +code.xref, +a code { + font-weight: normal; +} +a:hover tt, +a:hover code { + background-color: var(--code-background-brighter); + color: var(--page-foreground-color); +} + +/* Indexes */ +div.modindex-jumpbox { + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + color: var(--separator-color); +} + +div.genindex-jumpbox { + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + color: var(--separator-color); +} + +/* Tables */ +table, +table.docutils { + border: none; + border-collapse: collapse; + -webkit-box-shadow: none; + box-shadow: none; +} +table.align-default { + margin-left: inherit !important; + margin-right: inherit !important; +} +table tr:nth-child(even) { + background-color: transparent; +} +table tr:nth-child(odd) { + background-color: var(--odd-color); +} +/* table header rows */ +table tr.row-odd:nth-child(odd) { + background-color: transparent; + border-bottom: 1px solid var(--separator-color); +} +table tr.row-even:nth-child(even) { + border-bottom: 1px solid var(--separator-color); +} +table.docutils td, +table.docutils th { + border: none; +} + +/* Pygments syntax highlighting */ +.highlight { + background: inherit !important; +} + +/* Make the MaterialX logo a little smaller */ +img.logo { + width: 60%; + height: 60%; +} + +/* Turn off auto-hyphenation in text blocks */ +div.body p, +div.body dd, +div.body li, +div.body blockquote { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +/* Input elements */ +input[type="text"] { + border: 1px solid var(--separator-color); + font-size: 14px !important; + padding: 6px; + width: 300px; +} +input[type="submit"], +div.sphinxsidebar #searchbox input[type="submit"] { + background: #204D79; +} + +/* +Blue button style copied from https://materialx.org/style/flavor.css and +extended +*/ +div.body form input[type="submit"], +.blueButton { + background: #204D79; + border: 1px solid black !important; + box-shadow: 0 1px 0px #3471aa inset; + color: #ccc !important; + font-family: Arial, Helvetica, sans-serif !important; + font-size: 14px !important; + font-weight: bold !important; + letter-spacing: 1px; + margin: auto; + padding: 6px; + text-align: center; + text-decoration: none !important; + text-shadow: 0 1px 0 black; + text-transform: uppercase; + width: 115px; + + /* for IE */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#204D79', endColorstr='#003260'); + + /* for webkit browsers */ + background: -webkit-gradient(linear, left top, left bottom, from(#204D79), to(#003260)); + -webkit-border-radius: 5px; + -webkit-box-shadow: 0 1px 0px #3471aa inset; + + /* for Firefox */ + background: -moz-linear-gradient(bottom, #003260, #204D79); + -moz-border-radius: 5px; + -moz-box-shadow: 0 1px 0 #3471aa inset; +} +div.body form input[type="submit"], +.blueButton:hover { + background: #204D79; + cursor: pointer; +/* text-decoration: none !important;*/ + + /* for IE */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#275d8e', endColorstr='#003d72'); + + /* for webkit browsers */ + background: -webkit-gradient(linear, left top, left bottom, from(#275d8e), to(#003d72)); + + /* for Firefox */ + background: -moz-linear-gradient(bottom, #003d72, #275d8e); +} +.blueButton:active { + background: #000; + box-shadow: 0px 1px 0px #444; + color: #fff; +/* text-decoration: none !important;*/ + + /* for IE */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#002345', endColorstr='#204D79'); + + /* for webkit browsers */ + background: -webkit-gradient(linear, left top, left bottom, from(#002345), to(#204D79)); + -webkit-box-shadow: 0px 1px 0px #444; + + /* for Firefox */ + background: -moz-linear-gradient(bottom, #204D79, #002345); + -moz-box-shadow: 0px 1px 0px #444; +} diff --git a/documents/PythonAPI/sources/GraphEditor.md b/documents/PythonAPI/sources/GraphEditor.md new file mode 120000 index 0000000000..8e06e20da8 --- /dev/null +++ b/documents/PythonAPI/sources/GraphEditor.md @@ -0,0 +1 @@ +../../DeveloperGuide/GraphEditor.md \ No newline at end of file diff --git a/documents/PythonAPI/sources/MainPage.md b/documents/PythonAPI/sources/MainPage.md new file mode 120000 index 0000000000..145415067c --- /dev/null +++ b/documents/PythonAPI/sources/MainPage.md @@ -0,0 +1 @@ +../../DeveloperGuide/MainPage.md \ No newline at end of file diff --git a/documents/PythonAPI/sources/ShaderGeneration.md b/documents/PythonAPI/sources/ShaderGeneration.md new file mode 120000 index 0000000000..14506eb3cb --- /dev/null +++ b/documents/PythonAPI/sources/ShaderGeneration.md @@ -0,0 +1 @@ +../../DeveloperGuide/ShaderGeneration.md \ No newline at end of file diff --git a/documents/PythonAPI/sources/Viewer.md b/documents/PythonAPI/sources/Viewer.md new file mode 120000 index 0000000000..18742cbf4d --- /dev/null +++ b/documents/PythonAPI/sources/Viewer.md @@ -0,0 +1 @@ +../../DeveloperGuide/Viewer.md \ No newline at end of file diff --git a/documents/PythonAPI/sources/index.rst b/documents/PythonAPI/sources/index.rst new file mode 100644 index 0000000000..45549e017d --- /dev/null +++ b/documents/PythonAPI/sources/index.rst @@ -0,0 +1,86 @@ +MaterialX Python API Documentation +================================== + +The `MaterialX Python API`_ provides Python bindings for the +`MaterialX C++ API `_ . + +MaterialX is an open standard for representing rich material and look-development +content in computer graphics, enabling its platform-independent description and +exchange across applications and renderers. Launched at `Industrial Light & Magic +`_ in 2012, MaterialX has been a key technology in their +feature films and real-time experiences since *Star Wars: The Force Awakens* +and *Millennium Falcon: Smugglers Run*. The project was released as open source +in 2017, with companies including Sony Pictures Imageworks, Pixar, Autodesk, +Adobe, and SideFX contributing to its ongoing development. In 2021, MaterialX +became the seventh hosted project of the `Academy Software Foundation +`_ . + +.. toctree:: + :maxdepth: 1 + + MainPage.md + GraphEditor.md + Viewer.md + ShaderGeneration.md + + +MaterialX Python API +-------------------- + +The MaterialX Python API consists of two parts: + +- A set of `MaterialX Python Modules`_ that are implemented as Python C + extensions that correspond to MaterialX C++ libraries. +- A Python package named `MaterialX` that wraps the MaterialX Python modules to + provide a more pythonic interface, in particular for working with the + `Element Classes `_ and + `Value Classes `_ of + `PyMaterialXCore`. + +The `MaterialX` Python package is typically imported aliased as `mx`: + +.. code:: python + + import MaterialX as mx + +All functions and classes from the `PyMaterialXCore` and `PyMaterialXFormat` +modules are available in the top-level `MaterialX` namespace, and, by aliasing, +the `mx` namespace. + +For example, the `PyMaterialXCore.Matrix44` class is typically used as +`mx.Matrix44`: + +.. code:: python + + >>> import MaterialX as mx + >>> mx.Matrix44 + + +You can use the *Quick search* box in the sidebar on the left to quickly find +documentation for a particular module, function, class, method, or attribute of +interest, for example `getUdimCoordinates `_ +or `MeshPartition `_. + + +MaterialX Python Modules +------------------------ + +.. autosummary:: + :toctree: generated + :caption: MaterialX Python Modules: + + PyMaterialXCore + PyMaterialXFormat + PyMaterialXGenShader + PyMaterialXGenGlsl + PyMaterialXGenOsl + PyMaterialXGenMdl + PyMaterialXGenMsl + PyMaterialXRender + PyMaterialXRenderGlsl + PyMaterialXRenderOsl + PyMaterialXRenderMsl + +.. toctree:: + + genindex diff --git a/documents/PythonAPI/templates/autosummary/class.rst b/documents/PythonAPI/templates/autosummary/class.rst new file mode 100644 index 0000000000..79f4c96026 --- /dev/null +++ b/documents/PythonAPI/templates/autosummary/class.rst @@ -0,0 +1,81 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :show-inheritance: + + {% block methods %} + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + {%- if has_member(module, objname, item) %} + ~{{ name }}.{{ item }} + {%- endif %} + {%- endfor %} + {%- endif %} + {%- endblock %} + + {%- block attributes %} + {%- if attributes %} + {# Check whether attributes and/or properties are present #} + {%- set ns = namespace(attributes_present=false, properties_present=false) %} + {%- for item in attributes %} + {# Only consider attributes that are local to the current object #} + {%- if has_member(module, objname, item) %} + {# Consider the current item an attribute if it contains "_" in #} + {# its name, e.g. "INTERFACE_NAME_ATTRIBUTE" #} + {%- if "_" in item %} + {%- set ns.attributes_present = true %} + {%- else %} + {%- set ns.properties_present = true %} + {%- endif %} + {%- endif %} + {%- endfor %} + + {% if ns.attributes_present %} + Attributes + ---------- + + {%- for item in attributes %} + {%- if "_" in item and has_member(module, objname, item) %} + .. autoattribute:: {{ module }}.{{ objname }}.{{ item }} + + {{ getDocstring(module, objname, item) }} + {%- endif %} + + {%- endfor %} + {%- endif %} + + {% if ns.properties_present %} + Properties + ---------- + + {%- for item in attributes %} + {%- if has_member(module, objname, item) %} + {%- if not "_" in item %} + .. autoproperty:: {{ module }}.{{ objname }}.{{ item }} + {%- endif %} + {%- endif %} + + {%- endfor %} + {%- endif %} + + {% endif %} + {% endblock %} + + {% block automethods %} + {% if methods %} + + Methods + ------- + + {% for item in methods %} + {%- if has_member(module, objname, item) %} + .. automethod:: {{ module }}.{{ objname }}.{{ item }} + {%- endif %} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/documents/PythonAPI/templates/autosummary/module.rst b/documents/PythonAPI/templates/autosummary/module.rst new file mode 100644 index 0000000000..b8001ab066 --- /dev/null +++ b/documents/PythonAPI/templates/autosummary/module.rst @@ -0,0 +1,69 @@ +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + Alphabetical Index + ------------------ + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Module Attributes') }} + + .. autosummary:: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} + +Module source: +`MaterialX `_ / +`source `_ / +`PyMaterialX `_ / +`{{ fullname }} `_ / diff --git a/documents/PythonAPI/templates/sphinx-navigation.html b/documents/PythonAPI/templates/sphinx-navigation.html new file mode 100644 index 0000000000..b2ea99bf04 --- /dev/null +++ b/documents/PythonAPI/templates/sphinx-navigation.html @@ -0,0 +1,12 @@ +

{{ _('Navigation') }}

+{{ toctree(maxdepth=2, + includehidden=theme_sidebar_includehidden, + collapse=theme_sidebar_collapse) }} +{% if theme_extra_nav_links %} +
+
    + {% for text, uri in theme_extra_nav_links.items() %} +
  • {{ text }}
  • + {% endfor %} +
+{% endif %} diff --git a/source/PyMaterialX/PyMaterialX.h b/source/PyMaterialX/PyMaterialX.h index 1e7bc7ff87..169885bd48 100644 --- a/source/PyMaterialX/PyMaterialX.h +++ b/source/PyMaterialX/PyMaterialX.h @@ -7,11 +7,15 @@ #define MATERIALX_PYMATERIALX_H // -// This header is used to include PyBind11 headers consistently across the +// This header is used to include pybind11 headers consistently across the // translation units in the PyMaterialX library, and it should be the first // include within any PyMaterialX source file. // +// Set a flag to allow pybind11 to provide more helpful error messages +// (see `pybind11/detail/common.h`) +#define PYBIND11_DETAILED_ERROR_MESSAGES 1 + #include #include #include @@ -29,3 +33,10 @@ pybind11::module::import(#MODULE_NAME); \ } #endif + +// Define a macro to process a block of docstring text +// (currently, we strip the first and last character from the given text, so +// that we can write the first and last lines of the docstring on new lines, +// rather than on the same line that starts and ends the docstring) +#define PYMATERIALX_DOCSTRING(TEXT) \ + std::string(TEXT).substr(1, std::string(TEXT).length() - 2).c_str() From d76eb5a0d8d4ae233892f944c7702697dc310b9e Mon Sep 17 00:00:00 2001 From: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:46:34 -0700 Subject: [PATCH 2/6] Reverting changes in `PyMaterialX.h`. We may not need the proposed `PYMATERIALX_DOCSTRING` macro. Signed-off-by: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> --- source/PyMaterialX/PyMaterialX.h | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/source/PyMaterialX/PyMaterialX.h b/source/PyMaterialX/PyMaterialX.h index 169885bd48..1e7bc7ff87 100644 --- a/source/PyMaterialX/PyMaterialX.h +++ b/source/PyMaterialX/PyMaterialX.h @@ -7,15 +7,11 @@ #define MATERIALX_PYMATERIALX_H // -// This header is used to include pybind11 headers consistently across the +// This header is used to include PyBind11 headers consistently across the // translation units in the PyMaterialX library, and it should be the first // include within any PyMaterialX source file. // -// Set a flag to allow pybind11 to provide more helpful error messages -// (see `pybind11/detail/common.h`) -#define PYBIND11_DETAILED_ERROR_MESSAGES 1 - #include #include #include @@ -33,10 +29,3 @@ pybind11::module::import(#MODULE_NAME); \ } #endif - -// Define a macro to process a block of docstring text -// (currently, we strip the first and last character from the given text, so -// that we can write the first and last lines of the docstring on new lines, -// rather than on the same line that starts and ends the docstring) -#define PYMATERIALX_DOCSTRING(TEXT) \ - std::string(TEXT).substr(1, std::string(TEXT).length() - 2).c_str() From f09409f9b10f899cf479b50baffb3a28a1a2ec15 Mon Sep 17 00:00:00 2001 From: Jonathan Stone Date: Thu, 3 Oct 2024 08:46:18 -0700 Subject: [PATCH 3/6] Simplify text Signed-off-by: Jonathan Stone --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68495bcf01..b17297d89e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ option(MATERIALX_BUILD_PYTHON "Build the MaterialX Python package from C++ bindi option(MATERIALX_BUILD_VIEWER "Build the MaterialX Viewer." OFF) option(MATERIALX_BUILD_GRAPH_EDITOR "Build the MaterialX Graph Editor." OFF) option(MATERIALX_BUILD_DOCS "Create HTML documentation using Doxygen. Requires that Doxygen be installed." OFF) -option(MATERIALX_BUILD_PYTHON_DOCS "Create HTML documentation for the MaterialX Python API using Sphinx. Requires that Sphinx be installed. Sets MATERIALX_BUILD_PYTHON to ON." OFF) +option(MATERIALX_BUILD_PYTHON_DOCS "Create HTML documentation for the MaterialX Python API using Sphinx. Requires that Sphinx be installed." OFF) if(MATERIALX_BUILD_PYTHON_DOCS) set(MATERIALX_BUILD_PYTHON ON) endif() From 914392667dca397b46de3187b02eed5e8620e28a Mon Sep 17 00:00:00 2001 From: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:00:20 -0700 Subject: [PATCH 4/6] Renamed `getDocstring()` to `get_docstring()` and re-used it for documenting class properties that don't store attribute name strings. Signed-off-by: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> --- documents/PythonAPI/conf.py.in | 2 +- documents/PythonAPI/templates/autosummary/class.rst | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/documents/PythonAPI/conf.py.in b/documents/PythonAPI/conf.py.in index 1b3e1f445a..9dc863f451 100644 --- a/documents/PythonAPI/conf.py.in +++ b/documents/PythonAPI/conf.py.in @@ -91,7 +91,7 @@ def has_member(module_name: str, class_name: str, member_name: str) -> bool: def render(self, template_name: str, context: dict) -> str: - self.env.globals['getDocstring'] = get_docstring + self.env.globals['get_docstring'] = get_docstring self.env.globals['has_member'] = has_member return _original_render(self, template_name, context) diff --git a/documents/PythonAPI/templates/autosummary/class.rst b/documents/PythonAPI/templates/autosummary/class.rst index 79f4c96026..84318cb93c 100644 --- a/documents/PythonAPI/templates/autosummary/class.rst +++ b/documents/PythonAPI/templates/autosummary/class.rst @@ -43,7 +43,7 @@ {%- if "_" in item and has_member(module, objname, item) %} .. autoattribute:: {{ module }}.{{ objname }}.{{ item }} - {{ getDocstring(module, objname, item) }} + {{ get_docstring(module, objname, item) }} {%- endif %} {%- endfor %} @@ -56,7 +56,9 @@ {%- for item in attributes %} {%- if has_member(module, objname, item) %} {%- if not "_" in item %} - .. autoproperty:: {{ module }}.{{ objname }}.{{ item }} + .. autoattribute:: {{ module }}.{{ objname }}.{{ item }} + + {{ get_docstring(module, objname, item) }} {%- endif %} {%- endif %} From e391fbb5dfdd2e95faabdaed17629d1bb21b27d3 Mon Sep 17 00:00:00 2001 From: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:01:06 -0700 Subject: [PATCH 5/6] Renamed *Attributes* section to *Attribute Names*. Signed-off-by: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> --- .../PythonAPI/templates/autosummary/class.rst | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/documents/PythonAPI/templates/autosummary/class.rst b/documents/PythonAPI/templates/autosummary/class.rst index 84318cb93c..72ee3f3316 100644 --- a/documents/PythonAPI/templates/autosummary/class.rst +++ b/documents/PythonAPI/templates/autosummary/class.rst @@ -11,6 +11,7 @@ .. autosummary:: {% for item in methods %} + {# Only consider methods that are local to the current object #} {%- if has_member(module, objname, item) %} ~{{ name }}.{{ item }} {%- endif %} @@ -20,27 +21,26 @@ {%- block attributes %} {%- if attributes %} - {# Check whether attributes and/or properties are present #} - {%- set ns = namespace(attributes_present=false, properties_present=false) %} + {# Split the attributes into those defining names of attributes and other properties #} + {%- set ns = namespace(attribute_names_present=false, properties_present=false) %} {%- for item in attributes %} {# Only consider attributes that are local to the current object #} {%- if has_member(module, objname, item) %} - {# Consider the current item an attribute if it contains "_" in #} + {# Consider the current item an attribute if it contains "_ATTRIBUTE" in #} {# its name, e.g. "INTERFACE_NAME_ATTRIBUTE" #} - {%- if "_" in item %} - {%- set ns.attributes_present = true %} + {%- if "_ATTRIBUTE" in item %} + {%- set ns.attribute_names_present = true %} {%- else %} {%- set ns.properties_present = true %} {%- endif %} {%- endif %} {%- endfor %} - {% if ns.attributes_present %} - Attributes - ---------- - + {% if ns.attribute_names_present %} + Attribute Names + --------------- {%- for item in attributes %} - {%- if "_" in item and has_member(module, objname, item) %} + {%- if "_ATTRIBUTE" in item and has_member(module, objname, item) %} .. autoattribute:: {{ module }}.{{ objname }}.{{ item }} {{ get_docstring(module, objname, item) }} @@ -55,7 +55,7 @@ {%- for item in attributes %} {%- if has_member(module, objname, item) %} - {%- if not "_" in item %} + {%- if not "_ATTRIBUTE" in item %} .. autoattribute:: {{ module }}.{{ objname }}.{{ item }} {{ get_docstring(module, objname, item) }} From b390e2fda123a4e1ee6721de6ef3a4f822439a82 Mon Sep 17 00:00:00 2001 From: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:01:49 -0700 Subject: [PATCH 6/6] Replaced alphabetical index of module contents with links to separate documentation pages. Signed-off-by: Stefan Habel <19556655+StefanHabel@users.noreply.github.com> --- .../templates/autosummary/module.rst | 79 ++++++++----------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/documents/PythonAPI/templates/autosummary/module.rst b/documents/PythonAPI/templates/autosummary/module.rst index b8001ab066..5b11b96ded 100644 --- a/documents/PythonAPI/templates/autosummary/module.rst +++ b/documents/PythonAPI/templates/autosummary/module.rst @@ -2,63 +2,48 @@ .. automodule:: {{ fullname }} - Alphabetical Index - ------------------ +{% block classes %} +{% if classes %} +Classes +------- - {% block attributes %} - {% if attributes %} - .. rubric:: {{ _('Module Attributes') }} - - .. autosummary:: - {% for item in attributes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block functions %} - {% if functions %} - .. rubric:: {{ _('Functions') }} - - .. autosummary:: - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} +.. autosummary:: + :toctree: - {% block classes %} - {% if classes %} - .. rubric:: {{ _('Classes') }} +{% for item in classes %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} - .. autosummary:: - {% for item in classes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} +{% block exceptions %} +{% if exceptions %} +Exception Types +--------------- - {% block exceptions %} - {% if exceptions %} - .. rubric:: {{ _('Exceptions') }} +.. autosummary:: + :toctree: - .. autosummary:: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} +{% for item in exceptions %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} -{% block modules %} -{% if modules %} -.. rubric:: Modules +{% block functions %} +{% if functions %} +Module Functions +---------------- .. autosummary:: - :toctree: - :recursive: -{% for item in modules %} +{% for item in functions %} {{ item }} {%- endfor %} + +{{ (fullname + " Module Function Documentation") | underline("^") }} +{% for item in functions %} +.. autofunction:: {{ item }} +{%- endfor %} {% endif %} {% endblock %}