Filleting edges of polysurfaces is quite fast but there’s no proper solution for un filleting them back into straight corners.
I managed to work something out with ChatGPT. The code is far from perfect but it solves an issue that takes otherwise a lot of manual work. I would love to see something similar implemented in Rhino 9…
(Also, I am aware that FilletEdge has an Edit option - but I cannot see how i can change the corner i want to unfillet, the UI is very strange.)
Here’s my test and the script.
# UnFillet edges
import Rhino
import scriptcontext as sc
from Rhino.Geometry import Brep, Curve, AreaMassProperties
from Rhino.Input.Custom import GetObject
from Rhino.DocObjects import ObjectType
from System.Collections.Generic import List
DEBUG = True
MIN_AREA_RATIO = 0.15
def log(x):
if DEBUG:
print(x)
def get_face(prompt):
go = GetObject()
go.SetCommandPrompt(prompt)
go.GeometryFilter = ObjectType.Surface
go.SubObjectSelect = True
go.EnablePreSelect(False, True)
go.Get()
if go.CommandResult() != Rhino.Commands.Result.Success:
return None, None, None
objref = go.Object(0)
rh_obj = objref.Object()
face = objref.Face()
if rh_obj is None or face is None:
return None, None, None
return rh_obj, rh_obj.Geometry, face.FaceIndex
def shared_edges_between_faces(brep, face_a, face_b):
edges = []
for ei in brep.Faces[face_a].AdjacentEdges():
edge = brep.Edges[ei]
for ti in edge.TrimIndices():
tr = brep.Trims[ti]
if tr.Face and tr.Face.FaceIndex == face_b:
edges.append(ei)
break
return edges
def duplicate_face_brep(brep, face_index):
return brep.Faces[face_index].DuplicateFace(False)
def extract_breps(result):
breps = []
if isinstance(result, Brep):
breps.append(result)
elif isinstance(result, tuple) or isinstance(result, list):
for item in result:
if isinstance(item, Brep):
breps.append(item)
return breps
def brep_area(b):
amp = AreaMassProperties.Compute(b)
return amp.Area if amp else 0.0
def face_center(brep, face_index):
return brep.Faces[face_index].GetBoundingBox(True).Center
def brep_center(brep):
return brep.GetBoundingBox(True).Center
def preserved_boundary_curves(original_brep, side_face_i, fillet_face_i):
fillet_edges = set(shared_edges_between_faces(original_brep, side_face_i, fillet_face_i))
cutters = List[Curve]()
for ei in original_brep.Faces[side_face_i].AdjacentEdges():
if ei in fillet_edges:
continue
crv = original_brep.Edges[ei].DuplicateCurve()
if crv:
cutters.Add(crv)
return cutters
def choose_best_piece(pieces, original_brep, side_face_i):
target = face_center(original_brep, side_face_i)
data = []
max_area = 0.0
for p in pieces:
a = brep_area(p)
max_area = max(max_area, a)
data.append((p, a))
if max_area <= 0:
return pieces[0]
candidates = []
for p, a in data:
if a < max_area * MIN_AREA_RATIO:
continue
d = brep_center(p).DistanceTo(target)
candidates.append((d, -a, p))
if candidates:
candidates.sort()
return candidates[0][2]
data.sort(key=lambda x: x[1], reverse=True)
return data[0][0]
def trim_rebuilt_face(rebuilt_brep, original_brep, side_face_i, fillet_face_i, tol):
cutters = preserved_boundary_curves(original_brep, side_face_i, fillet_face_i)
if cutters.Count == 0:
print("No cutters found for side face", side_face_i)
return None
try:
pieces = rebuilt_brep.Split(cutters, tol)
except Exception as e:
print("Curve split failed on side face", side_face_i)
print(e)
return None
if not pieces or len(pieces) == 0:
print("Split made no pieces on side face", side_face_i)
return None
best = choose_best_piece(pieces, original_brep, side_face_i)
if best:
try:
best.Compact()
best.Faces.ShrinkFaces()
except:
pass
return best
return None
def match_rebuilt_to_original_faces(rebuilt, original_brep, main_a, main_b):
ca = face_center(original_brep, main_a)
cb = face_center(original_brep, main_b)
r0 = rebuilt[0]
r1 = rebuilt[1]
score_normal = brep_center(r0).DistanceTo(ca) + brep_center(r1).DistanceTo(cb)
score_swapped = brep_center(r0).DistanceTo(cb) + brep_center(r1).DistanceTo(ca)
if score_normal <= score_swapped:
return r0, r1
else:
return r1, r0
def unfillet_manual_faces():
tol = sc.doc.ModelAbsoluteTolerance
angle_tol = sc.doc.ModelAngleToleranceRadians
rh_obj, brep, fillet_i = get_face("Select fillet face to remove")
if brep is None:
print("No fillet face selected.")
return
rh_obj_a, brep_a, main_a = get_face("Select first side face")
if brep_a is None or rh_obj_a.Id != rh_obj.Id:
print("First side face must be on same Brep.")
return
rh_obj_b, brep_b, main_b = get_face("Select second side face")
if brep_b is None or rh_obj_b.Id != rh_obj.Id:
print("Second side face must be on same Brep.")
return
edges_a = shared_edges_between_faces(brep, fillet_i, main_a)
edges_b = shared_edges_between_faces(brep, fillet_i, main_b)
if not edges_a or not edges_b:
print("Selected side faces must touch the fillet face.")
return
edge_a = edges_a[0]
edge_b = edges_b[0]
print("Fillet face:", fillet_i)
print("Side faces:", main_a, main_b)
print("Fillet-side edges:", edge_a, edge_b)
try:
result = Brep.ExtendBrepFacesToConnect(
brep.Faces[main_a],
edge_a,
brep.Faces[main_b],
edge_b,
tol,
angle_tol
)
except Exception as e:
print("ExtendBrepFacesToConnect failed:")
print(e)
return
rebuilt = extract_breps(result)
if len(rebuilt) < 2:
print("Could not create replacement sharp faces.")
return
rebuilt_a, rebuilt_b = match_rebuilt_to_original_faces(
rebuilt,
brep,
main_a,
main_b
)
trimmed_a = trim_rebuilt_face(rebuilt_a, brep, main_a, fillet_i, tol)
trimmed_b = trim_rebuilt_face(rebuilt_b, brep, main_b, fillet_i, tol)
if trimmed_a is None or trimmed_b is None:
print("Trimming failed. Original object was NOT hidden.")
print("Adding rebuilt faces only for debugging.")
sc.doc.Objects.AddBrep(rebuilt_a)
sc.doc.Objects.AddBrep(rebuilt_b)
sc.doc.Views.Redraw()
return
parts = [trimmed_a, trimmed_b]
skip = set([fillet_i, main_a, main_b])
for i in range(brep.Faces.Count):
if i in skip:
continue
fb = duplicate_face_brep(brep, i)
if fb:
parts.append(fb)
joined = Brep.JoinBreps(parts, tol)
if joined and len(joined) > 0:
final_brep = joined[0]
try:
final_brep.Faces.ShrinkFaces()
except:
pass
capped = final_brep.CapPlanarHoles(tol)
if capped and capped.IsValid:
final_brep = capped
sc.doc.Objects.AddBrep(final_brep)
sc.doc.Objects.Hide(rh_obj.Id, True)
print("Unfillet complete. Original hidden.")
else:
print("Join failed. Original object was NOT hidden.")
for p in parts:
sc.doc.Objects.AddBrep(p)
sc.doc.Views.Redraw()
unfillet_manual_faces()
This topic is related to:
