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. +
+""" + +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