Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
65dc84b
Add generate_operators_doc.py & jinja2 template
ansMHanmer Jan 14, 2025
c70a436
Handle edge cases with missing properties data
ansMHanmer Jan 15, 2025
22ac4f1
Add support for ignoring private operators, add license if available
ansMHanmer Jan 15, 2025
c7b661b
cleanup
ansMHanmer Jan 17, 2025
a5a6250
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Jan 17, 2025
568a46b
Fix items flagged by ruff hook
ansMHanmer Jan 17, 2025
9f8474c
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Feb 11, 2025
78147eb
Suggestions
ansMHanmer Feb 11, 2025
5ec8abe
Process new inputs properly
ansMHanmer Feb 11, 2025
cd12ecc
Style
ansMHanmer Feb 12, 2025
21ed34e
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Feb 19, 2025
73b6e77
Add YAML frontmatter metadata to operators doc template
ansMHanmer Feb 19, 2025
ae5c6a2
Handle errors in fetching config_specification
ansMHanmer Feb 19, 2025
3a62bcc
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Feb 21, 2025
e84339c
PR suggestion
ansMHanmer Feb 21, 2025
c6ac5f0
Restructure output
ansMHanmer Mar 19, 2025
6909cb5
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Mar 19, 2025
5aaab20
Add toc tree generation
ansMHanmer Mar 21, 2025
0d2ab7d
Remove unnecessary json file write
ansMHanmer Mar 21, 2025
7260da4
Ruff
ansMHanmer Mar 21, 2025
122e299
Feedback
ansMHanmer Mar 21, 2025
2c03c97
Remove space in toc.yml generation
ansMHanmer Mar 21, 2025
0742253
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Jul 3, 2025
1863d2a
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Jul 7, 2025
9c3145f
Merge branch 'master' into mhanmer/operator_doc_template
ansMHanmer Jul 7, 2025
6d9d5ac
Add new templates, updated script, updated .gitignore
ansMHanmer Jul 7, 2025
32b41ce
Style
ansMHanmer Jul 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions .ci/generate_operators_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import argparse
from pathlib import Path

from jinja2 import Template

from ansys.dpf import core as dpf
from ansys.dpf.core.changelog import Changelog
from ansys.dpf.core.core import load_library
from ansys.dpf.core.dpf_operator import available_operator_names


def initialize_server(ansys_path=None, include_composites=False, include_sound=False):
server = dpf.start_local_server(ansys_path=ansys_path)
print(server.plugins)
print(f"Ansys Path: {server.ansys_path}")
print(f"Server Info: {server.info}")
print(f"Server Context: {server.context}")
print(f"Server Config: {server.config}")
print(f"Server version: {dpf.global_server().version}")
if include_composites:
print("Loading Composites Plugin")
load_library(
Path(server.ansys_path)
/ "dpf"
/ "plugins"
/ "dpf_composites"
/ "composite_operators.dll"
)
if include_sound:
print("Loading Acoustics Plugin")
load_library(Path(server.ansys_path) / "Acoustics" / "SAS" / "ads" / "dpf_sound.dll")
return server


def fetch_doc_info(server, operator_name):
spec = dpf.Operator.operator_specification(op_name=operator_name, server=server)
input_info = []
output_info = []
configurations_info = []
for input_pin in spec.inputs:
input = spec.inputs[input_pin]
input_info.append(
{
"pin_number": input_pin,
"name": input.name,
"types": [str(t) for t in input._type_names],
"document": input.document,
"optional": input.optional,
}
)
for output_pin in spec.outputs:
output = spec.outputs[output_pin]
output_info.append(
{
"pin_number": output_pin,
"name": output.name,
"types": [str(t) for t in output._type_names],
"document": output.document,
"optional": output.optional,
}
)
for configuration_key in spec.config_specification:
configuration = spec.config_specification[configuration_key]
configurations_info.append(
{
"name": configuration.name,
"types": [str(t) for t in configuration.type_names],
"document": configuration.document,
"default_value": configuration.default_value_str,
}
)
properties = spec.properties
plugin = properties.pop("plugin", "N/A")

