Ganjihong #7

Merged
likang merged 3 commits from ganjihong into main 2026-09-02 13:57:50 +08:00
4 changed files with 66 additions and 33 deletions
+43 -19
View File
@@ -10,39 +10,63 @@ The backend owns the application API and CAD generation workflow:
Expected development entrypoint: `app.main:app`, served by Uvicorn.
## Incremental Generation Configuration
## Reasoning Effort
Incremental generation is enabled by default. It requires a separately
configured vision-capable review model and the Python OpenCascade/Pillow
technical renderer; a run
fails instead of skipping visual review when either is unavailable.
The backend uses the Chat Completions API. Configure a provider's reasoning
budget with `CDSL_<PROVIDER>_REASONING_EFFORT`; for the current OpenAI setup:
```dotenv
# Authoring provider/model must already be configured as usual.
CDSL_INCREMENTAL_GENERATION=1
CDSL_OPENAI_REASONING_EFFORT=medium
```
Use `low`, `medium`, or `high` according to the latency/cost versus quality
tradeoff. The setting is sent as Chat Completions' `reasoning_effort` field to
authoring, streaming, and visual-review requests. Leave it empty to use the
provider/model default. The selected OpenAI-compatible endpoint must support
the requested value.
## Autonomous CDSL Agent Configuration
The autonomous agent writes one frozen free-form `requirements.md`, then
observes, measures, renders and appends one CDSL feature at a time. Its author
uses normal function calls; no provider strict JSON Schema capability or
complete modelling DAG is required. Candidate fragments are rebuilt in a
staging directory through `cdsl_only` before a checkpoint can be committed.
Final publication requires a separately configured vision-capable review model
and the Python OpenCascade/Pillow technical renderer. The agent may build and
inspect intermediate checkpoints without image review; a final run fails
closed if its independent review configuration is unavailable.
```dotenv
# Must name one configured provider and one model listed in that provider's
# CDSL_<PROVIDER>_VISION_MODELS setting. It is intentionally not inferred
# from the authoring model.
CDSL_REVIEW_PROVIDER=openai
CDSL_REVIEW_MODEL=gpt-4.1-mini
CDSL_OPENAI_VISION_MODELS=gpt-4.1-mini
CDSL_REVIEW_PROVIDER=deepseek
CDSL_REVIEW_MODEL=deepseek-v4-flash-vision-exp
CDSL_DEEPSEEK_VISION_MODELS=deepseek-v4-flash-vision-exp
# Install Python rendering dependencies. The renderer reads the revision STEP
# file and creates canonical images without a browser or GPU driver.
pip install -r requirements.txt
# Optional per-node retry budgets.
CDSL_NODE_AUTHORING_ATTEMPTS=2
CDSL_NODE_REPAIR_ATTEMPTS=2
CDSL_NODE_REPLAN_ATTEMPTS=1
# Limits apply to the current checkpoint head, never to total task complexity.
CDSL_AGENT_TOOL_CALLS_PER_CYCLE=12
CDSL_AGENT_CANDIDATE_ATTEMPTS_PER_HEAD=3
CDSL_AGENT_CONSECUTIVE_NO_PROGRESS_LIMIT=6
CDSL_AGENT_MAX_FEATURES_PER_FRAGMENT=6
CDSL_AGENT_CONTEXT_CHAR_LIMIT=24000
CDSL_AGENT_RENDER_CACHE=true
```
Every checkpoint is rebuilt from its fully materialized CDSL through the
The author chooses each coherent 1-6 feature batch. Every rebuilt batch is
rendered and independently reviewed before it can become a checkpoint; only
an accepted reviewer verdict advances the working model. Every checkpoint is rebuilt from its fully materialized CDSL through the
`cdsl_only` runtime. Checkpoint GLB files are preview-only; STEP, CDSL, and
reports are available only after the task reaches `COMPLETED`.
The generation plan contains semantic node IDs only. The backend derives the
unique CDSL feature and sketch IDs from each node, then writes them during
fragment materialization. This keeps naming and topology ownership stable
without requiring the authoring model to reproduce internal identifiers.
The backend assigns feature and sketch IDs, appends causal dependencies and
expands only opaque current-snapshot selector tokens. It does not compile
geometry templates or correct workplanes, profiles, sizes, directions or
boolean semantics authored by the model. Failed candidates remain auditable
but never become revisions.
@@ -399,9 +399,14 @@ class Build123dGeometryAdapter:
def body_geometry(body: Any) -> dict[str, Any]:
# 汇总主体基本几何信息:包围盒与体积。
bbox = body.bounding_box()
# A feature history can contain several body IDs while still ending in
# one connected solid (for example, a base extrusion followed by hole
# cuts). Count the current OCC result, never feature history entries.
solids = list(body.solids()) if hasattr(body, "solids") else [body]
return {
"bbox_mm": [bbox.min.X, bbox.min.Y, bbox.min.Z, bbox.max.X, bbox.max.Y, bbox.max.Z],
"volume_mm3": float(body.volume),
"solid_count": len(solids),
}
@staticmethod
+11 -11
View File
@@ -7,22 +7,22 @@
"runtime_supported_atomic_ids": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "reference_plane", "reference_axis", "hole_wizard", "fillet", "chamfer", "pattern_linear", "pattern_mirror"],
"runtime_supported_profiles": ["circle", "polygon", "analytic_contours"],
"feature_atomic_ids": {
"extrude_add_blind": {"summary": "Add the closed profile by one signed extrusion distance.", "required_params": ["distance_mm"], "optional_params": ["reverse"], "requires_sketch": true},
"extrude_add_two_sided": {"summary": "Add the closed profile with independently captured forward and reverse terminations.", "required_params": ["distance_mm", "reverse_distance_mm"], "optional_params": ["reverse", "end_condition", "reverse_end_condition"], "requires_sketch": true},
"extrude_add_blind": {"summary": "Add the closed profile by one signed extrusion distance.", "required_params": ["distance_mm"], "optional_params": ["reverse"], "requires_sketch": true, "produces_body": true},
"extrude_add_two_sided": {"summary": "Add the closed profile with independently captured forward and reverse terminations.", "required_params": ["distance_mm", "reverse_distance_mm"], "optional_params": ["reverse", "end_condition", "reverse_end_condition"], "requires_sketch": true, "produces_body": true},
"extrude_cut_blind": {"summary": "Remove the closed profile by one signed extrusion distance.", "required_params": ["distance_mm"], "optional_params": ["reverse"], "requires_sketch": true},
"revolve_add": {"summary": "Add the closed profile by revolving it around an axis.", "required_params": ["angle_deg", "axis"], "optional_params": ["reverse"], "requires_sketch": true},
"revolve_add": {"summary": "Add the closed profile by revolving it around an axis.", "required_params": ["angle_deg", "axis"], "optional_params": ["reverse"], "requires_sketch": true, "produces_body": true},
"revolve_cut": {"summary": "Remove the closed profile by revolving it around an axis.", "required_params": ["angle_deg", "axis"], "optional_params": ["reverse"], "requires_sketch": true},
"hole_blind": {"summary": "Cut one or more blind cylindrical holes in the current body.", "required_params": ["diameter_mm", "depth_mm", "positions", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face must provide a strict selector or an explicit frame.", "requires_sketch": true},
"hole_countersink": {"summary": "Cut one or more blind holes with countersink dimensions.", "required_params": ["diameter_mm", "depth_mm", "positions", "countersink_diameter_mm", "countersink_angle_rad", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face must provide a strict selector or an explicit frame.", "requires_sketch": true},
"hole_counterbore": {"summary": "Cut one or more blind holes with counterbore dimensions.", "required_params": ["diameter_mm", "depth_mm", "positions", "counterbore_diameter_mm", "counterbore_depth_mm", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face must provide a strict selector or an explicit frame.", "requires_sketch": true},
"sphere_add": {"summary": "Add one spherical solid at an explicit model-space center.", "required_params": ["radius_mm", "center_mm"], "optional_params": [], "requires_sketch": true},
"fillet": {"summary": "Apply a radius to selected edges or faces.", "required_params": ["radius_mm"], "optional_params": ["tangent_propagation"], "requires_sketch": false},
"chamfer": {"summary": "Apply an equal-distance or angle-distance chamfer to selected edges or faces.", "required_params": ["distance_mm"], "optional_params": ["distance_2_mm", "angle_rad"], "requires_sketch": false},
"hole_blind": {"summary": "Cut one or more blind cylindrical holes in the current body.", "required_params": ["diameter_mm", "depth_mm", "positions", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face is injected from exactly one face selector token.", "requires_sketch": false},
"hole_countersink": {"summary": "Cut one or more blind holes with countersink dimensions.", "required_params": ["diameter_mm", "depth_mm", "positions", "countersink_diameter_mm", "countersink_angle_rad", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face is injected from exactly one face selector token.", "requires_sketch": false},
"hole_counterbore": {"summary": "Cut one or more blind holes with counterbore dimensions.", "required_params": ["diameter_mm", "depth_mm", "positions", "counterbore_diameter_mm", "counterbore_depth_mm", "host_face"], "optional_params": ["drill_angle_rad"], "position_format": "positions is a non-empty array of objects: [{\"mm\":[u_mm,v_mm,w_mm]}]. A bare coordinate array is invalid. host_face is injected from exactly one face selector token.", "requires_sketch": false},
"sphere_add": {"summary": "Add one spherical solid at an explicit model-space center.", "required_params": ["radius_mm", "center_mm"], "optional_params": [], "requires_sketch": true, "produces_body": true},
"fillet": {"summary": "Apply a radius to selected edges or faces.", "required_params": ["radius_mm"], "optional_params": ["tangent_propagation"], "requires_sketch": false, "selector_slot": {"path": "feature.selectors", "min_items": 1, "max_items": 64}},
"chamfer": {"summary": "Apply an equal-distance or angle-distance chamfer to selected edges or faces.", "required_params": ["distance_mm"], "optional_params": ["distance_2_mm", "angle_rad"], "requires_sketch": false, "selector_slot": {"path": "feature.selectors", "min_items": 1, "max_items": 64}},
"pattern_linear": {"summary": "Repeat source features along one or two directions.", "required_params": ["source_feature_ids", "direction_1", "spacing_1_mm", "pattern_count_1"], "optional_params": ["direction_2", "spacing_2_mm", "pattern_count_2"], "requires_sketch": false},
"pattern_mirror": {"summary": "Mirror source features about a selected plane.", "required_params": ["source_feature_ids", "mirror_plane"], "optional_params": [], "requires_sketch": false},
"pattern_mirror": {"summary": "Mirror source features about a selected plane.", "required_params": ["source_feature_ids", "mirror_plane"], "optional_params": [], "requires_sketch": false, "selector_slot": {"path": "params.mirror_plane", "min_items": 1, "max_items": 1}},
"reference_plane": {"summary": "A named reference plane used by sketches or patterns.", "required_params": ["plane"], "optional_params": [], "requires_sketch": false},
"reference_axis": {"summary": "A named reference axis used by revolve or pattern features.", "required_params": ["axis"], "optional_params": [], "requires_sketch": false},
"hole_wizard": {"summary": "A SolidWorks Hole Wizard feature including its typed dimensional contract and placement selectors.", "required_params": ["hole_type", "diameter_mm", "depth_mm"], "optional_params": ["positions", "host_face", "thread", "countersink", "counterbore"], "requires_sketch": false}
"hole_wizard": {"summary": "A SolidWorks Hole Wizard feature including its typed dimensional contract and placement selectors.", "required_params": ["hole_type", "diameter_mm", "depth_mm"], "optional_params": ["positions", "host_face", "thread", "countersink", "counterbore"], "requires_sketch": false, "selector_slot": {"path": "params.host_face", "min_items": 1, "max_items": 1}}
},
"profiles": {
"circle": {
+7 -3
View File
@@ -549,9 +549,13 @@ def _execute_hole(node: FeaturePlanNode, session: ExecutionSession, *, wizard: b
positions_are_local = False
# 3. 解析孔规格 HoleSpec(直径、深度、类型等,wizard 模式提供额外默认值)。
spec = HoleSpec.from_feature(node.atomic_id, node.params, wizard=wizard)
# 4. 确定孔轴向:默认沿宿主面法向,但需保证指向主体内部(按主体中心与面原点的相对位置取反)。
normal = host.normal
inward = normal if vector_dot(vector_subtract(session.adapter.body_center(session.body), host.origin_mm), normal) >= 0 else vector_scale(normal, -1)
# 4. A host-face normal is an outward B-rep orientation, so its inverse
# always enters the material. Inferring direction from the global body
# centre fails for concave or multi-leg parts: for example, the top face
# of an L bracket can sit below the whole body's centre and the old rule
# drilled outward, producing a no-op feature reported as successful.
# The selected topology face is the local, authoritative orientation.
inward = vector_scale(host.normal, -1)
# 5. 生成孔切除工具:按孔规格、起始位置、内方向及“贯穿到主体底面”的深度构造工具实体。
tool = session.adapter.hole_tool(
spec,