Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
dbdf8e6
Add generate_operators_doc.py & jinja2 template
ansMHanmer Jan 14, 2025
73750bc
Handle edge cases with missing properties data
ansMHanmer Jan 15, 2025
a7d416b
Add support for ignoring private operators, add license if available
ansMHanmer Jan 15, 2025
0e5d78b
cleanup
ansMHanmer Jan 17, 2025
f4c49f3
Fix items flagged by ruff hook
ansMHanmer Jan 17, 2025
429bc81
Suggestions
ansMHanmer Feb 11, 2025
121fc6c
Process new inputs properly
ansMHanmer Feb 11, 2025
3219e4b
Style
ansMHanmer Feb 12, 2025
4c362bf
Add YAML frontmatter metadata to operators doc template
ansMHanmer Feb 19, 2025
c32c159
Handle errors in fetching config_specification
ansMHanmer Feb 19, 2025
91479c0
PR suggestion
ansMHanmer Feb 21, 2025
6d8c411
Restructure output
ansMHanmer Mar 19, 2025
d39abe2
Add toc tree generation
ansMHanmer Mar 21, 2025
511b8c4
Remove unnecessary json file write
ansMHanmer Mar 21, 2025
8199f44
Ruff
ansMHanmer Mar 21, 2025
8e10219
Feedback
ansMHanmer Mar 21, 2025
5963236
Remove space in toc.yml generation
ansMHanmer Mar 21, 2025
34fe6b4
add tables in the generated MD files
AnsMelanie Apr 11, 2025
df53b74
added link to types and changed doc structure
PProfizi Jul 9, 2025
0776c6d
Upd page titles and toc.yml
PProfizi Jul 9, 2025
2f6993a
modified operator_doc_template to align with Paul P request. See TFS
AnsMelanie May 28, 2025
ddb5b86
Upd page titles and toc.yml
AnsMelanie Jun 5, 2025
8b090f3
updated anchor links
AnsMelanie Jun 27, 2025
0dc1235
upd python script
AnsMelanie Jun 27, 2025
e0a8d88
upd template with anchor link logic
AnsMelanie Jun 30, 2025
c025a3d
restore script and templates
AnsMelanie Jul 9, 2025
4bc4c11
Upd gitignore file
AnsMelanie Jul 9, 2025
85914a9
Style check
ansMHanmer Jul 9, 2025
627e66c
removed descriptive content from operators_doc
AnsMelanie Jul 10, 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
229 changes: 229 additions & 0 deletions .ci/generate_operators_doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
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 category is not None:
category_dir.mkdir(parents=True, exist_ok=True) # Ensure all parent directories are created
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,62 @@
---
category: {{ scripting_info.category }}
plugin: {{ scripting_info.plugin }}
license: {{ scripting_info.license }}
---

# {{ operator_name }}

**Version: {{ scripting_info.version }}**

## Description

{{ operator_description }}

## Inputs

| Input | Name | Expected type(s) | Description |
|-------|-------|------------------|-------------|
{%- for input in inputs %}
| {% if not input.optional %}<strong>Pin {{ input.pin_number }}</strong> <br><span style="background-color:#d93025; color:white; padding:2px 6px; border-radius:3px; font-size:0.75em;">Required</span>{% else %}<strong>Pin {{ input.pin_number }}</strong>{% endif %}| {{ input.name }} |

Check warning on line 20 in doc/source/operators_doc/operator-specifications/operator_doc_template.md

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/operators_doc/operator-specifications/operator_doc_template.md#L20

[Google.FirstPerson] Avoid first-person pronouns such as ' i '.
Raw output
{"message": "[Google.FirstPerson] Avoid first-person pronouns such as ' i '.", "location": {"path": "doc/source/operators_doc/operator-specifications/operator_doc_template.md", "range": {"start": {"line": 20, "column": 1}}}, "severity": "WARNING"}
{%- for t in input.types -%}{% if "::" in t %}{{ t }}{% elif t == "int32" or t == "bool" or t == char or t == "double" or t == "string" or t == "uint32" or t == "uint64" or t == "vector<int32>" or t == "vector<bool>" or t == "vector<char>" or t == "vector<double>" or t == "vector<string>" or t == "vector<float>" %}[`{{ t }}`](../../core-concepts/dpf-types.md#standard-types}}){% elif t.startswith("abstract_") %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("abstract_", "") | replace("_", "-") | replace (" ", "-") | lower}}){% else %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("_", "-") | replace(" ", "-") | lower}}){% endif %}{% if not loop.last %}, {% endif %}{%- endfor %} | {{ input.document | replace("\n", "<br>") }} |
{%- endfor %}

## Outputs

| Output | Name | Expected type(s) | Description |
|-------|------|------------------|-------------|
{%- for output in outputs %}
| **Pin {{ output.pin_number }}**| {{ output.name }} |
{%- for t in output.types -%}{% if "::" in t %}{{ t }}{% elif t == "int32" or t == "bool" or t == char or t == "double" or t == "string" or t == "uint32" or t == "uint64" or t == "vector<int32>" or t == "vector<bool>" or t == "vector<char>" or t == "vector<double>" or t == "vector<string>" or t == "vector<float>" %}[`{{ t }}`](../../core-concepts/dpf-types.md#standard-types}}){% elif t.startswith("abstract_") %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("abstract_", "") | replace("_", "-") | replace (" ", "-") | lower}}){% else %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("_", "-") | replace(" ", "-") | lower}}){% endif %}{% if not loop.last %}, {% endif %}{%- endfor %} | {{ output.document }} |
{%- endfor %}

## Configurations

| Name| Expected type(s) | Default value | Description |
|-----|------|----------|-------------|
{%- for configuration in configurations %}
| **{{ configuration.name }}** |
{%- for t in configuration.types -%}{% if "::" in t %}{{ t }}{% elif t == "int32" or t == "bool" or t == char or t == "double" or t == "string" or t == "uint32" or t == "uint64" or t == "vector<int32>" or t == "vector<bool>" or t == "vector<char>" or t == "vector<double>" or t == "vector<string>" or t == "vector<float>" %}[`{{ t }}`](../../core-concepts/dpf-types.md#standard-types}}){% elif t.startswith("abstract_") %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("abstract_", "") | replace("_", "-") | replace (" ", "-") | lower}}){% else %}[`{{ t }}`](../../core-concepts/dpf-types.md#{{ t | replace("_", "-") | replace(" ", "-") | lower}}){% endif %}{% if not loop.last %}, {% endif %}{%- endfor %} | {{ configuration.default_value }} | {{ configuration.document }} |
{%- endfor %}

## Scripting

**Category**: {{ scripting_info.category }}

**Plugin**: {{ scripting_info.plugin }}

**Scripting name**: {{ scripting_info.scripting_name }}

**Full name**: {{ scripting_info.full_name }}

**Internal name**: {{ scripting_info.internal_name }}

**License**: {{ scripting_info.license }}


## Changelog

{%- for entry in scripting_info.changelog %}

- {{ entry }}
{%- endfor %}
24 changes: 24 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,24 @@
- name: Introduction
href: index.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: Core concepts
items:
- name: Available types
href: core-concepts/dpf-types.md
- name: Operator
href: core-concepts/operator.md
- name: Operator specifications
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