category = properties.pop("category", None)

scripting_name = properties.pop("scripting_name", None)
if category and scripting_name:
full_name = category + "." + scripting_name
else:
full_name = None

user_name = properties.pop("user_name", operator_name)

# Retrieve version and changelog using the Changelog class
if hasattr(spec, "changelog") and isinstance(spec.changelog, dpf.GenericDataContainer):
changelog_gdc = spec.changelog
changelog = Changelog(gdc=changelog_gdc, server=server)
last_version = changelog.last_version
changelog_entries = [
f"Version {str(version)}: {changelog[version]}" for version in changelog.versions
]
else:
last_version = "0.0.0"
changelog_entries = [f"Version {last_version}: Initial release."]

op_friendly_name = user_name
if category:
op_friendly_name = category + ":" + op_friendly_name

license = properties.pop("license", "None")

exposure = properties.pop("exposure", "private")
scripting_info = {
"category": category,
"plugin": plugin,
"scripting_name": scripting_name,
"full_name": full_name,
"internal_name": operator_name,
"license": license,
"version": str(last_version), # Include last version in scripting_info
"changelog": changelog_entries, # Include all changelog entries
}

return {
"operator_name": op_friendly_name,
"operator_description": spec.description,
"inputs": input_info,
"outputs": output_info,
"configurations": configurations_info,
"scripting_info": scripting_info,
"exposure": exposure,
}


def get_plugin_operators(server, plugin_name):
operators = available_operator_names(server)
plugin_operators = []
for operator_name in operators:
spec = dpf.Operator.operator_specification(op_name=operator_name, server=server)
if "plugin" in spec.properties and spec.properties["plugin"] == plugin_name:
plugin_operators.append(operator_name)
return plugin_operators


def generate_operator_doc(server, operator_name, include_private):
operator_info = fetch_doc_info(server, operator_name)
scripting_name = operator_info["scripting_info"]["scripting_name"]
category = operator_info["scripting_info"]["category"]
if scripting_name:
file_name = scripting_name
else:
file_name = operator_name
if "::" in file_name:
file_name = file_name.replace("::", "_")
if not include_private and operator_info["exposure"] == "private":
return
script_path = Path(__file__)
root_dir = script_path.parent.parent
template_dir = Path(root_dir) / "doc" / "source" / "operators_doc" / "operator-specifications"
category_dir = Path(template_dir) / category
if not category_dir.exists() and category is not None:
category_dir.mkdir()
if category is not None:
file_dir = category_dir
else:
file_dir = template_dir
with Path.open(Path(template_dir) / "operator_doc_template.md", "r") as file:
template = Template(file.read())

output = template.render(operator_info)
with Path.open(Path(file_dir) / f"{file_name}.md", "w") as file:
file.write(output)


def generate_toc_tree(docs_path):
# Target the operator-specifications folder for iteration
# operator_specs_path = docs_path / "operator-specifications"
data = []
for folder in docs_path.iterdir():
if folder.is_dir(): # Ensure 'folder' is a directory
category = folder.name
operators = [] # Reset operators for each category
for file in folder.iterdir():
if (
file.is_file() and file.suffix == ".md"
): # Ensure 'file' is a file with .md extension
file_name = file.name
file_path = f"{category}/{file_name}"
operator_name = file_name.replace("_", " ").replace(".md", "")
operators.append({"operator_name": operator_name, "file_path": file_path})
data.append({"category": category, "operators": operators})

# Render the Jinja2 template
template_path = docs_path / "toc_template.j2"
with Path.open(template_path, "r") as template_file:
template = Template(template_file.read())
output = template.render(data=data) # Pass 'data' as a named argument

# Write the rendered output to toc.yml at the operators_doc level
# toc_path = docs_path / "toc.yml"
with Path.open(docs_path / "toc.yml", "w") as file:
file.write(output)


