Skip to content

Instantly share code, notes, and snippets.

@AndreVallestero
Last active November 9, 2025 21:34
Show Gist options
  • Select an option

  • Save AndreVallestero/72afe2674742fe337eca567997a31373 to your computer and use it in GitHub Desktop.

Select an option

Save AndreVallestero/72afe2674742fe337eca567997a31373 to your computer and use it in GitHub Desktop.
import adsk.core, adsk.fusion, traceback
handlers = []
def computeConvexHull(points_input):
try:
points = [(p.x, p.y, p.z) if hasattr(p,'x') else tuple(p) for p in points_input]
n = len(points)
if n < 4:
return []
def sub(a,b): return (a[0]-b[0],a[1]-b[1],a[2]-b[2])
def cross(u,v): return (u[1]*v[2]-u[2]*v[1], u[2]*v[0]-u[0]*v[2], u[0]*v[1]-u[1]*v[0])
def dot(a,b): return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]
EPS = 1e-9
centroid = (sum(p[0] for p in points)/n,
sum(p[1] for p in points)/n,
sum(p[2] for p in points)/n)
faces = set()
import itertools
for tri in itertools.combinations(range(n),3):
a_idx, b_idx, c_idx = tri
a,b,c = points[a_idx], points[b_idx], points[c_idx]
nrm = cross(sub(b,a), sub(c,a))
if (nrm[0]**2 + nrm[1]**2 + nrm[2]**2)**0.5 < EPS:
continue
pos_side = neg_side = False
for i in range(n):
if i in tri: continue
d = dot(sub(points[i], a), nrm)
if d > EPS: pos_side = True
if d < -EPS: neg_side = True
if pos_side and neg_side:
continue
to_center = sub(centroid, a)
if dot(nrm, to_center) > 0:
faces.add((a_idx, c_idx, b_idx))
else:
faces.add((a_idx, b_idx, c_idx))
except:
app = adsk.core.Application.get()
ui = app.userInterface
ui.messageBox(traceback.format_exc())
return [(f[0], f[1], f[2]) for f in faces]
class CreatedHandler(adsk.core.CommandCreatedEventHandler):
def notify(self, args):
c = args.command
sel = c.commandInputs.addSelectionInput('pointsSel','Select Points','Pick points for convex hull')
for f in ['SketchPoints','Vertices','ConstructionPoints']: sel.addSelectionFilter(f)
sel.setSelectionLimits(1)
class ExecHandler(adsk.core.CommandEventHandler):
def notify(self, args):
s = c.commandInputs.itemById('pointsSel')
if s.selectionCount<4:
adsk.core.Application.get().userInterface.messageBox("Select at least 4 points."); return
pts=[]
for i in range(s.selectionCount):
ent=s.selection(i).entity
p=None
if isinstance(ent,adsk.fusion.SketchPoint):
p=ent.geometry.copy(); p.transformBy(ent.parentSketch.transform)
elif isinstance(ent,adsk.fusion.BRepVertex): p=ent.geometry
else: adsk.core.Application.get().userInterface.messageBox("Unsupported entity"); return
pts.append((p.x,p.y,p.z))
fcs=computeConvexHull(pts)
if not fcs: adsk.core.Application.get().userInterface.messageBox("Convex hull failed."); return
coords=[c for p in pts for c in p]; idx=[i for tri in fcs for i in tri]
root = adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct).rootComponent
m=root.meshBodies.addByTriangleMeshData(coords,idx,[],[]); m.name='ConvexHullMesh'
s=root.features.meshConvertFeatures.createInput([m])
conv=root.features.meshConvertFeatures.add(s)
conv.refinement=2; conv.bodies.item(0).name='ConvexHullSolid'
h = ExecHandler(); c.execute.add(h); handlers.append(h)
def run(context):
try:
app = adsk.core.Application.get()
ui = app.userInterface
design = adsk.fusion.Design.cast(app.activeProduct)
if not design: ui.messageBox('No active design'); return
cmdId='convexHullMeshCmd'
if ui.commandDefinitions.itemById(cmdId): ui.commandDefinitions.itemById(cmdId).deleteMe()
cmd = ui.commandDefinitions.addButtonDefinition(cmdId,'Convex Hull Mesh','Compute hull, create mesh & solid')
h = CreatedHandler(); cmd.commandCreated.add(h); handlers.append(h)
cmd.execute()
adsk.autoTerminate(False)
except: ui.messageBox(traceback.format_exc())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment