SORRY, it worked fine once I converted my geomtries to lines!
As I said I´m just a newb.
SORRY, it worked fine once I converted my geomtries to lines!
As I said I´m just a newb.
I had ChatGPT write a better script to do dogbones on solids.. Here it is if anyone wants to use it
(as is, you click the edge (or edges) where the dogbones should be and it places a cylinder for manual boolean difference.. you could probably get ChatGPT to automatically boolean but for me, I want it this way and prefer to do the boolean manually)
# -*- coding: utf-8 -*-
import scriptcontext as sc
import Rhino
import Rhino.Geometry as rg
# --- Persistent remembered bit diameter ---
# Load previous value if available
if "dogbone_bit_diameter" in sc.sticky:
cyl_diameter = sc.sticky["dogbone_bit_diameter"]
else:
cyl_diameter = 0.13 # default first-run value
# --- Ask user for bit diameter ---
get_diam = Rhino.Input.Custom.GetNumber()
get_diam.SetCommandPrompt("Bit diameter")
get_diam.SetDefaultNumber(cyl_diameter)
get_diam.SetLowerLimit(0.001, False)
if get_diam.Get() == Rhino.Input.GetResult.Number:
cyl_diameter = get_diam.Number()
sc.sticky["dogbone_bit_diameter"] = cyl_diameter # remember for this session
else:
print("Using previous bit diameter:", cyl_diameter)
inset = cyl_diameter / 2.0
radius = cyl_diameter / 2.0
# --- Select edges ---
go = Rhino.Input.Custom.GetObject()
go.SetCommandPrompt("Select edges for dogbones")
go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve
go.SubObjectSelect = True
go.GetMultiple(1, 0)
if go.CommandResult() != Rhino.Commands.Result.Success:
print("No edges selected")
exit()
# --- Process edges ---
for i in range(go.ObjectCount):
objref = go.Object(i)
sel_curve = objref.Curve()
if sel_curve is None:
print("Skipping selection {}".format(i))
continue
# Find Brep object
obj = sc.doc.Objects.Find(objref.ObjectId)
if obj is None or not isinstance(obj.Geometry, rg.Brep):
print("Skipping selection {}: not a Brep".format(i))
continue
brep = obj.Geometry
# Match selected curve to BrepEdge
matched_edge = None
sel_start = sel_curve.PointAtStart
sel_end = sel_curve.PointAtEnd
for e in brep.Edges:
e_start = e.PointAt(e.Domain.T0)
e_end = e.PointAt(e.Domain.T1)
if (sel_start.DistanceTo(e_start) < 1e-8 and sel_end.DistanceTo(e_end) < 1e-8) or \
(sel_start.DistanceTo(e_end) < 1e-8 and sel_end.DistanceTo(e_start) < 1e-8):
matched_edge = e
break
if matched_edge is None:
print("Skipping selection {}: no matching edge found".format(i))
continue
start_pt = matched_edge.PointAt(matched_edge.Domain.T0)
end_pt = matched_edge.PointAt(matched_edge.Domain.T1)
# Determine bisector direction
vectors = []
for e in brep.Edges:
if e == matched_edge:
continue
e_start = e.PointAt(e.Domain.T0)
e_end = e.PointAt(e.Domain.T1)
if e_start.DistanceTo(start_pt) < 1e-8:
v = e_end - start_pt
elif e_end.DistanceTo(start_pt) < 1e-8:
v = e_start - start_pt
else:
continue
v.Unitize()
vectors.append(v)
if vectors:
bisector = vectors[0]
for v in vectors[1:]:
bisector += v
bisector.Unitize()
else:
bisector = end_pt - start_pt
bisector.Unitize()
# Cylinder center
cyl_center = start_pt + bisector * inset
# Cylinder axis (along the edge)
edge_vec = end_pt - start_pt
edge_vec.Unitize()
edge_len = start_pt.DistanceTo(end_pt)
# Local plane for circle
x_axis = rg.Vector3d(bisector)
x_axis.Unitize()
z_axis = rg.Vector3d(edge_vec)
y_axis = rg.Vector3d.CrossProduct(z_axis, x_axis)
y_axis.Unitize()
x_axis = rg.Vector3d.CrossProduct(y_axis, z_axis)
x_axis.Unitize()
plane = rg.Plane(cyl_center, x_axis, y_axis)
if not plane.IsValid:
print("Skipping edge {}: invalid plane".format(i))
continue
# Build cylinder
circle = rg.Circle(plane, radius)
cylinder = rg.Cylinder(circle, edge_len)
cyl_brep = cylinder.ToBrep(True, True)
sc.doc.Objects.AddBrep(cyl_brep)
sc.doc.Views.Redraw()
print("Dogbone cylinders created for {} edges using bit diameter {}.".format(go.ObjectCount, cyl_diameter))
oh, it took about an hour of back&forth with that thing to get it to just select an edge with a single click (when there is a shared edge between two surfaces of a polysurface)
I don’t know if the solution it finally came up with is valid but it seems to work.. someone needs to teach that thing how to select a shared edge better
Hi @jeff_hammond, thanks for sharing the solution in the end.
Just out of curiosity, why do you model them in 3D? I usually add them in 2D afterwards because I feel it keeps the 3D model easier for adjustments until I’m ready to put things to cnc, so am just interested in why this is beneficial for your workflow. Or do you perhaps use 3d models instead of 2d vectors for CAM?
I do all my toolpaths in Fusion via step exports (I’m on Mac and that’s pretty much my only viable option for CAM)
All of that is done on 3d models so I just model the dog bones as well.
Also, a lot of times, a single object can need dog bones on the outer contours (full depth) then it has dogbones in the corners of pockets/stopped dados/etc and these are of varying depths (don’t go all the way to the bottom)
To me at least, it’s better to have the object fully modeled exactly as I want it to be after milling and it’s way less confusing than having different 2d curves with dogbones at various heights and somehow keep track of it all
ymmv of course but I’ve just always done everything in 3d since I first started using SketchUp in 2004 or so. (Then Rhino for Mac since I think 2007?).. I almost never use any viewport other than perspective. Always in 3d
I’ll often have pieces looking like this… that one has 6 different dogbones; each with a different condition.
I don’t even know how people would go about programming this part if not in 3D
I do have the opposite issue. I do need to remove dogbones (circular cutouts on corners) from solids.
I see, that makes sense, thanks for explaining.