def main():
parser = argparse.ArgumentParser(description="Fetch available operators")
parser.add_argument("--plugin", help="Filter operators by plugin")
parser.add_argument(
"--ansys_path", default=None, help="Path to Ansys DPF Server installation directory"
)
parser.add_argument("--include_private", action="store_true", help="Include private operators")
parser.add_argument(
"--include_composites", action="store_true", help="Include composites operators"
)
parser.add_argument("--include_sound", action="store_true", help="Include sound operators")
args = parser.parse_args()
desired_plugin = args.plugin

server = initialize_server(args.ansys_path, args.include_composites, args.include_sound)
if desired_plugin is None:
operators = available_operator_names(server)
else:
operators = get_plugin_operators(server, desired_plugin)
for operator_name in operators:
generate_operator_doc(server, operator_name, args.include_private)

docs_path = (
Path(__file__).parent.parent
/ "doc"
/ "source"
/ "operators_doc"
/ "operator-specifications"
)
print(docs_path)
generate_toc_tree(docs_path)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ instance/
# Sphinx documentation
doc/_build/

# Operators documentation
doc/source/operators_doc/operator-specifications/**/*
!doc/source/operators_doc/operator-specifications/operator_doc_template.md
!doc/source/operators_doc/operator-specifications/toc_template.j2

# PyBuilder
.pybuilder/
target/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
category: {{ scripting_info.category }}
plugin: {{ scripting_info.plugin }}
license: {{ scripting_info.license }}
---

# {{ operator_name }}

## Description

{{ operator_description }}

## Inputs


| Input | Name| Expected type(s) | Required | Description |
|-------|-----|------|----------|-------------|
{%- for input in inputs %}
| **Pin {{ input.pin_number }}** | {{ input.name }} | {% for t in input.types %}`{{ t }}`{% if not loop.last %}, {% endif %}{% endfor %} | {% if input.optional %}No{% else %}Yes{% endif %} | {{ input.document }} |
{%- endfor %}

## Outputs

| Input | Name| Expected type(s) | Required | Description |
|-------|-----|------|----------|-------------|
{%- for output in outputs %}
| **Pin {{ output.pin_number }}** | {{ output.name }} | {% for t in output.types %}`{{ t }}`{% if not loop.last %}, {% endif %}{% endfor %} | {% if output.optional %}No{% else %}Yes{% endif %} | {{ output.document }} |
{%- endfor %}

## Configurations

| Name| Expected type(s) | Default value | Description |
|-----|------|----------|-------------|
{%- for configuration in configurations %}
| **{{ configuration.name }}** | {% for t in configuration.types %}`{{ t }}`{% if not loop.last %}, {% endif %}{% endfor %} | {{ configuration.default_value }} | {{ configuration.document }} |
{%- endfor %}

## Scripting

| Category| Plugin | Scripting name | Full name | Internal name | License |
|---------|--------|----------------|-----------|---------------|---------|
{{range .scripting_info -}}
| **{{ scripting_info.category }}** | {{ scripting_info.plugin }} | {{ scripting_info.scripting_name }} | {{ scripting_info.full_name }} | {{ scripting_info.internal_name }} | {{ scripting_info.license }} |
{{endfor}}
26 changes: 26 additions & 0 deletions doc/source/operators_doc/operator-specifications/toc_template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
- name: Introduction
href: index.md
- name: Core concepts
items:
- name: DPF overview
href: core-concepts/dpf.md
- name: Data containers
href: core-concepts/data-containers.md
- name: Operator
href: core-concepts/operator.md
- name: Getting started
items:
- name: Using data containers
href: getting-started/using-data-containers.md
- name: Using operators
href: getting-started/using-operators.md
- name: Workflow examples
href: getting-started/workflow-examples.md
- name: Operator specification
items:
{% for category in data -%}
- name: {{ category.category }}
items:{% for operator in category.operators %}
- name: {{ operator.operator_name }}
href: operator-specifications/{{ operator.file_path }}{% endfor %}
{% endfor %}
Loading