* Change to unzip * Download kcl-samples as zip to public dir * Fix fetch:samples, e2e electron still not working * Change error message to be clearer * Refactor so that input and output directories of sim tests can be different * Add kcl samples test implementation * Update output since adding kcl_samples tests * Update kcl-samples branch * Fix git-ignore pattern to only apply to the root * Fix yarn install and yarn fetch:samples to work the first time * Remove unneeded exists check * Change to use kcl-samples in public directory * Add kcl-samples * Update output since updating kcl-samples * Update output files * Change to not fetch samples during yarn install * Update output after merge * Ignore kcl-samples in codespell * WIP: Don't run e2e if only kcl-samples changed * Conditionally run cargo tests * Fix to round floating point values in program memory arrays * Update output since merge and rounding numbers in memory * Fix memory redaction for floating point to find more values * Fix float redaction pattern * Update output since rounding floating point numbers * Add center to floating point pattern * Fix trigger to use picomatch syntax * Update output since rounding center * Remove kcl-samples github workflows * Enable Rust backtrace * Update output after re-running * Update output after changing order of post-extrude commands * Fix to have deterministic order of commands * Update output after reverting ordering changes * Update kcl-samples * Update output after updating samples * Fix error messages to show the names of all samples that failed * Change cargo test command to match current one * Update kcl-samples * Update output since updating kcl-samples * Add generate manifest workflow and yarn script * Fix error check to actually work * Change util function to be what we actually need * Move new files after merge * Fix paths since directory move * Add dependabot updates for kcl-samples * Add GitHub workflow to make PR to kcl-samples repo * Add GitHub workflow to check kcl-samples header comments * Fix worfklow to change to the right directory * Add auto-commit simulation test output changes * Add permissions to workflows * Fix to run git commit step * Install just if needed * Fix directory of justfile * Add installation of cargo-insta * Fix to use underscore * Fix to allow just command failure * Change to always install CLI tools and cache them * Trying to fix overwrite failing * Combine commands * Change reviewer * Change to PR targeting the next branch * Change git commands to not do unnecessary fetch * Comment out trigger for creating a PR * Update kcl-samples from next branch * Update outputs after kcl-samples change * Fix to use bash pipefail * Add rust backtrace * Print full env from sim tests * Change command to use long option name * Fix to use ci profile even when calling through just * Add INSTA_UPDATE=always * Fix git push by using an app token on checkout * Add comments * Fix to use bash options * Change to echo when no changes are found * Fix so that kcl-samples updates don't trigger full run * Fix paths to reflect new crate location * Fix path detection * Fix e2e job to ignore kcl_samples simulation test output * Fix the fetch logic for the KCL samples after vendoring (#5661) Fixes the last 2 E2E tests for #5460. --------- Co-authored-by: Pierre Jacquier <pierre@zoo.dev> Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com> Co-authored-by: Frank Noirot <frank@zoo.dev>
197 lines
6.1 KiB
Python
197 lines
6.1 KiB
Python
import asyncio
|
|
import os
|
|
import re
|
|
from concurrent.futures import ProcessPoolExecutor
|
|
from io import BytesIO
|
|
from operator import itemgetter
|
|
from pathlib import Path
|
|
|
|
import kcl
|
|
import requests
|
|
from PIL import Image
|
|
|
|
RETRIES = 5
|
|
|
|
|
|
def export_step(kcl_path: Path, save_path: Path) -> bool:
|
|
# determine the current directory
|
|
try:
|
|
export_response = asyncio.run(
|
|
kcl.execute_and_export(str(kcl_path.parent), kcl.FileExportFormat.Step)
|
|
)
|
|
|
|
stl_path = save_path.with_suffix(".step")
|
|
|
|
with open(stl_path, "wb") as out:
|
|
out.write(bytes(export_response[0].contents))
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(e)
|
|
return False
|
|
|
|
|
|
def find_files(
|
|
path: str | Path, valid_suffixes: list[str], name_pattern: str | None = None
|
|
) -> list[Path]:
|
|
"""
|
|
Recursively find files in a folder by a list of provided suffixes or file naming pattern
|
|
|
|
Args:
|
|
path: str | Path
|
|
Root folder to search
|
|
valid_suffixes: Container[str]
|
|
List of valid suffixes to find files by (e.g. ".stp", ".step")
|
|
name_pattern: str
|
|
Name pattern to additionally filter files by (e.g. "_component")
|
|
|
|
Returns:
|
|
list[Path]
|
|
"""
|
|
path = Path(path)
|
|
valid_suffixes = [i.lower() for i in valid_suffixes]
|
|
return sorted(
|
|
file for file in path.rglob("*")
|
|
if file.suffix.lower() in valid_suffixes and
|
|
(name_pattern is None or re.match(name_pattern, file.name))
|
|
)
|
|
|
|
|
|
def snapshot(kcl_path: Path, save_path: Path) -> bool:
|
|
try:
|
|
snapshot_response = asyncio.run(
|
|
kcl.execute_and_snapshot(str(kcl_path.parent), kcl.ImageFormat.Png)
|
|
)
|
|
|
|
image = Image.open(BytesIO(bytearray(snapshot_response)))
|
|
|
|
im_path = save_path.with_suffix(".png")
|
|
|
|
image.save(im_path)
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(e)
|
|
return False
|
|
|
|
|
|
def update_step_file_dates(step_file_path: Path) -> None:
|
|
# https://github.com/KittyCAD/cli/blob/main/src/cmd_kcl.rs#L1092
|
|
regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+\+\d{2}:\d{2}"
|
|
subst = r"1970-01-01T00:00:00.0+00:00"
|
|
|
|
with open(step_file_path, "r") as inp:
|
|
contents = inp.read()
|
|
|
|
contents = re.sub(regex, subst, contents)
|
|
|
|
with open(step_file_path, "w") as out:
|
|
out.write(contents)
|
|
|
|
|
|
def process_single_kcl(kcl_path: Path) -> dict:
|
|
# The part name is the parent folder since each file is main.kcl
|
|
part_name = kcl_path.parent.name
|
|
|
|
print(f"Processing {part_name}")
|
|
|
|
# determine the root dir, which is where this python script
|
|
root_dir = Path(__file__).parent
|
|
# step and screenshots for the part are based on the root dir
|
|
step_path = root_dir / "step" / part_name
|
|
screenshots_path = root_dir / "screenshots" / part_name
|
|
|
|
# attempt step export
|
|
export_status = export_step(kcl_path=kcl_path, save_path=step_path)
|
|
count = 1
|
|
while not export_status and count < RETRIES:
|
|
export_status = export_step(kcl_path=kcl_path, save_path=step_path)
|
|
count += 1
|
|
|
|
# attempt screenshot
|
|
snapshot_status = snapshot(kcl_path=kcl_path, save_path=screenshots_path)
|
|
count = 1
|
|
while not snapshot_status and count < RETRIES:
|
|
snapshot_status = snapshot(kcl_path=kcl_path, save_path=screenshots_path)
|
|
count += 1
|
|
|
|
# find relative paths, used for building the README.md
|
|
kcl_rel_path = kcl_path.relative_to(Path(__file__).parent)
|
|
step_rel_path = step_path.relative_to(Path(__file__).parent).with_suffix(".step")
|
|
screenshot_rel_path = screenshots_path.relative_to(Path(__file__).parent).with_suffix(".png")
|
|
|
|
# readme string for the part
|
|
readme_entry = (
|
|
f"#### [{part_name}]({kcl_rel_path}) ([step]({step_rel_path})) ([screenshot]({screenshot_rel_path}))\n"
|
|
f"[]({kcl_rel_path})"
|
|
)
|
|
|
|
return {"filename": f"{kcl_rel_path}", "export_status": export_status, "snapshot_status": snapshot_status,
|
|
"readme_entry": readme_entry}
|
|
|
|
|
|
def update_readme(new_content: str, search_string: str = '---\n') -> None:
|
|
with open("README.md", 'r', encoding='utf-8') as file:
|
|
lines = file.readlines()
|
|
|
|
# Find the line containing the search string
|
|
found_index = -1
|
|
for i, line in enumerate(lines):
|
|
if search_string in line:
|
|
found_index = i
|
|
break
|
|
|
|
new_lines = lines[:found_index + 1]
|
|
new_lines.append(new_content)
|
|
|
|
# Write the modified content back to the file
|
|
with open("README.md", 'w', encoding='utf-8') as file:
|
|
file.writelines(new_lines)
|
|
file.write("\n")
|
|
|
|
|
|
def main():
|
|
kcl_files = find_files(path=Path(__file__).parent, valid_suffixes=[".kcl"], name_pattern="main")
|
|
|
|
# run concurrently
|
|
with ProcessPoolExecutor(max_workers=5) as executor:
|
|
futures = [executor.submit(process_single_kcl, kcl_file) for kcl_file in kcl_files]
|
|
results = [future.result() for future in futures]
|
|
|
|
results = sorted(results, key=itemgetter('filename'))
|
|
|
|
step_files = find_files(path=Path(__file__).parent, valid_suffixes=[".step"])
|
|
with ProcessPoolExecutor(max_workers=5) as executor:
|
|
_ = [executor.submit(update_step_file_dates, step_file) for step_file in step_files]
|
|
|
|
if False in [i["export_status"] for i in results]:
|
|
comment_body = "The following files failed to export to STEP format:\n"
|
|
for i in results:
|
|
if not i["export_status"]:
|
|
comment_body += f"{i['filename']}\n"
|
|
|
|
url = f"https://api.github.com/repos/{os.getenv('GH_REPO')}/issues/{os.getenv('GH_PR')}/comments"
|
|
|
|
headers = {
|
|
'Authorization': f'token {os.getenv("GH_TOKEN")}',
|
|
}
|
|
|
|
json_data = {
|
|
'body': comment_body,
|
|
}
|
|
|
|
requests.post(url, headers=headers, json=json_data, timeout=60)
|
|
|
|
new_readme_links = []
|
|
for result in results:
|
|
if result["export_status"] and result["snapshot_status"]:
|
|
new_readme_links.append(result["readme_entry"])
|
|
|
|
new_readme_str = "\n".join(new_readme_links)
|
|
|
|
update_readme(new_readme_str)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|