helper scripts and images
thumbnail images, example images, two pytho n helper scipts
227
docs/assets/examples/create_rst.py
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
"""
|
||||||
|
name: "create_rst.py"
|
||||||
|
title: "Python Script to Create RST Files for Sphinx"
|
||||||
|
authors: "felix@42sol.eu"
|
||||||
|
license: "http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
|
created: "2023-01-21"
|
||||||
|
modified: "2024-01-21"
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Python script to generate documentation in RST format.
|
||||||
|
Used for the `build123d` documentation of examples.
|
||||||
|
TODO: check if we could add Sphinx-Gallery to the project https://sphinx-gallery.github.io/stable/advanced.html
|
||||||
|
NOTE: https://yaml-multiline.info/ is a good site to learn about multiline yaml strings.
|
||||||
|
has_builder_mode: false
|
||||||
|
has_algebra_mode: false
|
||||||
|
image_files:
|
||||||
|
- "none.png"
|
||||||
|
"""
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Imports]
|
||||||
|
import sys
|
||||||
|
import ast # see https://docs.python.org/3/library/ast.html
|
||||||
|
from os import getcwd as pwd # see https://docs.python.org/3/library/os.html#os.getcwd
|
||||||
|
import dataclasses as dc # see https://docs.python.org/3/library/dataclasses.html
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from typing import List, Dict # see https://docs.python.org/3/library/typing.html
|
||||||
|
from pyperclip import copy # see https://pyperclip.readthedocs.io/en/latest/
|
||||||
|
from ruamel.yaml import YAML # see https://yaml.readthedocs.io/en/latest/index.html
|
||||||
|
# and for yaml tutorial see https://www.cloudbees.com/blog/yaml-tutorial-everything-you-need-get-started
|
||||||
|
from shellrunner import X # see https://github.com/adamhl8/shellrunner
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Definitions]
|
||||||
|
log = print
|
||||||
|
stdout = print
|
||||||
|
stderr = print
|
||||||
|
Yes = True
|
||||||
|
No = False
|
||||||
|
def debug(message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Parameters]
|
||||||
|
# - none
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Classes]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Example:
|
||||||
|
name: str
|
||||||
|
title: str
|
||||||
|
authors: str
|
||||||
|
license: str
|
||||||
|
created: str
|
||||||
|
modified: str
|
||||||
|
description: str
|
||||||
|
has_builder_mode: bool
|
||||||
|
has_algebra_mode: bool
|
||||||
|
image_files: List[str]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Functions]
|
||||||
|
|
||||||
|
def wait_for_user():
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
|
||||||
|
def remove_extension( file_name ):
|
||||||
|
output = file_name
|
||||||
|
if output.find('.') != -1:
|
||||||
|
output = file_name.split('.')[:-1][0]
|
||||||
|
return output
|
||||||
|
|
||||||
|
def do_index_rst(file_name, title, has_builder_mode=Yes, has_algebra_mode=No):
|
||||||
|
file_name = remove_extension( file_name )
|
||||||
|
|
||||||
|
modes = ""
|
||||||
|
if has_builder_mode:
|
||||||
|
modes += '|Builder| '
|
||||||
|
if has_algebra_mode:
|
||||||
|
modes += '|Algebra| '
|
||||||
|
|
||||||
|
index_rst = """
|
||||||
|
.. grid-item-card:: {title} {modes}
|
||||||
|
:img-top: assets/examples/thumbnail_{file_name}_01.{extension}
|
||||||
|
:link: examples-{file_name}
|
||||||
|
:link-type: ref
|
||||||
|
"""
|
||||||
|
output = index_rst.format( title=title, modes=modes, file_name=file_name, extension='png' )
|
||||||
|
return output
|
||||||
|
|
||||||
|
def do_code_rst(code_file, has_builder_mode=Yes, has_algebra_mode=No, start_after = "[Code]", end_before = "[End]"):
|
||||||
|
builder_mode = "Builder"
|
||||||
|
builder_algebra = "Algebra"
|
||||||
|
code_file = remove_extension( code_file )
|
||||||
|
|
||||||
|
code_template = """
|
||||||
|
.. dropdown:: |{mode}| Reference Implementation ({mode} Mode)
|
||||||
|
|
||||||
|
.. literalinclude:: ../examples/{file}.py
|
||||||
|
:start-after: {start_after}
|
||||||
|
:end-before: {end_before}
|
||||||
|
"""
|
||||||
|
output = ""
|
||||||
|
if has_builder_mode:
|
||||||
|
output += code_template.format( mode=builder_mode, file=code_file, start_after=start_after, end_before=end_before )
|
||||||
|
if has_algebra_mode:
|
||||||
|
if code_file.find("algebra") == -1:
|
||||||
|
code_file = code_file + "_algebra"
|
||||||
|
output += code_template.format( mode=builder_algebra, file=code_file, start_after=start_after, end_before=end_before )
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def do_images_rst(list_of_files):
|
||||||
|
output = "\n.. dropdown:: More Images\n\n"
|
||||||
|
|
||||||
|
for file in list_of_files:
|
||||||
|
output += f""" .. image:: assets/examples/{file}
|
||||||
|
:align: center\n\n"""
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def do_details_rst(file_name, title, description, image_files=['example_build123d_customizable_logo_01.png'], has_builder_mode=Yes, has_algebra_mode=No, start_after = "[Code]", end_before = "[End]"):
|
||||||
|
file_name = remove_extension( file_name )
|
||||||
|
code_file = file_name
|
||||||
|
output_core = """
|
||||||
|
.. _examples-{example_name}:
|
||||||
|
|
||||||
|
{title}
|
||||||
|
--------------------------------
|
||||||
|
.. image:: assets/examples/{image_file_01}
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
\n\n{description}\n\n"""
|
||||||
|
|
||||||
|
output = output_core.format( example_name=file_name, title=title, description=description, image_file_01=image_files[0] )
|
||||||
|
if len(image_files) > 1:
|
||||||
|
output += do_images_rst(image_files[1:])
|
||||||
|
output += do_code_rst(code_file, has_builder_mode=has_builder_mode, has_algebra_mode=has_algebra_mode, start_after=start_after, end_before=end_before)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def dict_to_dataclass(the_class, data):
|
||||||
|
debug(f"{the_class=}, {data=}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
#field_types = {field.name: field.type for field in dc.fields(the_class)}
|
||||||
|
#return the_class(**{item: dict_to_dataclass(field_types[item], data[item]) for item in data})
|
||||||
|
return the_class(**data)
|
||||||
|
|
||||||
|
except:
|
||||||
|
stderr(f"Error: Could not convert dictionary to dataclass.")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_data_from_docstring_in_file(file_path):
|
||||||
|
"""
|
||||||
|
Extracts the docstring from a Python file.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- file_path (str): Path to the Python file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- data: dict or Example
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
tree = ast.parse(file.read(), filename=file_path)
|
||||||
|
|
||||||
|
# Find the first string literal, which is the docstring
|
||||||
|
docstring_node = next((node for node in ast.walk(tree) if isinstance(node, ast.Str)), None)
|
||||||
|
|
||||||
|
if docstring_node:
|
||||||
|
data = yaml.load(docstring_node.s)
|
||||||
|
data = dict_to_dataclass(Example, data)
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_from_yaml_file(file_path="examples.yaml"):
|
||||||
|
"""
|
||||||
|
Opens a yaml file and returns the content as a dictionary.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- file_path (str): Path to the yaml file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: The content of the yaml file.
|
||||||
|
"""
|
||||||
|
log(pwd())
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
data = yaml.load(file.read())
|
||||||
|
data = dict_to_dataclass(Example, data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
# [Code]
|
||||||
|
if __name__ == "__main__":
|
||||||
|
yaml = YAML(typ='safe')
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
file_path = "../../../examples/benchy_v2024.py"
|
||||||
|
example = get_data_from_docstring_in_file(file_path)
|
||||||
|
|
||||||
|
stdout(f"Data from docstring in file '{file_path}':")
|
||||||
|
if type(example) == Example:
|
||||||
|
stdout(f"Example: {example.name}")
|
||||||
|
else:
|
||||||
|
stderr(f"Error: Could not convert dictionary to dataclass.")
|
||||||
|
|
||||||
|
index_rst = do_index_rst(example.name, title=example.title, has_builder_mode=example.has_builder_mode, has_algebra_mode=example.has_algebra_mode)
|
||||||
|
details_rst = do_details_rst(example.name, title=example.title, description=example.description, image_files=example.image_files, has_builder_mode=example.has_builder_mode, has_algebra_mode=example.has_algebra_mode)
|
||||||
|
copy(index_rst)
|
||||||
|
stdout('index copied to clipboard... please add it to the `examples_1.rst` above `NOTE 01`')
|
||||||
|
wait_for_user()
|
||||||
|
copy(details_rst)
|
||||||
|
stdout('details copied to clipboard... please add it to the `examples_1.rst` above `NOTE 02`')
|
||||||
|
wait_for_user()
|
||||||
|
stdout('now running sphinx via `make html`')
|
||||||
|
X([
|
||||||
|
'cd ../../',
|
||||||
|
'make html',
|
||||||
|
'cd assets/examples',
|
||||||
|
])
|
||||||
|
|
||||||
|
# [End]
|
||||||
65
docs/assets/examples/create_thumbnail_images.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
# [Imports]
|
||||||
|
import argparse # see https://docs.python.org/3/library/argparse.html
|
||||||
|
from PIL import Image # see https://pillow.readthedocs.io/en/stable/
|
||||||
|
log = print
|
||||||
|
|
||||||
|
# [Parameters]
|
||||||
|
fixed_width = 400.0
|
||||||
|
file_name = 'example_{name}.png'
|
||||||
|
|
||||||
|
# [Code]
|
||||||
|
# - Setup Parser
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='create_thumbnail_images.py',
|
||||||
|
description='convert a image to a 400x400px image keeping the aspect ratio of the input',
|
||||||
|
epilog='please see the code in `docs/assets/examples/create_thumbnail_images.py` for more details` for details.')
|
||||||
|
|
||||||
|
parser.add_argument('filename') # positional argument
|
||||||
|
parser.add_argument('-v', '--verbose',
|
||||||
|
action='store_true') # on/off flag
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# - create output file name
|
||||||
|
file_name = args.filename
|
||||||
|
file_thumbnail_name = f'thumbnail_' + file_name.replace('example_', '')
|
||||||
|
|
||||||
|
def make_square(im, min_size=256, fill_color=(0, 0, 0, 0)):
|
||||||
|
x, y = im.size
|
||||||
|
size = max(min_size, x, y)
|
||||||
|
new_im = Image.new('RGBA', (size, size), fill_color)
|
||||||
|
new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
|
||||||
|
return new_im
|
||||||
|
|
||||||
|
# - Load the image
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
log(f'Loading image: {file_name}')
|
||||||
|
|
||||||
|
image = Image.open(file_name)
|
||||||
|
width = image.width
|
||||||
|
height = image.height
|
||||||
|
|
||||||
|
# - Calculate scaling
|
||||||
|
scale_factor = width / fixed_width
|
||||||
|
new_height = height / scale_factor
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
log(f'Image size: {width}x{height}')
|
||||||
|
log(f'Scale factor: {scale_factor}')
|
||||||
|
log(f'New size: {fixed_width}x{new_height}')
|
||||||
|
|
||||||
|
# - Resize the image
|
||||||
|
resized_image = image.resize((int(fixed_width), int(new_height)))
|
||||||
|
|
||||||
|
# - Make the image square
|
||||||
|
resized_image = make_square(resized_image,min_size=int(fixed_width))
|
||||||
|
|
||||||
|
# - Save
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
log(f'Saving image: {file_thumbnail_name}')
|
||||||
|
|
||||||
|
resized_image.save(file_thumbnail_name)
|
||||||
|
|
||||||
|
# [End]
|
||||||
BIN
docs/assets/examples/example_benchy_01.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
docs/assets/examples/example_benchy_02.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/assets/examples/example_benchy_03.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/assets/examples/example_boxes_on_faces_01.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/assets/examples/example_build123d_customizable_logo_01.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
31
docs/assets/examples/examples.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
# Example file helper file
|
||||||
|
benchy:
|
||||||
|
name: "benchy.py"
|
||||||
|
title: "Low Poly Benchy"
|
||||||
|
authors: "Gumyr"
|
||||||
|
license: "http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
|
created: "2023-07-09"
|
||||||
|
modified: "2024-01-09"
|
||||||
|
|
||||||
|
file_name: "benchy"
|
||||||
|
description: >
|
||||||
|
STL import and edit example
|
||||||
|
The Benchy examples shows hot to import a STL model as a `Solid` object and change it.
|
||||||
|
|
||||||
|
> *Attribution:*
|
||||||
|
> The low-poly-benchy used in this example is by `reddaugherty`, see
|
||||||
|
> https://www.printables.com/model/151134-low-poly-benchy.
|
||||||
|
|
||||||
|
.. dropdown:: Info
|
||||||
|
|
||||||
|
- uses file `low_poly_benchy.stl`
|
||||||
|
- uses `class Mesher`
|
||||||
|
- uses `group_by` and `sort_by`
|
||||||
|
- uses `make_polygon`
|
||||||
|
- uses `split`
|
||||||
|
has_builder_mode: true
|
||||||
|
has_algebra_mode: false
|
||||||
|
image_files:
|
||||||
|
- "example_benchy_01.png"
|
||||||
|
- "example_benchy_02.png"
|
||||||
BIN
docs/assets/examples/thumbnail_benchy_01.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/assets/examples/thumbnail_boxes_on_faces_01.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 46 KiB |