优化skill,添加特征树、编辑窗口
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# SimpleCADAPI Agent Capability Guide
|
||||
|
||||
This document is the required backend capability brief for CAD Agent Studio
|
||||
when considering the `simplecadapi` route. It is intentionally an Agent-facing
|
||||
decision document, not a geometry template.
|
||||
|
||||
## Role
|
||||
|
||||
Use this route when the Agent will author backend-native SimpleCADAPI Python
|
||||
source and the server will only execute, validate, normalize to DesignIR 3.0,
|
||||
and publish artifacts.
|
||||
|
||||
SimpleCADAPI is strongest when its standard factories or replayable model graph
|
||||
capabilities materially improve correctness, editability, or validation.
|
||||
|
||||
## Strong Fits
|
||||
|
||||
- The primary requested object is a supported standard mechanical family:
|
||||
spur, helical, herringbone, ring, or bevel gears; racks; cycloidal discs;
|
||||
ball bearings; bolts; nuts; roller-chain sprockets; planetary or cycloidal
|
||||
reducers; joint actuator assemblies.
|
||||
- The task benefits from `@model`, `ModelResult`, `capture_result`, model JSON,
|
||||
graph replay, semantic tags, topology lineage, source mapping, QL inspection,
|
||||
unit checking, tolerance chains, scene packages, or CAD translators.
|
||||
- The task needs strict STEP/B-Rep diagnostics, target/candidate comparison,
|
||||
multi-view diagnostics, or slice XOR diagnostics.
|
||||
- The model is a reusable mechanical product family whose formulas should be a
|
||||
deterministic SDK workflow rather than one-off source construction.
|
||||
|
||||
## Weak Fits
|
||||
|
||||
- The request is a custom machined part, flange, hub adapter, housing, bracket,
|
||||
plate, or fixture where no SimpleCADAPI standard factory directly represents
|
||||
the primary object.
|
||||
- Standard part words appear only as feature context. Examples: bolt holes,
|
||||
screw clearances, nut pockets, bearing seats, gear-mounting holes. These are
|
||||
features of another part, not requests to generate the standard part itself.
|
||||
- The requested shape needs freeform or multi-rail surface work outside the
|
||||
documented SimpleCADAPI vocabulary.
|
||||
- The current source of truth is an existing build123d source and the user did
|
||||
not request backend conversion.
|
||||
|
||||
## Studio Documentation Discipline
|
||||
|
||||
For CAD Agent Studio backend selection, this document is sufficient. Decide
|
||||
from the capability boundary here: what SimpleCADAPI is strong at, what it is
|
||||
weak at, and whether the primary requested object matches those strengths.
|
||||
|
||||
Do not read SimpleCADAPI API manuals, SDK indexes, or per-function pages during
|
||||
ordinary backend selection. The selection task only needs to know what the tool
|
||||
can generate, not the exact API signatures.
|
||||
|
||||
Prefer standard-library functions only when the standard component is the
|
||||
primary requested object and the factory actually covers it.
|
||||
|
||||
## Agent Source Contract
|
||||
|
||||
When choosing this backend, submit raw Python source to `generate_cad` with:
|
||||
|
||||
- `selectedBackend = "simplecadapi"`
|
||||
- `sourceKind = "simplecadapi_python"`
|
||||
- A script that accepts `--step`, `--metadata`, and `--model-json`
|
||||
- STEP export written exactly to the `--step` path
|
||||
- Model JSON written exactly to the `--model-json` path
|
||||
- Metadata JSON written exactly to the `--metadata` path
|
||||
- stdout JSON is optional diagnostic output; file artifacts are the source of
|
||||
truth for execution success
|
||||
|
||||
The source should use one replayable `@model` entry point when model JSON or
|
||||
graph replay is part of the task. Capture explicit outputs with
|
||||
`capture_result(...)` and write the returned `ModelResult.model_json`.
|
||||
|
||||
Do not submit diagnostic, API-probing, topology-probing, radius-sweep, or smoke
|
||||
test scripts as `nativeSource`. Do not use `inspect.signature(...)`, broad
|
||||
`dir(...)` dumps, trial loops, or intentional exceptions to discover the SDK or
|
||||
geometry at execution time. The source submitted to `generate_cad` must be the
|
||||
final model generator for the user's part and must write the requested file
|
||||
artifacts in one execution. If this compact reference is insufficient, report
|
||||
the missing reference instead of using `generate_cad` as an exploration tool.
|
||||
|
||||
If exposing editable parameters, include a top-level block:
|
||||
|
||||
```python
|
||||
# CAD_AGENT_PARAMETERS_START
|
||||
PARAMETERS = {
|
||||
"example": 1.0
|
||||
}
|
||||
# CAD_AGENT_PARAMETERS_END
|
||||
```
|
||||
|
||||
Each editable metadata parameter must include `name`, `value`, `unit`,
|
||||
`editable`, `binding_kind`, `parameter_path`, and `regenerate_adapter`.
|
||||
Use `binding_kind = "python_constant"` for parameters bound to the
|
||||
`PARAMETERS` block, or `model_graph_parameter` only when the parameter is
|
||||
actually replayable through the model graph. Use
|
||||
`regenerate_adapter = "simplecadapi"`.
|
||||
|
||||
## Minimal Native Source Reference
|
||||
|
||||
Use this compact API surface for Studio generation. It is included here so the
|
||||
Agent can write production source without reading API indexes during backend
|
||||
selection.
|
||||
|
||||
```python
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import simplecadapi as scad
|
||||
|
||||
@scad.model(graph_id="model")
|
||||
def build_model():
|
||||
shape = scad.std.gear.make_spur_gear_rsolid(
|
||||
n_teeth=24,
|
||||
module=2.0,
|
||||
pressure_angle=20.0,
|
||||
gear_height=8.0,
|
||||
)
|
||||
scad.capture_result(value=shape)
|
||||
return shape
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--step", required=True)
|
||||
parser.add_argument("--metadata", required=True)
|
||||
parser.add_argument("--model-json", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
result = build_model()
|
||||
Path(args.model_json).write_text(result.model_json, encoding="utf-8")
|
||||
scad.export_step(shapes=result.value, filename=args.step)
|
||||
Path(args.metadata).write_text(json.dumps({"backend": "simplecadapi"}), encoding="utf-8")
|
||||
```
|
||||
|
||||
Replace the body of `build_model()` with the requested standard component or
|
||||
workflow. Keep the CLI/output protocol unchanged.
|
||||
|
||||
Common standard gear factories:
|
||||
|
||||
- `scad.std.gear.make_spur_gear_rsolid(n_teeth: int, module: float, pressure_angle: float = 20.0, gear_height: float = 6.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25, backlash: float = 0.0)`
|
||||
- `scad.std.gear.make_helical_gear_rsolid(n_teeth: int, module: float, pressure_angle: float = 20.0, helix_angle: float = 30.0, gear_height: float = 8.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25, backlash: float = 0.0)`
|
||||
- `scad.std.gear.make_herringbone_gear_rsolid(n_teeth: int, module: float, pressure_angle: float = 20.0, helix_angle: float = 32.0, gear_height: float = 10.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25, backlash: float = 0.0)`
|
||||
- `scad.std.gear.make_spur_ring_gear_rsolid(n_teeth: int, module: float, pressure_angle: float = 20.0, gear_height: float = 6.0, rim_thickness: float = 3.0, backlash: float = 0.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25)`
|
||||
- `scad.std.gear.make_helical_ring_gear_rsolid(n_teeth: int, module: float, pressure_angle: float = 20.0, helix_angle: float = 25.0, gear_height: float = 8.0, rim_thickness: float = 3.0, backlash: float = 0.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25)`
|
||||
- `scad.std.gear.make_straight_bevel_gear_rsolid(n_teeth: int, module: float, pitch_angle: float = 45.0, pressure_angle: float = 20.0, face_width: float = 8.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25, backlash: float = 0.0)`
|
||||
|
||||
## Decision Rule
|
||||
|
||||
Choose SimpleCADAPI only when the primary requested model or required workflow
|
||||
matches its documented standard factories or graph/semantic capabilities. Do
|
||||
not select SimpleCADAPI merely because the prompt mentions a standard part word
|
||||
inside another feature. For example, a wheel hub adapter with bolt holes is a
|
||||
custom flange-like part unless the user asks to generate a bolt as the object.
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate submitted Studio source against the imported SimpleCADAPI runtime.
|
||||
|
||||
This is a server-side guard only. Agent-facing API documentation remains the
|
||||
original SimpleCADAPI skill and its API Markdown pages.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
|
||||
STDLIB_MODULES = ("gear", "bearing", "chain", "fastener")
|
||||
|
||||
# Make the package's public ``scad.std.<module>`` paths available before AST
|
||||
# resolution. Model source may use any of these documented standard factories.
|
||||
for _module_name in STDLIB_MODULES:
|
||||
importlib.import_module(f"simplecadapi.std.{_module_name}")
|
||||
|
||||
|
||||
def resolve_simplecad_path(parts: list[str]) -> Any:
|
||||
value: Any = scad
|
||||
for part in parts:
|
||||
value = getattr(value, part)
|
||||
return value
|
||||
|
||||
|
||||
class SourceValidator(ast.NodeVisitor):
|
||||
def __init__(self) -> None:
|
||||
self.package_aliases: set[str] = set()
|
||||
self.imported_names: dict[str, tuple[str, list[str]]] = {}
|
||||
self.errors: list[dict[str, object]] = []
|
||||
|
||||
def error(self, node: ast.AST, message: str) -> None:
|
||||
self.errors.append({
|
||||
"line": getattr(node, "lineno", None),
|
||||
"column": getattr(node, "col_offset", None),
|
||||
"message": message,
|
||||
})
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
for alias in node.names:
|
||||
if alias.name == "simplecadapi":
|
||||
self.package_aliases.add(alias.asname or "simplecadapi")
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||
module = node.module or ""
|
||||
if module == "simplecadapi" or module.startswith("simplecadapi."):
|
||||
module_parts = module.split(".")[1:]
|
||||
for alias in node.names:
|
||||
if alias.name == "*":
|
||||
self.error(node, "Wildcard imports from SimpleCADAPI are not allowed in submitted source.")
|
||||
continue
|
||||
try:
|
||||
resolve_simplecad_path(module_parts + [alias.name])
|
||||
except AttributeError:
|
||||
self.error(alias, f"SimpleCADAPI import does not exist: {module}.{alias.name}")
|
||||
self.imported_names[alias.asname or alias.name] = (module, module_parts + [alias.name])
|
||||
self.generic_visit(node)
|
||||
|
||||
def attribute_path(self, node: ast.Attribute) -> tuple[str, list[str]] | None:
|
||||
parts: list[str] = [node.attr]
|
||||
current: ast.AST = node.value
|
||||
while isinstance(current, ast.Attribute):
|
||||
parts.append(current.attr)
|
||||
current = current.value
|
||||
if isinstance(current, ast.Name) and current.id in self.package_aliases:
|
||||
return current.id, list(reversed(parts))
|
||||
return None
|
||||
|
||||
def visit_Attribute(self, node: ast.Attribute) -> None:
|
||||
resolved = self.attribute_path(node)
|
||||
if resolved:
|
||||
_, parts = resolved
|
||||
try:
|
||||
resolve_simplecad_path(parts)
|
||||
except AttributeError:
|
||||
self.error(node, f"SimpleCADAPI attribute does not exist: scad.{'.'.join(parts)}")
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
if isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "hasattr", "dir", "inspect"}:
|
||||
self.error(node, "Dynamic or introspective SDK access is not allowed in submitted SimpleCADAPI source.")
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def validate_source(source_path: Path) -> dict[str, object]:
|
||||
try:
|
||||
source = source_path.read_text(encoding="utf-8")
|
||||
tree = ast.parse(source, filename=str(source_path))
|
||||
except (OSError, SyntaxError) as exc:
|
||||
return {"valid": False, "errors": [{"message": f"Cannot parse Python source: {exc}"}]}
|
||||
validator = SourceValidator()
|
||||
validator.visit(tree)
|
||||
if not validator.package_aliases:
|
||||
validator.errors.append({"message": "Submitted SimpleCADAPI source must import simplecadapi as scad."})
|
||||
return {"valid": not validator.errors, "errors": validator.errors}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(validate_source(args.source), ensure_ascii=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user