|
- Correct Usage of fuse() in FreeCAD- |
|
When performing a union (boolean fuse) of multiple shapes in FreeCAD, always use the iterative .fuse() method on Part objects instead of Part.Union(). |
|
|
|
Correct Approach: |
|
|
|
fan_final_shape = all_parts_to_fuse[0] # Start with the first shape |
|
for shape in all_parts_to_fuse[1:]: # Iterate over remaining shapes |
|
fan_final_shape = fan_final_shape.fuse(shape) # Fuse one by one |
|
Avoid: |
|
|
|
fan_final_shape = Part.Union(all_parts_to_fuse) # Incorrect method |
|
|
|
|
|
- When applying a Placement to a FreeCAD shape (like a Part.Solid or Part.Shape), do not use .Placed(placement) — this method does not exist. |
|
Instead, use .copy() and assign the Placement directly, like this: |
|
|
|
shape = Part.makeBox(10, 10, 10) |
|
placed_shape = shape.copy() |
|
placed_shape.Placement = Placement(Vector(x, y, z), Rotation(Vector(0,0,1), angle)) |
|
Always use .copy() to avoid modifying the original shape directly, and set Placement as an attribute on the copied shape. |
|
|
|
|
|
- Whenever you are asked to make a fastner including nut bolt and screw, you need to make a similar code as the one given below. you have the rag in your context window from where you must write the necessary function of calculating dimensions from screw_maker.py. You need to then make a dummy function for the variables of the screw as asked- |
|
|
|
from screw_maker import * |
|
|
|
try: |
|
import FreeCADGui |
|
GUI_AVAILABLE = True |
|
except ImportError: |
|
GUI_AVAILABLE = False |
|
|
|
|
|
|
|
def makeAllMetalFlangedLockNut(self, fa): |
|
"""Creates a distorted thread lock nut with a flange |
|
Supported types: |
|
- ISO 7044 all metal lock nuts with flange |
|
- ISO 12126 all metal flanged lock nuts with fine pitch thread |
|
""" |
|
dia = self.getDia(fa.calc_diam, True) |
|
if fa.baseType in ["ISO7044", "ISO12126"]: |
|
P, c, _, _, dc, _, _, h, _, m_min, _, s, _, _ = fa.dimTable |
|
m_w = m_min |
|
else: |
|
raise NotImplementedError(f"Unknown fastener type: {fa.Type}") |
|
# main hexagonal body of the nut |
|
shape = self.makeHexPrism(s, h) |
|
# flange of the hex |
|
fm = FSFaceMaker() |
|
fm.AddPoint((1.05 * dia + s) / 4, 0.0) |
|
fm.AddPoint((dc + sqrt3 * c) / 2, 0.0) |
|
fm.AddPoint((dc - c) / 2, 0.0) |
|
fm.AddArc2(0, c / 2, 150) |
|
fm.AddPoint( |
|
(1.05 * dia + s) / 4, |
|
sqrt3 |
|
/ 3 |
|
* ((dc - c) / 2 + c / (4 - 2 * sqrt3) - (1.05 * dia + s) / 4), |
|
) |
|
flange = self.RevolveZ(fm.GetFace()) |
|
shape = shape.fuse(flange).removeSplitter() |
|
# internal bore |
|
fm.Reset() |
|
id = self.GetInnerThreadMinDiameter(dia, P, 0.0) |
|
bore_cham_ht = (dia * 1.05 - id) / 2 * tan15 |
|
fm.AddPoint(0.0, 0.0) |
|
fm.AddPoint(dia * 1.05 / 2, 0.0) |
|
fm.AddPoint(id / 2, bore_cham_ht) |
|
fm.AddPoint(id / 2, h - bore_cham_ht) |
|
fm.AddPoint(dia * 1.05 / 2, h) |
|
fm.AddPoint(0.0, h) |
|
bore_cutter = self.RevolveZ(fm.GetFace()) |
|
shape = shape.cut(bore_cutter) |
|
# outer chamfer on the hex |
|
fm.Reset() |
|
fm.AddPoint((s / sqrt3 + 1.05 * dia / 2) / 2, h) |
|
fm.AddPoint(s / sqrt3, h) |
|
fm.AddPoint(s / sqrt3, m_w) |
|
top_cham_cutter = self.RevolveZ(fm.GetFace()) |
|
shape = shape.cut(top_cham_cutter) |
|
# add modelled threads if needed |
|
if fa.Thread: |
|
thread_cutter = self.CreateInnerThreadCutter(dia, P, h + P) |
|
shape = shape.cut(thread_cutter) |
|
return shape |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
import sys |
|
sys.path.append(r"C:\Users\yasin\Desktop\Code\text2cad_trial") |
|
import screw_maker as sm |
|
import FreeCAD as App, Part |
|
|
|
s = sm.Screw() |
|
|
|
class DummyFA: |
|
baseType = "ISO7044" |
|
Type = "ISO7044" |
|
calc_diam = "M10" |
|
Diameter = "M10" |
|
Thread = True |
|
LeftHanded = False |
|
dimTable = [1.5, 0.5, 0, 0, 17.5, 0, 0, 8.0, 0, 7.5, 0, 15, 0, 0] |
|
|
|
fa = DummyFA() |
|
shape = makeAllMetalFlangedLockNut(s, fa) |
|
|
|
doc = App.newDocument("AutoNut") |
|
obj = doc.addObject("Part::Feature", "AutoFlangedNut") |
|
obj.Shape = shape |
|
doc.recompute() |
|
|
|
print("Flanged lock nut generated successfully.") |
|
|
|
if GUI_AVAILABLE: |
|
# FreeCADGui.showMainWindow() |
|
FreeCADGui.activeDocument().activeView().viewAxometric() |
|
FreeCADGui.SendMsgToActiveView("ViewFit") |
|
|
|
|
|
- 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: |
|
import FreeCAD as App |
|
import FreeCADGui as Gui |
|
from FreeCAD import Vector |
|
import math |
|
|
|
|
|
def createFlangeAssembly(): |
|
doc = App.newDocument("Flange") |
|
|
|
# === Parameters === |
|
FLANGE_OUTER_DIAMETER = 100.0 |
|
FLANGE_THICKNESS = 7.5 |
|
BORE_INNER_DIAMETER = 50.0 |
|
NECK_HEIGHT = 15.0 |
|
NECK_OUTER_DIAMETER = 60.0 |
|
NUM_BOLT_HOLES = 6 |
|
BOLT_HOLE_DIAMETER = 12.0 |
|
PCD = 75.0 |
|
|
|
total_height = FLANGE_THICKNESS + NECK_HEIGHT |
|
|
|
# === 1. Create flange base === |
|
flange = doc.addObject("Part::Cylinder", "Flange") |
|
flange.Radius = FLANGE_OUTER_DIAMETER / 2 |
|
flange.Height = FLANGE_THICKNESS |
|
|
|
# === 2. Cut central bore from flange === |
|
bore = doc.addObject("Part::Cylinder", "CentralBore") |
|
bore.Radius = BORE_INNER_DIAMETER / 2 |
|
bore.Height = FLANGE_THICKNESS |
|
bore_cut = doc.addObject("Part::Cut", "FlangeWithBore") |
|
bore_cut.Base = flange |
|
bore_cut.Tool = bore |
|
|
|
# === 3. Create neck === |
|
neck_outer = doc.addObject("Part::Cylinder", "NeckOuter") |
|
neck_outer.Radius = NECK_OUTER_DIAMETER / 2 |
|
neck_outer.Height = NECK_HEIGHT |
|
neck_outer.Placement.Base = Vector(0, 0, FLANGE_THICKNESS) |
|
|
|
neck_inner = doc.addObject("Part::Cylinder", "NeckInner") |
|
neck_inner.Radius = BORE_INNER_DIAMETER / 2 |
|
neck_inner.Height = NECK_HEIGHT |
|
neck_inner.Placement.Base = Vector(0, 0, FLANGE_THICKNESS) |
|
|
|
neck_hollow = doc.addObject("Part::Cut", "HollowNeck") |
|
neck_hollow.Base = neck_outer |
|
neck_hollow.Tool = neck_inner |
|
|
|
# === 4. Fuse flange (with central hole) and neck === |
|
fused = doc.addObject("Part::Fuse", "FlangeAndNeck") |
|
fused.Base = bore_cut |
|
fused.Tool = neck_hollow |
|
|
|
# === 5. Cut bolt holes sequentially === |
|
current_shape = fused |
|
bolt_radius = BOLT_HOLE_DIAMETER / 2 |
|
bolt_circle_radius = PCD / 2 |
|
|
|
for i in range(NUM_BOLT_HOLES): |
|
angle_deg = 360 * i / NUM_BOLT_HOLES |
|
angle_rad = math.radians(angle_deg) |
|
x = bolt_circle_radius * math.cos(angle_rad) |
|
y = bolt_circle_radius * math.sin(angle_rad) |
|
|
|
hole = doc.addObject("Part::Cylinder", f"BoltHole_{i+1:02d}") |
|
hole.Radius = bolt_radius |
|
hole.Height = total_height |
|
hole.Placement.Base = Vector(x, y, 0) |
|
|
|
cut = doc.addObject("Part::Cut", f"Cut_Bolt_{i+1:02d}") |
|
cut.Base = current_shape |
|
cut.Tool = hole |
|
current_shape = cut # update for next iteration |
|
|
|
# === 6. Final result === |
|
|
|
|
|
# Recompute and fit view |
|
doc.recompute() |
|
Gui.activeDocument().activeView().viewAxometric() |
|
Gui.SendMsgToActiveView("ViewFit") |
|
|
|
return doc |
|
|
|
if __name__ == "__main__": |
|
createFlangeAssembly() |
|
|
|
use this template whenever asked to make a flange |
|
|
|
- Use material only when specified by user. An example of using material is- |
|
|
|
view_obj = final_obj.ViewObject |
|
view_obj.ShapeColor = (0.8, 0.8, 0.85) # Light grey-blue tone |
|
view_obj.DiffuseColor = [(0.8, 0.8, 0.85)] # Consistent color across faces |
|
view_obj.Transparency = 0 |
|
|
|
material_obj = doc.addObject("App::MaterialObject", "Material") |
|
material_obj.Material = { |
|
'Name': 'Stainless steel', |
|
'Density': '8000 kg/m^3', |
|
'YoungsModulus': '200000 MPa', |
|
'PoissonRatio': '0.3' |
|
} |
|
material_obj.Label = "StainlessSteelMaterial" |
|
|
|
- This is a good example for a teapot. Whenever asked to generate a teapot, make something similar: |
|
import FreeCAD as App |
|
import FreeCADGui as Gui |
|
from FreeCAD import Vector, Placement, Rotation |
|
import Part |
|
|
|
# Teapot dimensions |
|
BODY_BOTTOM_RADIUS = 50.0 |
|
BODY_MAX_RADIUS = 80.0 |
|
BODY_HEIGHT = 100.0 |
|
LID_OPENING_RADIUS = 35.0 |
|
|
|
# Spout parameters |
|
SPOUT_ATTACH_HEIGHT = BODY_HEIGHT * 0.5 # 50.0 |
|
SPOUT_OFFSET_Y = BODY_MAX_RADIUS * 0.7 # 56.0 |
|
SPOUT_LENGTH_HORIZONTAL = 60.0 |
|
SPOUT_LENGTH_VERTICAL = 30.0 |
|
SPOUT_RADIUS = 7.0 |
|
|
|
# Handle parameters |
|
HANDLE_ATTACH_TOP_HEIGHT = BODY_HEIGHT * 0.7 # 70.0 |
|
HANDLE_ATTACH_BOTTOM_HEIGHT = BODY_HEIGHT * 0.3 # 30.0 |
|
HANDLE_OFFSET_Y = -BODY_MAX_RADIUS * 0.7 # -56.0 |
|
HANDLE_RADIUS = 6.0 |
|
|
|
def createTeapot(): |
|
doc = App.newDocument("Teapot") |
|
|
|
# --- 1. Body --- |
|
body_profile_pts = [ |
|
Vector(BODY_BOTTOM_RADIUS, 0, 0), |
|
Vector(BODY_MAX_RADIUS, 0, BODY_HEIGHT * 0.4), |
|
Vector(BODY_MAX_RADIUS * 0.8, 0, BODY_HEIGHT * 0.7), |
|
Vector(LID_OPENING_RADIUS, 0, BODY_HEIGHT) |
|
] |
|
body_spline = Part.BSplineCurve(body_profile_pts) |
|
body_edge = body_spline.toShape() |
|
line1 = Part.LineSegment(Vector(LID_OPENING_RADIUS, 0, BODY_HEIGHT), Vector(0, 0, BODY_HEIGHT)).toShape() |
|
line2 = Part.LineSegment(Vector(0, 0, BODY_HEIGHT), Vector(0, 0, 0)).toShape() |
|
line3 = Part.LineSegment(Vector(0, 0, 0), Vector(BODY_BOTTOM_RADIUS, 0, 0)).toShape() |
|
wire = Part.Wire([body_edge, line1, line2, line3]) |
|
face = Part.Face(wire) |
|
body_solid = face.revolve(Vector(0, 0, 0), Vector(0, 0, 1), 360) |
|
|
|
obj_body = doc.addObject("Part::Feature", "Body") |
|
obj_body.Shape = body_solid |
|
obj_body.ViewObject.ShapeColor = (0.9, 0.7, 0.7) |
|
|
|
# --- 2. Lid --- |
|
lid_profile_pts = [ |
|
Vector(36.0, 0, 0), |
|
Vector(36.0, 0, 3.0), |
|
Vector(35.0, 0, 3.0 + 20.0 * 0.2), |
|
Vector(17.5, 0, 3.0 + 20.0 * 0.7), |
|
Vector(10.0, 0, 3.0 + 20.0), |
|
Vector(5.0, 0, 3.0 + 20.0 + 15.0 * 0.8), |
|
Vector(0, 0, 3.0 + 20.0 + 15.0) |
|
] |
|
lid_spline = Part.BSplineCurve(lid_profile_pts) |
|
lid_edge = lid_spline.toShape() |
|
line1 = Part.LineSegment(Vector(0, 0, 3.0 + 20.0 + 15.0), Vector(0, 0, 0)).toShape() |
|
line2 = Part.LineSegment(Vector(0, 0, 0), Vector(36.0, 0, 0)).toShape() |
|
wire_lid = Part.Wire([lid_edge, line1, line2]) |
|
face_lid = Part.Face(wire_lid) |
|
lid_solid = face_lid.revolve(Vector(0, 0, 0), Vector(0, 0, 1), 360) |
|
|
|
obj_lid = doc.addObject("Part::Feature", "Lid") |
|
obj_lid.Shape = lid_solid |
|
obj_lid.Placement = Placement(Vector(0, 0, BODY_HEIGHT), Rotation()) |
|
obj_lid.ViewObject.ShapeColor = (0.9, 0.7, 0.7) |
|
|
|
# --- 3. Spout (Precomputed final positions) --- |
|
spout_path_pts = [ |
|
Vector(0, -121, 66), # Original: (0, -56, 50) -> transformed |
|
Vector(0, -91, 51), # Original: (0, -26, 65) -> transformed |
|
Vector(0, -61, 36) # Original: (0, 4, 80) -> transformed |
|
] |
|
|
|
spout_curve = Part.BSplineCurve(spout_path_pts) |
|
spout_wire = Part.Wire(spout_curve.toShape()) |
|
|
|
tangent_spout = spout_curve.tangent(spout_curve.FirstParameter)[0] |
|
tangent_spout.normalize() |
|
|
|
spout_circle = Part.Circle() |
|
spout_circle.Center = spout_path_pts[0] |
|
spout_circle.Axis = tangent_spout |
|
spout_circle.Radius = SPOUT_RADIUS |
|
spout_profile = Part.Wire(spout_circle.toShape()) |
|
|
|
spout_solid = spout_wire.makePipe(spout_profile) |
|
obj_spout = doc.addObject("Part::Feature", "Spout") |
|
obj_spout.Shape = spout_solid |
|
obj_spout.ViewObject.ShapeColor = (0.9, 0.7, 0.7) |
|
|
|
# --- 4. Handle (Precomputed final positions) --- |
|
handle_path_pts = [ |
|
Vector(0, 56, 31), # Original: (0, 56, 70) -> transformed |
|
Vector(0, 78, 43), # Original: (0, 78, 58) -> transformed |
|
Vector(0, 78, 79), # Original: (0, 78, 22) -> transformed |
|
Vector(0, 56, 71) # Original: (0, 56, 30) -> transformed |
|
] |
|
|
|
handle_curve = Part.BSplineCurve(handle_path_pts) |
|
handle_wire = Part.Wire(handle_curve.toShape()) |
|
|
|
tangent_handle = handle_curve.tangent(handle_curve.FirstParameter)[0] |
|
tangent_handle.normalize() |
|
|
|
handle_circle = Part.Circle() |
|
handle_circle.Center = handle_path_pts[0] |
|
handle_circle.Axis = tangent_handle |
|
handle_circle.Radius = HANDLE_RADIUS |
|
handle_profile = Part.Wire(handle_circle.toShape()) |
|
|
|
handle_solid = handle_wire.makePipe(handle_profile) |
|
obj_handle = doc.addObject("Part::Feature", "Handle") |
|
obj_handle.Shape = handle_solid |
|
obj_handle.ViewObject.ShapeColor = (0.9, 0.7, 0.7) |
|
|
|
# --- 5. Fuse all parts --- |
|
fused = obj_body.Shape.fuse(obj_lid.Shape) |
|
fused = fused.fuse(obj_spout.Shape) |
|
fused = fused.fuse(obj_handle.Shape) |
|
|
|
obj_final = doc.addObject("Part::Feature", "Teapot_Complete") |
|
obj_final.Shape = fused |
|
obj_final.ViewObject.ShapeColor = (0.9, 0.6, 0.6) |
|
|
|
# Hide individual parts for clarity |
|
obj_body.ViewObject.Visibility = False |
|
obj_lid.ViewObject.Visibility = False |
|
obj_spout.ViewObject.Visibility = False |
|
obj_handle.ViewObject.Visibility = False |
|
|
|
doc.recompute() |
|
|
|
Gui.activeDocument().activeView().viewAxometric() |
|
Gui.SendMsgToActiveView("ViewFit") |
|
|
|
return doc |
|
|
|
if __name__ == "__main__": |
|
createTeapot() |
|
|
|
- This is a good example for a herringbone gear. If asked to make a herringbone gear, generate similar: |
|
#Herringbone gear |
|
|
|
import FreeCAD as App |
|
import FreeCADGui as Gui |
|
import Part |
|
import math |
|
from FreeCAD import Vector, Placement, Rotation |
|
|
|
def createHerringboneGear( |
|
num_teeth=20, |
|
module=5.0, |
|
pressure_angle_deg=20.0, |
|
helix_angle_deg=25.0, |
|
face_width=50.0, |
|
central_bore_diameter=20.0, |
|
num_loft_sections=50, |
|
addendum_factor=1.0, |
|
dedendum_factor=1.25, |
|
tooth_radial_offset=1.5 # Teeth pushed radially outward |
|
): |
|
doc = App.newDocument("HerringboneGear") |
|
|
|
pressure_angle_rad = math.radians(pressure_angle_deg) |
|
helix_angle_rad = math.radians(helix_angle_deg) |
|
|
|
pitch_diameter = module * num_teeth |
|
pitch_radius = pitch_diameter / 2 |
|
addendum = addendum_factor * module |
|
dedendum = dedendum_factor * module |
|
root_radius = pitch_radius - dedendum + tooth_radial_offset |
|
outer_radius = pitch_radius + addendum + tooth_radial_offset |
|
|
|
gear_total_height = face_width |
|
half_gear_height = gear_total_height / 2 |
|
|
|
total_angular_twist_rad = (face_width * math.tan(helix_angle_rad)) / pitch_radius |
|
|
|
gear_hub = doc.addObject("Part::Cylinder", "GearHub") |
|
gear_hub.Radius = outer_radius - tooth_radial_offset |
|
gear_hub.Height = gear_total_height |
|
gear_hub.Placement.Base = Vector(0, 0, 0) |
|
|
|
if central_bore_diameter > 0: |
|
bore_radius = central_bore_diameter / 2 |
|
central_bore = doc.addObject("Part::Cylinder", "CentralBore") |
|
central_bore.Radius = bore_radius |
|
central_bore.Height = gear_total_height |
|
central_bore.Placement.Base = Vector(0, 0, 0) |
|
|
|
hub_base = doc.addObject("Part::Cut", "Hub_With_Bore") |
|
hub_base.Base = gear_hub |
|
hub_base.Tool = central_bore |
|
else: |
|
hub_base = gear_hub |
|
|
|
angle_per_tooth = 360.0 / num_teeth |
|
|
|
effective_half_angle_for_flank_base = (math.pi / num_teeth) / 2 |
|
effective_half_angle_for_flank_tip = effective_half_angle_for_flank_base * 0.7 |
|
|
|
P_A = Vector(root_radius * math.sin(effective_half_angle_for_flank_base), root_radius * math.cos(effective_half_angle_for_flank_base), 0) |
|
P_B = Vector(root_radius * math.sin(-effective_half_angle_for_flank_base), root_radius * math.cos(-effective_half_angle_for_flank_base), 0) |
|
|
|
P_C = Vector(outer_radius * math.sin(effective_half_angle_for_flank_tip), outer_radius * math.cos(effective_half_angle_for_flank_tip), 0) |
|
P_D = Vector(outer_radius * math.sin(-effective_half_angle_for_flank_tip), outer_radius * math.cos(-effective_half_angle_for_flank_tip), 0) |
|
|
|
e_flank1 = Part.LineSegment(P_B, P_D).toShape() |
|
e_flank2 = Part.LineSegment(P_A, P_C).toShape() |
|
|
|
def offset_midpoint(p1, p2, offset=0.1): |
|
mid = (p1 + p2).multiply(0.5) |
|
vec = p2.sub(p1) |
|
perp = Vector(-vec.y, vec.x, 0) |
|
perp.normalize() |
|
return mid.add(perp.multiply(offset)) |
|
|
|
tip_midpoint = offset_midpoint(P_D, P_C, 0.1) |
|
e_tip_arc = Part.ArcOfCircle(P_D, tip_midpoint, P_C).toShape() |
|
|
|
root_midpoint = offset_midpoint(P_A, P_B, 0.1) |
|
e_root_arc = Part.ArcOfCircle(P_A, root_midpoint, P_B).toShape() |
|
|
|
try: |
|
tooth_profile_wire = Part.Wire([e_root_arc, e_flank1, e_tip_arc, e_flank2]) |
|
except Exception as e: |
|
App.Console.PrintError(f"Error creating tooth profile wire: {e}. Using fallback wire.\n") |
|
fallback_edges = [ |
|
Part.LineSegment(P_A, P_B).toShape(), |
|
Part.LineSegment(P_B, P_D).toShape(), |
|
Part.LineSegment(P_D, P_C).toShape(), |
|
Part.LineSegment(P_C, P_A).toShape() |
|
] |
|
tooth_profile_wire = Part.Wire(fallback_edges) |
|
|
|
tooth_profile_face = Part.Face(tooth_profile_wire) |
|
|
|
helical_teeth_LH_fused = None |
|
lh_z_start = 0 |
|
lh_z_end = half_gear_height |
|
lh_twist_start = 0 |
|
lh_twist_end = total_angular_twist_rad / 2 |
|
|
|
for tooth_idx in range(num_teeth): |
|
current_tooth_LH_profiles = [] |
|
initial_tooth_rotation_deg = tooth_idx * angle_per_tooth |
|
|
|
for i in range(num_loft_sections + 1): |
|
z_pos_current = lh_z_start + (lh_z_end - lh_z_start) * (i / num_loft_sections) |
|
current_slice_twist_angle_rad = lh_twist_start + (lh_twist_end - lh_twist_start) * (i / num_loft_sections) |
|
|
|
combined_rotation_deg = initial_tooth_rotation_deg + math.degrees(current_slice_twist_angle_rad) |
|
|
|
profile_copy = tooth_profile_face.copy() |
|
profile_copy.Placement = Placement( |
|
Vector(0, 0, z_pos_current), |
|
Rotation(Vector(0, 0, 1), combined_rotation_deg) |
|
) |
|
current_tooth_LH_profiles.append(profile_copy) |
|
|
|
helical_tooth_LH_solid = Part.makeLoft(current_tooth_LH_profiles, True) |
|
|
|
if helical_teeth_LH_fused is None: |
|
helical_teeth_LH_fused = helical_tooth_LH_solid |
|
else: |
|
helical_teeth_LH_fused = helical_teeth_LH_fused.fuse(helical_tooth_LH_solid) |
|
|
|
obj_helical_LH = doc.addObject("Part::Feature", "HelicalTeeth_Left_Section") |
|
obj_helical_LH.Shape = helical_teeth_LH_fused |
|
obj_helical_LH.ViewObject.ShapeColor = (0.7, 0.7, 0.9) |
|
|
|
helical_teeth_RH_fused = None |
|
rh_z_start = half_gear_height |
|
rh_z_end = gear_total_height |
|
rh_twist_start = total_angular_twist_rad / 2 |
|
rh_twist_end = 0 |
|
|
|
for tooth_idx in range(num_teeth): |
|
current_tooth_RH_profiles = [] |
|
initial_tooth_rotation_deg = tooth_idx * angle_per_tooth |
|
|
|
for i in range(num_loft_sections + 1): |
|
z_pos_current = rh_z_start + (rh_z_end - rh_z_start) * (i / num_loft_sections) |
|
current_slice_twist_angle_rad = rh_twist_start + (rh_twist_end - rh_twist_start) * (i / num_loft_sections) |
|
|
|
combined_rotation_deg = initial_tooth_rotation_deg + math.degrees(current_slice_twist_angle_rad) |
|
|
|
profile_copy = tooth_profile_face.copy() |
|
profile_copy.Placement = Placement( |
|
Vector(0, 0, z_pos_current), |
|
Rotation(Vector(0, 0, 1), combined_rotation_deg) |
|
) |
|
current_tooth_RH_profiles.append(profile_copy) |
|
|
|
helical_tooth_RH_solid = Part.makeLoft(current_tooth_RH_profiles, True) |
|
|
|
if helical_teeth_RH_fused is None: |
|
helical_teeth_RH_fused = helical_tooth_RH_solid |
|
else: |
|
helical_teeth_RH_fused = helical_teeth_RH_fused.fuse(helical_tooth_RH_solid) |
|
|
|
obj_helical_RH = doc.addObject("Part::Feature", "HelicalTeeth_Right_Section") |
|
obj_helical_RH.Shape = helical_teeth_RH_fused |
|
obj_helical_RH.ViewObject.ShapeColor = (0.7, 0.9, 0.7) |
|
|
|
combined_teeth_sections = doc.addObject("Part::Fuse", "Combined_Teeth_Sections") |
|
combined_teeth_sections.Base = obj_helical_LH |
|
combined_teeth_sections.Tool = obj_helical_RH |
|
|
|
final_gear = doc.addObject("Part::Fuse", "HerringboneGear_Complete") |
|
final_gear.Base = hub_base |
|
final_gear.Tool = combined_teeth_sections |
|
|
|
final_gear.ViewObject.ShapeColor = (0.8, 0.6, 0.9) |
|
|
|
if central_bore_diameter > 0: |
|
central_bore.ViewObject.Visibility = False |
|
gear_hub.ViewObject.Visibility = False |
|
hub_base.ViewObject.Visibility = False |
|
obj_helical_LH.ViewObject.Visibility = False |
|
obj_helical_RH.ViewObject.Visibility = False |
|
combined_teeth_sections.ViewObject.Visibility = False |
|
|
|
doc.recompute() |
|
Gui.activeDocument().activeView().viewAxometric() |
|
Gui.SendMsgToActiveView("ViewFit") |
|
|
|
return doc |
|
|
|
if __name__ == "__main__": |
|
createHerringboneGear() |
|
|