YSMlearnsCode commited on
Commit ·
f1a45cb
1
Parent(s): 3c38be2
added updated vectorstore and added prompts
Browse files- generated/result_script.py +73 -81
- prompts/base_instruction.txt +402 -1
- src/llm_client.py +12 -3
generated/result_script.py
CHANGED
|
@@ -1,88 +1,80 @@
|
|
| 1 |
import FreeCAD as App
|
| 2 |
-
import
|
|
|
|
| 3 |
import math
|
| 4 |
-
from FreeCAD import Vector, Placement, Rotation
|
| 5 |
|
| 6 |
-
doc = App.newDocument()
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
obj_rod = doc.addObject("Part::Cylinder", "Rod")
|
| 80 |
-
obj_rod.Radius = rod_radius
|
| 81 |
-
obj_rod.Height = rod_length
|
| 82 |
-
obj_rod.Placement = Placement(Vector(0, 0, rod_placement_z))
|
| 83 |
-
|
| 84 |
-
doc.recompute()
|
| 85 |
-
App.ActiveDocument.ActiveView.fitAll()
|
| 86 |
|
| 87 |
|
| 88 |
import FreeCADGui
|
|
|
|
| 1 |
import FreeCAD as App
|
| 2 |
+
import FreeCADGui as Gui
|
| 3 |
+
from FreeCAD import Vector
|
| 4 |
import math
|
|
|
|
| 5 |
|
|
|
|
| 6 |
|
| 7 |
+
def createFlangeAssembly():
|
| 8 |
+
doc = App.newDocument("Flange")
|
| 9 |
+
|
| 10 |
+
FLANGE_OUTER_DIAMETER = 100.0
|
| 11 |
+
FLANGE_THICKNESS = 7.5
|
| 12 |
+
BORE_INNER_DIAMETER = 50.0
|
| 13 |
+
NECK_HEIGHT = 15.0
|
| 14 |
+
NECK_OUTER_DIAMETER = 60.0
|
| 15 |
+
NUM_BOLT_HOLES = 6
|
| 16 |
+
BOLT_HOLE_DIAMETER = 12.0
|
| 17 |
+
PCD = 75.0
|
| 18 |
+
|
| 19 |
+
total_height = FLANGE_THICKNESS + NECK_HEIGHT
|
| 20 |
+
|
| 21 |
+
flange = doc.addObject("Part::Cylinder", "Flange")
|
| 22 |
+
flange.Radius = FLANGE_OUTER_DIAMETER / 2
|
| 23 |
+
flange.Height = FLANGE_THICKNESS
|
| 24 |
+
|
| 25 |
+
bore = doc.addObject("Part::Cylinder", "CentralBore")
|
| 26 |
+
bore.Radius = BORE_INNER_DIAMETER / 2
|
| 27 |
+
bore.Height = FLANGE_THICKNESS
|
| 28 |
+
bore_cut = doc.addObject("Part::Cut", "FlangeWithBore")
|
| 29 |
+
bore_cut.Base = flange
|
| 30 |
+
bore_cut.Tool = bore
|
| 31 |
+
|
| 32 |
+
neck_outer = doc.addObject("Part::Cylinder", "NeckOuter")
|
| 33 |
+
neck_outer.Radius = NECK_OUTER_DIAMETER / 2
|
| 34 |
+
neck_outer.Height = NECK_HEIGHT
|
| 35 |
+
neck_outer.Placement.Base = Vector(0, 0, FLANGE_THICKNESS)
|
| 36 |
+
|
| 37 |
+
neck_inner = doc.addObject("Part::Cylinder", "NeckInner")
|
| 38 |
+
neck_inner.Radius = BORE_INNER_DIAMETER / 2
|
| 39 |
+
neck_inner.Height = NECK_HEIGHT
|
| 40 |
+
neck_inner.Placement.Base = Vector(0, 0, FLANGE_THICKNESS)
|
| 41 |
+
|
| 42 |
+
neck_hollow = doc.addObject("Part::Cut", "HollowNeck")
|
| 43 |
+
neck_hollow.Base = neck_outer
|
| 44 |
+
neck_hollow.Tool = neck_inner
|
| 45 |
+
|
| 46 |
+
fused = doc.addObject("Part::Fuse", "FlangeAndNeck")
|
| 47 |
+
fused.Base = bore_cut
|
| 48 |
+
fused.Tool = neck_hollow
|
| 49 |
+
|
| 50 |
+
current_shape = fused
|
| 51 |
+
bolt_radius = BOLT_HOLE_DIAMETER / 2
|
| 52 |
+
bolt_circle_radius = PCD / 2
|
| 53 |
+
|
| 54 |
+
for i in range(NUM_BOLT_HOLES):
|
| 55 |
+
angle_deg = 360 * i / NUM_BOLT_HOLES
|
| 56 |
+
angle_rad = math.radians(angle_deg)
|
| 57 |
+
x = bolt_circle_radius * math.cos(angle_rad)
|
| 58 |
+
y = bolt_circle_radius * math.sin(angle_rad)
|
| 59 |
+
|
| 60 |
+
hole = doc.addObject("Part::Cylinder", f"BoltHole_{i+1:02d}")
|
| 61 |
+
hole.Radius = bolt_radius
|
| 62 |
+
hole.Height = total_height
|
| 63 |
+
hole.Placement.Base = Vector(x, y, 0)
|
| 64 |
+
|
| 65 |
+
cut = doc.addObject("Part::Cut", f"Cut_Bolt_{i+1:02d}")
|
| 66 |
+
cut.Base = current_shape
|
| 67 |
+
cut.Tool = hole
|
| 68 |
+
current_shape = cut
|
| 69 |
+
|
| 70 |
+
doc.recompute()
|
| 71 |
+
Gui.activeDocument().activeView().viewAxometric()
|
| 72 |
+
Gui.SendMsgToActiveView("ViewFit")
|
| 73 |
+
|
| 74 |
+
return doc
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
createFlangeAssembly()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
|
| 80 |
import FreeCADGui
|
prompts/base_instruction.txt
CHANGED
|
@@ -28,4 +28,405 @@ You are an assistant that generates valid Python code for FreeCAD.
|
|
| 28 |
For sweeping profiles along a path, use the makePipeShell() method on the path wire instead of a non-existent makeSweep(). Pass a list of profile wires, and control solid creation with parameters:
|
| 29 |
pipe_shape = path_wire.makePipeShell([profile1, profile2, profile3], makeSolid=True, isFrenet=True)
|
| 30 |
- Handling PipeShell Solid Creation Errors
|
| 31 |
-
If sweeping results in BRepOffsetAPI_MakePipeShell::MakeSolid errors, try creating the pipe shell without forcing a solid first (set makeSolid=False) to debug geometry issues:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
For sweeping profiles along a path, use the makePipeShell() method on the path wire instead of a non-existent makeSweep(). Pass a list of profile wires, and control solid creation with parameters:
|
| 29 |
pipe_shape = path_wire.makePipeShell([profile1, profile2, profile3], makeSolid=True, isFrenet=True)
|
| 30 |
- Handling PipeShell Solid Creation Errors
|
| 31 |
+
If sweeping results in BRepOffsetAPI_MakePipeShell::MakeSolid errors, try creating the pipe shell without forcing a solid first (set makeSolid=False) to debug geometry issues:
|
| 32 |
+
|
| 33 |
+
-Use the following functions to make simple solids:
|
| 34 |
+
1. makeBox(length,width,height,[pnt,dir]) #Description: Makes a box located at pnt with the dimensions (length,width,height). By default pnt is Vector(0,0,0) and dir is Vector(0,0,1)
|
| 35 |
+
2. makeCircle(radius,[pnt,dir,angle1,angle2]) #Description: Makes a circle with a given radius. By default pnt is Vector(0,0,0), dir is Vector(0,0,1), angle1 is 0 and angle2 is 360.
|
| 36 |
+
3. makeCone(radius1,radius2,height,[pnt,dir,angle]) #Description: Makes a cone with given radii and height. By default pnt is Vector(0,0,0), dir is Vector(0,0,1) and angle is 360
|
| 37 |
+
4. makeCylinder(radius,height,[pnt,dir,angle]) #Description: Makes a cylinder with a given radius and height. By default pnt is Vector(0,0,0),dir is Vector(0,0,1) and angle is 360
|
| 38 |
+
5. makeHelix(pitch,height,radius,[angle,lefthand,heightstyle]) #Description: Makes a helix shape with a given pitch, height and radius. Defaults to right-handed cylindrical helix. Non-zero angle parameter produces a conical helix. Lefthand True produces left handed helix. Heightstyle applies only to conical helices. Heightstyle False (default) will cause the height parameter to be interpreted as the length of the side of the underlying frustum. Heightstyle True will cause the height parameter to be interpreted as the vertical height of the helix. Pitch is "metric pitch" (advance/revolution). For conical helix, radius is the minor radius.
|
| 39 |
+
6. makeLine((x1,y1,z1),(x2,y2,z2)) #Description: Makes a line of two points
|
| 40 |
+
7. makeLoft(shapelist<profiles>,[boolean<solid>,boolean<ruled>]) #Description: Creates a loft shape using the list of profiles. Optionally make result a solid (vs surface/shell) or make result a ruled surface.
|
| 41 |
+
8. makePlane(length,width,[pnt,dir]) #Description: Makes a plane. By default pnt is Vector(0,0,0) and dir is Vector(0,0,1)
|
| 42 |
+
9. makePolygon(list) #Description: Makes a polygon of a list of Vectors
|
| 43 |
+
10. makeRevolution(Curve,[vmin,vmax,angle,pnt,dir]) #Description: Makes a revolved shape by rotating the curve or a portion of it around an axis given by (pnt,dir). By default vmin/vmax are set to bounds of the curve,angle is 360,pnt is Vector(0,0,0) and dir is Vector(0,0,1)
|
| 44 |
+
11. makeShell(list) #Description: Creates a shell out of a list of faces. Note: Resulting shell should be manifold. Non-manifold shells are not well supported.
|
| 45 |
+
12. makeSolid(Part.Shape) #Description: Creates a solid out of the shells inside a shape.
|
| 46 |
+
13. makeSphere(radius,[center_pnt, axis_dir, V_startAngle, V_endAngle, U_angle]) #Description: Makes a sphere (or partial sphere) with a given radius. By default center_pnt is Vector(0,0,0), axis_dir is Vector(0,0,1), V_startAngle is 0, V_endAngle is 90 and U_angle is 360
|
| 47 |
+
14. makeTorus(radius1,radius2,[pnt,dir,angle1,angle2,angle]) #Description: Makes a torus with a given radii and angles. By default pnt is Vector(0,0,0),dir is Vector(0,0,1),angle1 is 0,angle2 is 360 and angle is 360
|
| 48 |
+
15. makeTube(edge,float) #Description: Creates a tube.
|
| 49 |
+
- ensure all dimensions are correct and parts dont intersect each other
|
| 50 |
+
- Always use from FreeCAD import Vector instead of import FreeCAD.Vector when importing the Vector class in FreeCAD. This is the correct and preferred method.
|
| 51 |
+
- Always use from FreeCAD import Placement instead of import FreeCAD.Placement when importing the Placement class in FreeCAD. This is the correct and preferred method.
|
| 52 |
+
- Always use from FreeCAD import Rotation instead of import FreeCAD.Rotation when importing the Rotation class in FreeCAD. This is the correct and preferred method.
|
| 53 |
+
- use the fuse() function instead of Part.Union(). Use it only where necessary. Only when it is necessary to combine parts
|
| 54 |
+
- make the design tree as well for all parts. In the design tree, I must be able to change the dimension and placement of parts.
|
| 55 |
+
- dont make several features and then copy and make one feature at the end.
|
| 56 |
+
- Make the generated object visible
|
| 57 |
+
- try to make the design as modular as possible. for example, if i need to generate a cup, make the cup body as one feature in design tree and handle as other feature. in this way, i can edit the dimensions of both separately.
|
| 58 |
+
- There is no built-in Part.makeTube function in FreeCAD. You're trying to create a hollow cylinder (tube) directly with parameters like outer_radius, inner_radius, height, but FreeCAD expects shapes, not numbers.
|
| 59 |
+
- dont write comments
|
| 60 |
+
- When converting a Part.BSplineCurve to a wire, do not use .toWire() directly (it does not exist). Instead, convert the spline to an edge with .toShape() and wrap it inside Part.Wire([ ... ]).
|
| 61 |
+
Example:
|
| 62 |
+
wire = Part.Wire([bspline.toShape()])
|
| 63 |
+
- To create ellipse profiles, do not use Part.makeEllipse(). Instead, create a Part.Ellipse(), set the MajorRadius and MinorRadius, then convert it to a wire:
|
| 64 |
+
ellipse = Part.Ellipse()
|
| 65 |
+
ellipse.MajorRadius = width / 2
|
| 66 |
+
ellipse.MinorRadius = height / 2
|
| 67 |
+
wire = Part.Wire([ellipse.toShape()])
|
| 68 |
+
- Sweeping Profiles Along Paths
|
| 69 |
+
For sweeping profiles along a path, use the makePipeShell() method on the path wire instead of a non-existent makeSweep(). Pass a list of profile wires, and control solid creation with parameters:
|
| 70 |
+
pipe_shape = path_wire.makePipeShell([profile1, profile2, profile3], makeSolid=True, isFrenet=True)
|
| 71 |
+
- Handling PipeShell Solid Creation Errors
|
| 72 |
+
If sweeping results in BRepOffsetAPI_MakePipeShell::MakeSolid errors, try creating the pipe shell without forcing a solid first (set makeSolid=False) to debug geometry issues:
|
| 73 |
+
- Dont write comments in the generated code. reduce the number of used tokens
|
| 74 |
+
|
| 75 |
+
- Correct Usage of fuse() in FreeCAD-
|
| 76 |
+
When performing a union (boolean fuse) of multiple shapes in FreeCAD, always use the iterative .fuse() method on Part objects instead of Part.Union().
|
| 77 |
+
|
| 78 |
+
Correct Approach:
|
| 79 |
+
|
| 80 |
+
fan_final_shape = all_parts_to_fuse[0] # Start with the first shape
|
| 81 |
+
for shape in all_parts_to_fuse[1:]: # Iterate over remaining shapes
|
| 82 |
+
fan_final_shape = fan_final_shape.fuse(shape) # Fuse one by one
|
| 83 |
+
Avoid:
|
| 84 |
+
|
| 85 |
+
fan_final_shape = Part.Union(all_parts_to_fuse) # Incorrect method
|
| 86 |
+
|
| 87 |
+
- when asked to "make a flange of OD 100mm, bore size as 50mm, thickness 7.5mm. the height of the middle hollow neck must be 15mm. make 6 m12 holes at PCD 75mm", make the following code:
|
| 88 |
+
import FreeCAD as App
|
| 89 |
+
import FreeCADGui as Gui
|
| 90 |
+
from FreeCAD import Vector
|
| 91 |
+
import math
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def createFlangeAssembly():
|
| 95 |
+
doc = App.newDocument("Flange")
|
| 96 |
+
|
| 97 |
+
# === Parameters ===
|
| 98 |
+
FLANGE_OUTER_DIAMETER = 100.0
|
| 99 |
+
FLANGE_THICKNESS = 7.5
|
| 100 |
+
BORE_INNER_DIAMETER = 50.0
|
| 101 |
+
NECK_HEIGHT = 15.0
|
| 102 |
+
NECK_OUTER_DIAMETER = 60.0
|
| 103 |
+
NUM_BOLT_HOLES = 6
|
| 104 |
+
BOLT_HOLE_DIAMETER = 12.0
|
| 105 |
+
PCD = 75.0
|
| 106 |
+
|
| 107 |
+
total_height = FLANGE_THICKNESS + NECK_HEIGHT
|
| 108 |
+
|
| 109 |
+
# === 1. Create flange base ===
|
| 110 |
+
flange = doc.addObject("Part::Cylinder", "Flange")
|
| 111 |
+
flange.Radius = FLANGE_OUTER_DIAMETER / 2
|
| 112 |
+
flange.Height = FLANGE_THICKNESS
|
| 113 |
+
|
| 114 |
+
# === 2. Cut central bore from flange ===
|
| 115 |
+
bore = doc.addObject("Part::Cylinder", "CentralBore")
|
| 116 |
+
bore.Radius = BORE_INNER_DIAMETER / 2
|
| 117 |
+
bore.Height = FLANGE_THICKNESS
|
| 118 |
+
bore_cut = doc.addObject("Part::Cut", "FlangeWithBore")
|
| 119 |
+
bore_cut.Base = flange
|
| 120 |
+
bore_cut.Tool = bore
|
| 121 |
+
|
| 122 |
+
# === 3. Create neck ===
|
| 123 |
+
neck_outer = doc.addObject("Part::Cylinder", "NeckOuter")
|
| 124 |
+
neck_outer.Radius = NECK_OUTER_DIAMETER / 2
|
| 125 |
+
neck_outer.Height = NECK_HEIGHT
|
| 126 |
+
neck_outer.Placement.Base = Vector(0, 0, FLANGE_THICKNESS)
|
| 127 |
+
|
| 128 |
+
neck_inner = doc.addObject("Part::Cylinder", "NeckInner")
|
| 129 |
+
neck_inner.Radius = BORE_INNER_DIAMETER / 2
|
| 130 |
+
neck_inner.Height = NECK_HEIGHT
|
| 131 |
+
neck_inner.Placement.Base = Vector(0, 0, FLANGE_THICKNESS)
|
| 132 |
+
|
| 133 |
+
neck_hollow = doc.addObject("Part::Cut", "HollowNeck")
|
| 134 |
+
neck_hollow.Base = neck_outer
|
| 135 |
+
neck_hollow.Tool = neck_inner
|
| 136 |
+
|
| 137 |
+
# === 4. Fuse flange (with central hole) and neck ===
|
| 138 |
+
fused = doc.addObject("Part::Fuse", "FlangeAndNeck")
|
| 139 |
+
fused.Base = bore_cut
|
| 140 |
+
fused.Tool = neck_hollow
|
| 141 |
+
|
| 142 |
+
# === 5. Cut bolt holes sequentially ===
|
| 143 |
+
current_shape = fused
|
| 144 |
+
bolt_radius = BOLT_HOLE_DIAMETER / 2
|
| 145 |
+
bolt_circle_radius = PCD / 2
|
| 146 |
+
|
| 147 |
+
for i in range(NUM_BOLT_HOLES):
|
| 148 |
+
angle_deg = 360 * i / NUM_BOLT_HOLES
|
| 149 |
+
angle_rad = math.radians(angle_deg)
|
| 150 |
+
x = bolt_circle_radius * math.cos(angle_rad)
|
| 151 |
+
y = bolt_circle_radius * math.sin(angle_rad)
|
| 152 |
+
|
| 153 |
+
hole = doc.addObject("Part::Cylinder", f"BoltHole_{i+1:02d}")
|
| 154 |
+
hole.Radius = bolt_radius
|
| 155 |
+
hole.Height = total_height
|
| 156 |
+
hole.Placement.Base = Vector(x, y, 0)
|
| 157 |
+
|
| 158 |
+
cut = doc.addObject("Part::Cut", f"Cut_Bolt_{i+1:02d}")
|
| 159 |
+
cut.Base = current_shape
|
| 160 |
+
cut.Tool = hole
|
| 161 |
+
current_shape = cut # update for next iteration
|
| 162 |
+
|
| 163 |
+
# === 6. Final result ===
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
# Recompute and fit view
|
| 167 |
+
doc.recompute()
|
| 168 |
+
Gui.activeDocument().activeView().viewAxometric()
|
| 169 |
+
Gui.SendMsgToActiveView("ViewFit")
|
| 170 |
+
|
| 171 |
+
return doc
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
createFlangeAssembly()
|
| 175 |
+
|
| 176 |
+
use this template whenever asked to make a flange
|
| 177 |
+
|
| 178 |
+
This is a good example for a teapot. Whenever asked to generate a teapot, make something similar:
|
| 179 |
+
import FreeCAD as App
|
| 180 |
+
import FreeCADGui as Gui
|
| 181 |
+
from FreeCAD import Vector, Placement, Rotation
|
| 182 |
+
import Part
|
| 183 |
+
|
| 184 |
+
# Teapot dimensions
|
| 185 |
+
BODY_BOTTOM_RADIUS = 50.0
|
| 186 |
+
BODY_MAX_RADIUS = 80.0
|
| 187 |
+
BODY_HEIGHT = 100.0
|
| 188 |
+
LID_OPENING_RADIUS = 35.0
|
| 189 |
+
|
| 190 |
+
# Spout parameters
|
| 191 |
+
SPOUT_ATTACH_HEIGHT = BODY_HEIGHT * 0.5 # 50.0
|
| 192 |
+
SPOUT_OFFSET_Y = BODY_MAX_RADIUS * 0.7 # 56.0
|
| 193 |
+
SPOUT_LENGTH_HORIZONTAL = 60.0
|
| 194 |
+
SPOUT_LENGTH_VERTICAL = 30.0
|
| 195 |
+
SPOUT_RADIUS = 7.0
|
| 196 |
+
|
| 197 |
+
# Handle parameters
|
| 198 |
+
HANDLE_ATTACH_TOP_HEIGHT = BODY_HEIGHT * 0.7 # 70.0
|
| 199 |
+
HANDLE_ATTACH_BOTTOM_HEIGHT = BODY_HEIGHT * 0.3 # 30.0
|
| 200 |
+
HANDLE_OFFSET_Y = -BODY_MAX_RADIUS * 0.7 # -56.0
|
| 201 |
+
HANDLE_RADIUS = 6.0
|
| 202 |
+
|
| 203 |
+
def createTeapot():
|
| 204 |
+
doc = App.newDocument("Teapot")
|
| 205 |
+
|
| 206 |
+
# --- 1. Body ---
|
| 207 |
+
body_profile_pts = [
|
| 208 |
+
Vector(BODY_BOTTOM_RADIUS, 0, 0),
|
| 209 |
+
Vector(BODY_MAX_RADIUS, 0, BODY_HEIGHT * 0.4),
|
| 210 |
+
Vector(BODY_MAX_RADIUS * 0.8, 0, BODY_HEIGHT * 0.7),
|
| 211 |
+
Vector(LID_OPENING_RADIUS, 0, BODY_HEIGHT)
|
| 212 |
+
]
|
| 213 |
+
body_spline = Part.BSplineCurve(body_profile_pts)
|
| 214 |
+
body_edge = body_spline.toShape()
|
| 215 |
+
line1 = Part.LineSegment(Vector(LID_OPENING_RADIUS, 0, BODY_HEIGHT), Vector(0, 0, BODY_HEIGHT)).toShape()
|
| 216 |
+
line2 = Part.LineSegment(Vector(0, 0, BODY_HEIGHT), Vector(0, 0, 0)).toShape()
|
| 217 |
+
line3 = Part.LineSegment(Vector(0, 0, 0), Vector(BODY_BOTTOM_RADIUS, 0, 0)).toShape()
|
| 218 |
+
wire = Part.Wire([body_edge, line1, line2, line3])
|
| 219 |
+
face = Part.Face(wire)
|
| 220 |
+
body_solid = face.revolve(Vector(0, 0, 0), Vector(0, 0, 1), 360)
|
| 221 |
+
|
| 222 |
+
obj_body = doc.addObject("Part::Feature", "Body")
|
| 223 |
+
obj_body.Shape = body_solid
|
| 224 |
+
obj_body.ViewObject.ShapeColor = (0.9, 0.7, 0.7)
|
| 225 |
+
|
| 226 |
+
# --- 2. Lid ---
|
| 227 |
+
lid_profile_pts = [
|
| 228 |
+
Vector(36.0, 0, 0),
|
| 229 |
+
Vector(36.0, 0, 3.0),
|
| 230 |
+
Vector(35.0, 0, 3.0 + 20.0 * 0.2),
|
| 231 |
+
Vector(17.5, 0, 3.0 + 20.0 * 0.7),
|
| 232 |
+
Vector(10.0, 0, 3.0 + 20.0),
|
| 233 |
+
Vector(5.0, 0, 3.0 + 20.0 + 15.0 * 0.8),
|
| 234 |
+
Vector(0, 0, 3.0 + 20.0 + 15.0)
|
| 235 |
+
]
|
| 236 |
+
lid_spline = Part.BSplineCurve(lid_profile_pts)
|
| 237 |
+
lid_edge = lid_spline.toShape()
|
| 238 |
+
line1 = Part.LineSegment(Vector(0, 0, 3.0 + 20.0 + 15.0), Vector(0, 0, 0)).toShape()
|
| 239 |
+
line2 = Part.LineSegment(Vector(0, 0, 0), Vector(36.0, 0, 0)).toShape()
|
| 240 |
+
wire_lid = Part.Wire([lid_edge, line1, line2])
|
| 241 |
+
face_lid = Part.Face(wire_lid)
|
| 242 |
+
lid_solid = face_lid.revolve(Vector(0, 0, 0), Vector(0, 0, 1), 360)
|
| 243 |
+
|
| 244 |
+
obj_lid = doc.addObject("Part::Feature", "Lid")
|
| 245 |
+
obj_lid.Shape = lid_solid
|
| 246 |
+
obj_lid.Placement = Placement(Vector(0, 0, BODY_HEIGHT), Rotation())
|
| 247 |
+
obj_lid.ViewObject.ShapeColor = (0.9, 0.7, 0.7)
|
| 248 |
+
|
| 249 |
+
# --- 3. Spout (Precomputed final positions) ---
|
| 250 |
+
spout_path_pts = [
|
| 251 |
+
Vector(0, -121, 66), # Original: (0, -56, 50) -> transformed
|
| 252 |
+
Vector(0, -91, 51), # Original: (0, -26, 65) -> transformed
|
| 253 |
+
Vector(0, -61, 36) # Original: (0, 4, 80) -> transformed
|
| 254 |
+
]
|
| 255 |
+
|
| 256 |
+
spout_curve = Part.BSplineCurve(spout_path_pts)
|
| 257 |
+
spout_wire = Part.Wire(spout_curve.toShape())
|
| 258 |
+
|
| 259 |
+
tangent_spout = spout_curve.tangent(spout_curve.FirstParameter)[0]
|
| 260 |
+
tangent_spout.normalize()
|
| 261 |
+
|
| 262 |
+
spout_circle = Part.Circle()
|
| 263 |
+
spout_circle.Center = spout_path_pts[0]
|
| 264 |
+
spout_circle.Axis = tangent_spout
|
| 265 |
+
spout_circle.Radius = SPOUT_RADIUS
|
| 266 |
+
spout_profile = Part.Wire(spout_circle.toShape())
|
| 267 |
+
|
| 268 |
+
spout_solid = spout_wire.makePipe(spout_profile)
|
| 269 |
+
obj_spout = doc.addObject("Part::Feature", "Spout")
|
| 270 |
+
obj_spout.Shape = spout_solid
|
| 271 |
+
obj_spout.ViewObject.ShapeColor = (0.9, 0.7, 0.7)
|
| 272 |
+
|
| 273 |
+
# --- 4. Handle (Precomputed final positions) ---
|
| 274 |
+
handle_path_pts = [
|
| 275 |
+
Vector(0, 56, 31), # Original: (0, 56, 70) -> transformed
|
| 276 |
+
Vector(0, 78, 43), # Original: (0, 78, 58) -> transformed
|
| 277 |
+
Vector(0, 78, 79), # Original: (0, 78, 22) -> transformed
|
| 278 |
+
Vector(0, 56, 71) # Original: (0, 56, 30) -> transformed
|
| 279 |
+
]
|
| 280 |
+
|
| 281 |
+
handle_curve = Part.BSplineCurve(handle_path_pts)
|
| 282 |
+
handle_wire = Part.Wire(handle_curve.toShape())
|
| 283 |
+
|
| 284 |
+
tangent_handle = handle_curve.tangent(handle_curve.FirstParameter)[0]
|
| 285 |
+
tangent_handle.normalize()
|
| 286 |
+
|
| 287 |
+
handle_circle = Part.Circle()
|
| 288 |
+
handle_circle.Center = handle_path_pts[0]
|
| 289 |
+
handle_circle.Axis = tangent_handle
|
| 290 |
+
handle_circle.Radius = HANDLE_RADIUS
|
| 291 |
+
handle_profile = Part.Wire(handle_circle.toShape())
|
| 292 |
+
|
| 293 |
+
handle_solid = handle_wire.makePipe(handle_profile)
|
| 294 |
+
obj_handle = doc.addObject("Part::Feature", "Handle")
|
| 295 |
+
obj_handle.Shape = handle_solid
|
| 296 |
+
obj_handle.ViewObject.ShapeColor = (0.9, 0.7, 0.7)
|
| 297 |
+
|
| 298 |
+
# --- 5. Fuse all parts ---
|
| 299 |
+
fused = obj_body.Shape.fuse(obj_lid.Shape)
|
| 300 |
+
fused = fused.fuse(obj_spout.Shape)
|
| 301 |
+
fused = fused.fuse(obj_handle.Shape)
|
| 302 |
+
|
| 303 |
+
obj_final = doc.addObject("Part::Feature", "Teapot_Complete")
|
| 304 |
+
obj_final.Shape = fused
|
| 305 |
+
obj_final.ViewObject.ShapeColor = (0.9, 0.6, 0.6)
|
| 306 |
+
|
| 307 |
+
# Hide individual parts for clarity
|
| 308 |
+
obj_body.ViewObject.Visibility = False
|
| 309 |
+
obj_lid.ViewObject.Visibility = False
|
| 310 |
+
obj_spout.ViewObject.Visibility = False
|
| 311 |
+
obj_handle.ViewObject.Visibility = False
|
| 312 |
+
|
| 313 |
+
doc.recompute()
|
| 314 |
+
|
| 315 |
+
Gui.activeDocument().activeView().viewAxometric()
|
| 316 |
+
Gui.SendMsgToActiveView("ViewFit")
|
| 317 |
+
|
| 318 |
+
return doc
|
| 319 |
+
|
| 320 |
+
if __name__ == "__main__":
|
| 321 |
+
createTeapot()
|
| 322 |
+
|
| 323 |
+
- This is a good example for a herringbone gear. If asked to make a herringbone gear, generate similar:
|
| 324 |
+
#Herringbone gear
|
| 325 |
+
|
| 326 |
+
import FreeCAD as App
|
| 327 |
+
import FreeCADGui as Gui
|
| 328 |
+
import Part
|
| 329 |
+
import math
|
| 330 |
+
from FreeCAD import Vector, Placement, Rotation
|
| 331 |
+
|
| 332 |
+
def createHerringboneGear(
|
| 333 |
+
num_teeth=20,
|
| 334 |
+
module=5.0,
|
| 335 |
+
pressure_angle_deg=20.0,
|
| 336 |
+
helix_angle_deg=25.0,
|
| 337 |
+
face_width=50.0,
|
| 338 |
+
central_bore_diameter=20.0,
|
| 339 |
+
num_loft_sections=50,
|
| 340 |
+
addendum_factor=1.0,
|
| 341 |
+
dedendum_factor=1.25,
|
| 342 |
+
tooth_radial_offset=1.5 # Teeth pushed radially outward
|
| 343 |
+
):
|
| 344 |
+
doc = App.newDocument("HerringboneGear")
|
| 345 |
+
|
| 346 |
+
pressure_angle_rad = math.radians(pressure_angle_deg)
|
| 347 |
+
helix_angle_rad = math.radians(helix_angle_deg)
|
| 348 |
+
|
| 349 |
+
pitch_diameter = module * num_teeth
|
| 350 |
+
pitch_radius = pitch_diameter / 2
|
| 351 |
+
addendum = addendum_factor * module
|
| 352 |
+
dedendum = dedendum_factor * module
|
| 353 |
+
root_radius = pitch_radius - dedendum + tooth_radial_offset
|
| 354 |
+
outer_radius = pitch_radius + addendum + tooth_radial_offset
|
| 355 |
+
|
| 356 |
+
gear_total_height = face_width
|
| 357 |
+
half_gear_height = gear_total_height / 2
|
| 358 |
+
|
| 359 |
+
total_angular_twist_rad = (face_width * math.tan(helix_angle_rad)) / pitch_radius
|
| 360 |
+
|
| 361 |
+
gear_hub = doc.addObject("Part::Cylinder", "GearHub")
|
| 362 |
+
gear_hub.Radius = outer_radius - tooth_radial_offset
|
| 363 |
+
gear_hub.Height = gear_total_height
|
| 364 |
+
gear_hub.Placement.Base = Vector(0, 0, 0)
|
| 365 |
+
|
| 366 |
+
if central_bore_diameter > 0:
|
| 367 |
+
bore_radius = central_bore_diameter / 2
|
| 368 |
+
central_bore = doc.addObject("Part::Cylinder", "CentralBore")
|
| 369 |
+
central_bore.Radius = bore_radius
|
| 370 |
+
central_bore.Height = gear_total_height
|
| 371 |
+
central_bore.Placement.Base = Vector(0, 0, 0)
|
| 372 |
+
|
| 373 |
+
hub_base = doc.addObject("Part::Cut", "Hub_With_Bore")
|
| 374 |
+
hub_base.Base = gear_hub
|
| 375 |
+
hub_base.Tool = central_bore
|
| 376 |
+
else:
|
| 377 |
+
hub_base = gear_hub
|
| 378 |
+
|
| 379 |
+
angle_per_tooth = 360.0 / num_teeth
|
| 380 |
+
|
| 381 |
+
effective_half_angle_for_flank_base = (math.pi / num_teeth) / 2
|
| 382 |
+
effective_half_angle_for_flank_tip = effective_half_angle_for_flank_base * 0.7
|
| 383 |
+
|
| 384 |
+
P_A = Vector(root_radius * math.sin(effective_half_angle_for_flank_base), root_radius * math.cos(effective_half_angle_for_flank_base), 0)
|
| 385 |
+
P_B = Vector(root_radius * math.sin(-effective_half_angle_for_flank_base), root_radius * math.cos(-effective_half_angle_for_flank_base), 0)
|
| 386 |
+
|
| 387 |
+
P_C = Vector(outer_radius * math.sin(effective_half_angle_for_flank_tip), outer_radius * math.cos(effective_half_angle_for_flank_tip), 0)
|
| 388 |
+
P_D = Vector(outer_radius * math.sin(-effective_half_angle_for_flank_tip), outer_radius * math.cos(-effective_half_angle_for_flank_tip), 0)
|
| 389 |
+
|
| 390 |
+
e_flank1 = Part.LineSegment(P_B, P_D).toShape()
|
| 391 |
+
e_flank2 = Part.LineSegment(P_A, P_C).toShape()
|
| 392 |
+
|
| 393 |
+
def offset_midpoint(p1, p2, offset=0.1):
|
| 394 |
+
mid = (p1 + p2).multiply(0.5)
|
| 395 |
+
vec = p2.sub(p1)
|
| 396 |
+
perp = Vector(-vec.y, vec.x, 0)
|
| 397 |
+
perp.normalize()
|
| 398 |
+
return mid.add(perp.multiply(offset))
|
| 399 |
+
|
| 400 |
+
tip_midpoint = offset_midpoint(P_D, P_C, 0.1)
|
| 401 |
+
e_tip_arc = Part.ArcOfCircle(P_D, tip_midpoint, P_C).toShape()
|
| 402 |
+
|
| 403 |
+
root_midpoint = offset_midpoint(P_A, P_B, 0.1)
|
| 404 |
+
e_root_arc = Part.ArcOfCircle(P_A, root_midpoint, P_B).toShape()
|
| 405 |
+
|
| 406 |
+
try:
|
| 407 |
+
tooth_profile_wire = Part.Wire([e_root_arc, e_flank1, e_tip_arc, e_flank2])
|
| 408 |
+
except Exception as e:
|
| 409 |
+
App.Console.PrintError(f"Error creating tooth profile wire: {e}. Using fallback wire.\n")
|
| 410 |
+
fallback_edges = [
|
| 411 |
+
Part.LineSegment(P_A, P_B).toShape(),
|
| 412 |
+
Part.LineSegment(P_B, P_D).toShape(),
|
| 413 |
+
Part.LineSegment(P_D, P_C).toShape(),
|
| 414 |
+
Part.LineSegment(P_C, P_A).toShape()
|
| 415 |
+
]
|
| 416 |
+
tooth_profile_wire = Part.Wire(fallback_edges)
|
| 417 |
+
|
| 418 |
+
tooth_profile_face = Part.Face(tooth_profile_wire)
|
| 419 |
+
|
| 420 |
+
helical_teeth_LH_fused = None
|
| 421 |
+
lh_z_start = 0
|
| 422 |
+
lh_z_end = half_gear_height
|
| 423 |
+
lh_twist_start = 0
|
| 424 |
+
lh_twist_end = total_angular_twist_rad / 2
|
| 425 |
+
|
| 426 |
+
for tooth_idx in range(num_teeth):
|
| 427 |
+
current_tooth_LH_profiles = []
|
| 428 |
+
initial_tooth_rotation_deg = tooth_idx * angle_per_tooth
|
| 429 |
+
|
| 430 |
+
for i in range(num_loft_sections + 1):
|
| 431 |
+
z_pos_current = lh_z_start + (lh_z_end - lh_z_start) * (i / num_loft_sections)
|
| 432 |
+
current_slice_twist_angle_rad = lh_twist_start + (lh_twist_end - lh_twist_start) * (i / num_loft_sections)
|
src/llm_client.py
CHANGED
|
@@ -20,6 +20,12 @@ REPO_ID = "Yas1n/CADomatic_vectorstore"
|
|
| 20 |
FILENAME_FAISS = "index_oss120b.faiss"
|
| 21 |
FILENAME_PKL = "index_oss120b.pkl"
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
faiss_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME_FAISS)
|
| 24 |
pkl_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME_PKL)
|
| 25 |
download_dir = Path(faiss_path).parent
|
|
@@ -29,9 +35,9 @@ vectorstore = FAISS.load_local(
|
|
| 29 |
str(download_dir),
|
| 30 |
embeddings=embedding,
|
| 31 |
allow_dangerous_deserialization=True,
|
| 32 |
-
index_name="
|
| 33 |
)
|
| 34 |
-
retriever = vectorstore.as_retriever(search_kwargs={"k":
|
| 35 |
|
| 36 |
# Conversation memory
|
| 37 |
memory = ConversationBufferMemory(return_messages=True)
|
|
@@ -39,7 +45,7 @@ memory = ConversationBufferMemory(return_messages=True)
|
|
| 39 |
# Gemini 2.5 Flash (LangChain wrapper, no genai import needed)
|
| 40 |
llm = ChatGoogleGenerativeAI(
|
| 41 |
model="gemini-2.5-flash",
|
| 42 |
-
temperature=
|
| 43 |
api_key=GEMINI_API_KEY # Use the key directly
|
| 44 |
)
|
| 45 |
|
|
@@ -61,6 +67,9 @@ Use the following FreeCAD wiki documentation as context:
|
|
| 61 |
|
| 62 |
{context}
|
| 63 |
|
|
|
|
|
|
|
|
|
|
| 64 |
Here is the conversation so far:
|
| 65 |
{history_text}
|
| 66 |
|
|
|
|
| 20 |
FILENAME_FAISS = "index_oss120b.faiss"
|
| 21 |
FILENAME_PKL = "index_oss120b.pkl"
|
| 22 |
|
| 23 |
+
BASE_PROMPT_PATH = Path(__file__).parent.parent / "prompts" / "base_instruction.txt"
|
| 24 |
+
if not BASE_PROMPT_PATH.exists():
|
| 25 |
+
raise FileNotFoundError(f"Base instruction file not found: {BASE_PROMPT_PATH}")
|
| 26 |
+
with open(BASE_PROMPT_PATH, "r", encoding="utf-8") as f:
|
| 27 |
+
BASE_INSTRUCTIONS = f.read().strip()
|
| 28 |
+
|
| 29 |
faiss_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME_FAISS)
|
| 30 |
pkl_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME_PKL)
|
| 31 |
download_dir = Path(faiss_path).parent
|
|
|
|
| 35 |
str(download_dir),
|
| 36 |
embeddings=embedding,
|
| 37 |
allow_dangerous_deserialization=True,
|
| 38 |
+
index_name="index"
|
| 39 |
)
|
| 40 |
+
retriever = vectorstore.as_retriever(search_kwargs={"k": 15})
|
| 41 |
|
| 42 |
# Conversation memory
|
| 43 |
memory = ConversationBufferMemory(return_messages=True)
|
|
|
|
| 45 |
# Gemini 2.5 Flash (LangChain wrapper, no genai import needed)
|
| 46 |
llm = ChatGoogleGenerativeAI(
|
| 47 |
model="gemini-2.5-flash",
|
| 48 |
+
temperature=0.7,
|
| 49 |
api_key=GEMINI_API_KEY # Use the key directly
|
| 50 |
)
|
| 51 |
|
|
|
|
| 67 |
|
| 68 |
{context}
|
| 69 |
|
| 70 |
+
Here are a few important instructions for you to follow
|
| 71 |
+
{BASE_INSTRUCTIONS}
|
| 72 |
+
|
| 73 |
Here is the conversation so far:
|
| 74 |
{history_text}
|
| 75 |
|