提交env

This commit is contained in:
2026-08-21 15:22:15 +08:00
parent 9d331cc37f
commit 91a443d990
7 changed files with 117 additions and 3 deletions
+1
View File
@@ -5,6 +5,7 @@
# Environment files
.env
.env.*
!backend/.env
!.env.example
!.env.*.example
+31
View File
@@ -0,0 +1,31 @@
# Default provider. Only providers with an API key are exposed to the UI.
CDSL_DEFAULT_PROVIDER=deepseek
CDSL_DEFAULT_MODEL=deepseek-chat
# DeepSeek. Fill in your own API key below.
CDSL_LLM_BASE_URL=https://api.deepseek.com/v1
CDSL_LLM_API_KEY=sk-d3f8fe84bf9a4100a6563559e5d2fefd
CDSL_LLM_MODEL=deepseek-chat
CDSL_LLM_TIMEOUT_S=90
# Optional OpenAI provider. Comma-separate enabled models; list vision models
# separately so image attachments can be routed safely.
CDSL_OPENAI_BASE_URL=https://api.openai.com/v1
CDSL_OPENAI_API_KEY=
CDSL_OPENAI_MODELS=gpt-4.1,gpt-4.1-mini
CDSL_OPENAI_VISION_MODELS=gpt-4.1,gpt-4.1-mini
# Optional Kimi provider.
CDSL_KIMI_BASE_URL=https://api.moonshot.cn/v1
CDSL_KIMI_API_KEY=
CDSL_KIMI_MODELS=moonshot-v1-8k
CDSL_KIMI_VISION_MODELS=
# Enable only after verifying that the selected endpoint supports strict
# OpenAI-compatible function schemas. This affects generate_cdsl_model's JSON
# arguments only, never ordinary assistant chat text.
CDSL_DEEPSEEK_STRICT_TOOL_SCHEMA=false
CDSL_OPENAI_STRICT_TOOL_SCHEMA=true
CDSL_KIMI_STRICT_TOOL_SCHEMA=true
# To enable individual models instead of every model from a provider, keep the
# provider-wide switch false and list exact IDs, for example:
# CDSL_OPENAI_STRICT_TOOL_MODELS=gpt-4.1
+11 -1
View File
@@ -103,6 +103,15 @@
"required": ["angle_deg", "axis"],
"additionalProperties": false
},
"sphereParams": {
"type": "object",
"properties": {
"radius_mm": {"$ref": "#/$defs/positive"},
"center_mm": {"$ref": "#/$defs/point3"}
},
"required": ["radius_mm", "center_mm"],
"additionalProperties": false
},
"holeBaseParams": {
"type": "object",
"properties": {
@@ -290,7 +299,7 @@
"required": ["type", "contours"],
"additionalProperties": false
},
"feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "reference_plane", "reference_axis", "hole_wizard"]},
"feature_atomic_ids": {"enum": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "fillet", "chamfer", "pattern_linear", "pattern_mirror", "reference_plane", "reference_axis", "hole_wizard"]},
"feature": {
"type": "object",
"properties": {
@@ -312,6 +321,7 @@
{"if": {"properties": {"atomic_id": {"const": "extrude_cut_blind"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/extrudeParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "revolve_add"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/revolveParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "revolve_cut"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/revolveParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "sphere_add"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/sphereParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "hole_blind"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/holeBlindParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "hole_countersink"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/holeCountersinkParams"}}}},
{"if": {"properties": {"atomic_id": {"const": "hole_counterbore"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/holeCounterboreParams"}}}},
@@ -22,6 +22,7 @@ REQUIRED = {
"hole_blind": ["diameter_mm", "depth_mm"],
"hole_countersink": ["diameter_mm", "depth_mm"],
"hole_counterbore": ["diameter_mm", "depth_mm"],
"sphere_add": ["radius_mm", "center_mm"],
}
+12 -1
View File
@@ -28,6 +28,7 @@ from build123d import ( # noqa: E402
Mode,
Plane,
Polygon,
Sphere,
Vector,
Wire,
export_step,
@@ -49,6 +50,7 @@ SUPPORTED_ATOMIC_IDS = frozenset({
"hole_blind",
"hole_countersink",
"hole_counterbore",
"sphere_add",
})
@@ -265,7 +267,16 @@ def run_engine_plan(
sketch = step.get("sketch")
sid = step.get("step_id")
if atomic in ("extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind"):
if atomic == "sphere_add":
radius = _f(params.get("radius_mm") or 0)
center = params.get("center_mm") or [0, 0, 0]
if radius <= 0 or len(center) != 3:
raise ValueError(f"{sid}: sphere_add requires a positive radius_mm and center_mm")
with Locations((_f(center[0]), _f(center[1]), _f(center[2]))):
Sphere(radius, mode=Mode.ADD)
log.append(f"{sid}: sphere_add radius={radius}")
elif atomic in ("extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind"):
if sketch is None:
raise ValueError(f"{sid}: missing sketch")
plane = _plane_from_workplane(sketch.get("workplane") or {})
@@ -4,7 +4,7 @@
"cdsl_json_schema_file": "cdsl_schema.json",
"maintenance_rule": "The semantic CDSL contract is a superset of the current runtime. runtime_supported_atomic_ids and runtime_supported_profiles must stay synchronized with sketch_solver.py, llm_compiler.py and llm_engine.py; deferred entries describe future engine work.",
"coordinate_convention": "All profile dimensions use millimetres. Two-dimensional points are [u, v] in the sketch workplane.",
"runtime_supported_atomic_ids": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore"],
"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"],
"runtime_supported_profiles": ["circle", "annulus", "circles", "circle_grid", "rectangle", "rectangle_with_circles", "rectangle_with_fillets", "obround", "polygon", "ibone", "rectangle_with_symmetric_notches", "revolve_chamfer", "revolve_chamfer_slanted", "circle_with_arc_notches", "circular_sector_slot", "circle_with_radial_tabs", "filleted_rect_side_slots", "d_shape", "partial_ring", "partial_ring_with_arc_island", "arc_chain", "radial_slot", "patterned_cutouts", "compound_patterned_cutouts", "complex_arc_shape", "unknown_shape"],
"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},
@@ -15,6 +15,7 @@
"hole_blind": {"summary": "Cut one or more blind cylindrical holes in the current body.", "required_params": ["diameter_mm", "depth_mm", "positions"], "optional_params": ["host_face", "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.", "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"], "optional_params": ["host_face", "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.", "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"], "optional_params": ["host_face", "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.", "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, "execution_status": "deferred"},
"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, "execution_status": "deferred"},
"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, "execution_status": "deferred"},
+59
View File
@@ -0,0 +1,59 @@
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
from build123d import import_step
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend" / "engine"))
import cdsl_engine # noqa: E402
class SphereAddTests(unittest.TestCase):
def test_cdsl_only_rebuilds_a_sphere_at_the_requested_center(self) -> None:
cdsl = {
"schema": "cad.cdsl.llm.v1",
"schema_version": "1.0.0",
"kind": "part",
"part_id": "sphere_test",
"meta": {"unit": "mm"},
"geometry": {
"sketches": [{
"id": "sphere_locator",
"workplane": {
"origin_mm": [0, 0, 0],
"x_dir": [1, 0, 0],
"normal": [0, 0, 1],
},
"profile": {"type": "circle", "radius_mm": 1.0},
}],
},
"features": [{
"id": "sphere",
"atomic_id": "sphere_add",
"depends_on": [],
"name": "Test sphere",
"params": {"radius_mm": 2.5, "center_mm": [3.0, -4.0, 5.0]},
"sketch_id": "sphere_locator",
}],
}
with tempfile.TemporaryDirectory() as directory:
out_step = Path(directory) / "sphere.step"
result = cdsl_engine.run_rebuild(cdsl, out_step)
shape = import_step(str(out_step))
bbox = shape.bounding_box()
self.assertEqual(result["engine"], "cdsl_only")
self.assertAlmostEqual(shape.volume, 4 / 3 * 3.141592653589793 * 2.5 ** 3, places=5)
self.assertEqual((bbox.min.X, bbox.min.Y, bbox.min.Z), (0.5, -6.5, 2.5))
self.assertEqual((bbox.max.X, bbox.max.Y, bbox.max.Z), (5.5, -1.5, 7.5))
if __name__ == "__main__":
unittest.main()