Calc extension (pkg/calc/):
- Python UNO ProtocolHandler with 8 toolbar commands
- SiloClient HTTP client adapted from FreeCAD workbench
- Pull BOM/Project: populates sheets with 28-col format, hidden property
columns, row hash tracking, auto project tagging
- Push: row classification, create/update items, conflict detection
- Completion wizard: 3-step category/description/fields with PN conflict
resolution dialog
- OpenRouter AI integration: generate standardized descriptions from seller
text, configurable model/instructions, review dialog
- Settings: JSON persistence, env var fallbacks, OpenRouter fields
- 31 unit tests (no UNO/network required)
Go ODS library (internal/ods/):
- Pure Go ODS read/write (ZIP of XML, no headless LibreOffice)
- Writer, reader, 10 round-trip tests
Server ODS endpoints (internal/api/ods.go):
- GET /api/items/export.ods, template.ods, POST import.ods
- GET /api/items/{pn}/bom/export.ods
- GET /api/projects/{code}/sheet.ods
- POST /api/sheets/diff
Documentation:
- docs/CALC_EXTENSION.md: extension progress report
- docs/COMPONENT_AUDIT.md: web audit tool design with weighted scoring,
assembly computed fields, batch AI assistance plan
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
"""Local project file management for ODS workbooks.
|
|
|
|
Mirrors the FreeCAD file path pattern from ``pkg/freecad/silo_commands.py``.
|
|
Project ODS files live at::
|
|
|
|
~/projects/sheets/{PROJECT_CODE}/{PROJECT_CODE}.ods
|
|
|
|
The ``SILO_PROJECTS_DIR`` env var (shared with the FreeCAD workbench)
|
|
controls the base directory.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from . import settings as _settings
|
|
|
|
|
|
def get_sheets_dir() -> Path:
|
|
"""Return the base directory for ODS project sheets."""
|
|
return _settings.get_projects_dir() / "sheets"
|
|
|
|
|
|
def get_project_sheet_path(project_code: str) -> Path:
|
|
"""Canonical path for a project workbook.
|
|
|
|
Example: ``~/projects/sheets/3DX10/3DX10.ods``
|
|
"""
|
|
return get_sheets_dir() / project_code / f"{project_code}.ods"
|
|
|
|
|
|
def ensure_project_dir(project_code: str) -> Path:
|
|
"""Create the project sheet directory if needed and return its path."""
|
|
d = get_sheets_dir() / project_code
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
return d
|
|
|
|
|
|
def project_sheet_exists(project_code: str) -> bool:
|
|
"""Check whether a project workbook already exists locally."""
|
|
return get_project_sheet_path(project_code).is_file()
|
|
|
|
|
|
def save_project_sheet(project_code: str, ods_bytes: bytes) -> Path:
|
|
"""Write ODS bytes to the canonical project path.
|
|
|
|
Returns the Path written to.
|
|
"""
|
|
ensure_project_dir(project_code)
|
|
path = get_project_sheet_path(project_code)
|
|
with open(path, "wb") as f:
|
|
f.write(ods_bytes)
|
|
return path
|
|
|
|
|
|
def read_project_sheet(project_code: str) -> Optional[bytes]:
|
|
"""Read ODS bytes from the canonical project path, or None."""
|
|
path = get_project_sheet_path(project_code)
|
|
if not path.is_file():
|
|
return None
|
|
with open(path, "rb") as f:
|
|
return f.read()
|
|
|
|
|
|
def list_project_sheets() -> list:
|
|
"""Return a list of (project_code, path) tuples for all local sheets."""
|
|
sheets_dir = get_sheets_dir()
|
|
results = []
|
|
if not sheets_dir.is_dir():
|
|
return results
|
|
for entry in sorted(sheets_dir.iterdir()):
|
|
if entry.is_dir():
|
|
ods = entry / f"{entry.name}.ods"
|
|
if ods.is_file():
|
|
results.append((entry.name, ods))
|
|
return results
|