Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions commands/commandCreateBin/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
BIN_SCREW_HOLES_INPUT_ID = 'bin_screw_holes'
BIN_MAGNET_CUTOUTS_INPUT_ID = 'bin_magnet_cutouts'
BIN_MAGNET_CUTOUTS_TABS_INPUT_ID = 'bin_magnet_cutouts_tabs'
BIN_MAGNET_REFINED_HOLE_INPUT_ID = 'bin_magnet_refined_hole'
BIN_SCREW_DIAMETER_INPUT = 'screw_diameter'
BIN_MAGNET_DIAMETER_INPUT = 'magnet_diameter'
BIN_MAGNET_HEIGHT_INPUT = 'magnet_height'
Expand Down Expand Up @@ -188,6 +189,7 @@ def initDefaultUiState():
commandUIState.initValue(BIN_SCREW_DIAMETER_INPUT, const.DIMENSION_SCREW_HOLE_DIAMETER, adsk.core.ValueCommandInput.classType())
commandUIState.initValue(BIN_MAGNET_CUTOUTS_INPUT_ID, False, adsk.core.BoolValueCommandInput.classType())
commandUIState.initValue(BIN_MAGNET_CUTOUTS_TABS_INPUT_ID, False, adsk.core.BoolValueCommandInput.classType())
commandUIState.initValue(BIN_MAGNET_REFINED_HOLE_INPUT_ID, False, adsk.core.BoolValueCommandInput.classType())
commandUIState.initValue(BIN_MAGNET_DIAMETER_INPUT, const.DIMENSION_MAGNET_CUTOUT_DIAMETER, adsk.core.ValueCommandInput.classType())
commandUIState.initValue(BIN_MAGNET_HEIGHT_INPUT, const.DIMENSION_MAGNET_CUTOUT_DEPTH, adsk.core.ValueCommandInput.classType())

Expand Down Expand Up @@ -643,6 +645,8 @@ def command_created(args: adsk.core.CommandCreatedEventArgs):
commandUIState.registerCommandInput(generateMagnetSocketCheckboxInput)
generateMagnetsTabCheckboxInput = baseFeaturesGroup.children.addBoolValueInput(BIN_MAGNET_CUTOUTS_TABS_INPUT_ID, 'Add tabs to magnet sockets', True, '', commandUIState.getState(BIN_MAGNET_CUTOUTS_TABS_INPUT_ID))
commandUIState.registerCommandInput(generateMagnetsTabCheckboxInput)
refinedMagnetHoleCheckboxInput = baseFeaturesGroup.children.addBoolValueInput(BIN_MAGNET_REFINED_HOLE_INPUT_ID, 'Side-insertion magnet slots (no glue)', True, '', commandUIState.getState(BIN_MAGNET_REFINED_HOLE_INPUT_ID))
commandUIState.registerCommandInput(refinedMagnetHoleCheckboxInput)
magnetSizeInput = baseFeaturesGroup.children.addValueInput(BIN_MAGNET_DIAMETER_INPUT, 'Magnet cutout diameter', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_MAGNET_DIAMETER_INPUT)))
magnetSizeInput.minimumValue = 0.1
magnetSizeInput.isMinimumInclusive = True
Expand Down Expand Up @@ -806,6 +810,7 @@ def onChangeValidate():
commandUIState.getInput(BIN_SCREW_HOLES_INPUT_ID).isEnabled = generateBase
commandUIState.getInput(BIN_MAGNET_CUTOUTS_INPUT_ID).isEnabled = generateBase
commandUIState.getInput(BIN_MAGNET_CUTOUTS_TABS_INPUT_ID).isEnabled = generateBase
commandUIState.getInput(BIN_MAGNET_REFINED_HOLE_INPUT_ID).isEnabled = generateBase
commandUIState.getInput(BIN_MAGNET_DIAMETER_INPUT).isEnabled = generateBase
commandUIState.getInput(BIN_MAGNET_HEIGHT_INPUT).isEnabled = generateBase
commandUIState.getInput(BIN_SCREW_DIAMETER_INPUT).isEnabled = generateBase
Expand Down Expand Up @@ -866,6 +871,7 @@ def generateBin(args: adsk.core.CommandEventArgs):
bin_magnet_cutouts: adsk.core.BoolValueCommandInput = inputs.itemById(BIN_MAGNET_CUTOUTS_INPUT_ID)
bin_screw_hole_diameter: adsk.core.ValueCommandInput = inputs.itemById(BIN_SCREW_DIAMETER_INPUT)
bin_magnet_cutouts_tabs: adsk.core.BoolValueCommandInput = inputs.itemById(BIN_MAGNET_CUTOUTS_TABS_INPUT_ID)
bin_magnet_refined_hole: adsk.core.BoolValueCommandInput = inputs.itemById(BIN_MAGNET_REFINED_HOLE_INPUT_ID)
bin_magnet_cutout_diameter: adsk.core.ValueCommandInput = inputs.itemById(BIN_MAGNET_DIAMETER_INPUT)
bin_magnet_cutout_depth: adsk.core.ValueCommandInput = inputs.itemById(BIN_MAGNET_HEIGHT_INPUT)
with_lip: adsk.core.BoolValueCommandInput = inputs.itemById(BIN_WITH_LIP_INPUT_ID)
Expand Down Expand Up @@ -920,6 +926,7 @@ def generateBin(args: adsk.core.CommandEventArgs):
baseGeneratorInput.hasScrewHoles = bin_screw_holes.value and not isShelled
baseGeneratorInput.hasMagnetCutouts = bin_magnet_cutouts.value and not isShelled
baseGeneratorInput.hasMagnetCutoutsTabs = bin_magnet_cutouts_tabs.value and not isShelled
baseGeneratorInput.hasRefinedMagnetHole = bin_magnet_refined_hole.value and not isShelled
baseGeneratorInput.screwHolesDiameter = bin_screw_hole_diameter.value
baseGeneratorInput.magnetCutoutsDiameter = bin_magnet_cutout_diameter.value
baseGeneratorInput.magnetCutoutsDepth = bin_magnet_cutout_depth.value
Expand Down
70 changes: 57 additions & 13 deletions lib/gridfinityUtils/baseGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,18 +203,62 @@ def createSingleGridfinityBaseBody(

# magnet cutouts
if input.hasMagnetCutouts:
magnetSocketBody = shapeUtils.simpleCylinder(
baseBottomPlane,
0,
-input.magnetCutoutsDepth,
input.magnetCutoutsDiameter / 2,
cutoutCenterPoint,
targetComponent,
)
cutoutBodies.add(magnetSocketBody)
if input.hasRefinedMagnetHole:
# Refined magnet hole (grizzie17's design):
# - Press-fit pocket slightly smaller than magnet, with side insertion slot
# - Thin floor below pocket prevents magnet falling out
# - Poke-through hole below for magnet removal
pocketRadius = input.magnetCutoutsDiameter / 2 - const.DIMENSION_REFINED_HOLE_SQUEEZE
pocketDepth = const.DIMENSION_REFINED_HOLE_DEPTH
floorThickness = const.DIMENSION_REFINED_HOLE_FLOOR

# Slot exit point at the base edge, centered on the hole Y position
slotEdgePoint = adsk.core.Point3D.create(
input.originPoint.x,
cutoutCenterPoint.y,
0,
)
# Pocket is offset up from the bottom face by the floor thickness
magnetSocketBody = shapeUtils.refinedMagnetHoleBody(
baseBottomPlane,
-floorThickness,
pocketRadius,
pocketDepth,
slotEdgePoint,
cutoutCenterPoint,
targetComponent,
)
cutoutBodies.add(magnetSocketBody)

# Poke-through slot for magnet removal with a toothpick.
# Goes from the base bottom face all the way through to the top
# of the pocket, on the opposite side from the insertion slot.
pokeRadius = const.DIMENSION_REFINED_POKE_THROUGH_DIAMETER / 2
pokeDepth = floorThickness + pocketDepth
pokeBody = shapeUtils.refinedPokeThrough(
baseBottomPlane,
0,
pokeDepth,
pokeRadius,
slotEdgePoint,
cutoutCenterPoint,
targetComponent,
)
pokeBody.name = "Refined poke-through slot"
cutoutBodies.add(pokeBody)
else:
magnetSocketBody = shapeUtils.simpleCylinder(
baseBottomPlane,
0,
-input.magnetCutoutsDepth,
input.magnetCutoutsDiameter / 2,
cutoutCenterPoint,
targetComponent,
)
cutoutBodies.add(magnetSocketBody)

# magnet tab cutouts
if input.hasMagnetCutoutsTabs:
# magnet tab cutouts (only for standard holes, not refined)
if input.hasMagnetCutoutsTabs and not input.hasRefinedMagnetHole:
magnetTabCutoutSketch = createTabAtCircleEdgeSketch(
baseBottomPlane,
input.magnetCutoutsDiameter / 2,
Expand All @@ -232,8 +276,8 @@ def createSingleGridfinityBaseBody(
targetComponent,
)
cutoutBodies.add(magnetTabCutoutExtrude.bodies.item(0))
if input.hasScrewHoles and (const.BIN_BASE_HEIGHT - input.magnetCutoutsDepth) > const.BIN_MAGNET_HOLE_GROOVE_DEPTH:

if not input.hasRefinedMagnetHole and input.hasScrewHoles and (const.BIN_BASE_HEIGHT - input.magnetCutoutsDepth) > const.BIN_MAGNET_HOLE_GROOVE_DEPTH:
grooveBody = shapeUtils.simpleCylinder(
baseBottomPlane,
-input.magnetCutoutsDepth,
Expand Down
1 change: 1 addition & 0 deletions lib/gridfinityUtils/baseGeneratorInput.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def __init__(self):
self.hasMagnetCutouts = False
self.hasScrewHoles = False
self.hasBottomChamfer = True
self.hasRefinedMagnetHole = False
self.screwHolesDiameter = DIMENSION_SCREW_HOLE_DIAMETER
self.magnetCutoutsDiameter = DIMENSION_MAGNET_CUTOUT_DIAMETER
self.magnetCutoutsDepth = DIMENSION_MAGNET_CUTOUT_DEPTH
Expand Down
10 changes: 10 additions & 0 deletions lib/gridfinityUtils/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,15 @@
DIMENSION_MAGNET_CUTOUT_DEPTH = 0.24
DIMENSION_PRINT_HELPER_GROOVE_DEPTH = 0.03

# Refined magnet hole constants (based on grizzie17's Gridfinity Refined design)
# Pocket is slightly smaller than magnet for press-fit (6.5mm - 2*0.32mm = 5.86mm)
DIMENSION_REFINED_HOLE_SQUEEZE = 0.032
# Magnet height minus tolerance (2mm - 0.1mm = 1.9mm)
DIMENSION_REFINED_HOLE_DEPTH = 0.19
# Thin floor below the pocket to prevent magnet from falling out (h_slit * 2 = 0.4mm)
DIMENSION_REFINED_HOLE_FLOOR = 0.04
# Poke-through hole diameter for magnet removal with a toothpick
DIMENSION_REFINED_POKE_THROUGH_DIAMETER = 0.25


DEFAULT_FILTER_TOLERANCE = 0.00001
164 changes: 163 additions & 1 deletion lib/gridfinityUtils/shapeUtils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import adsk.core, adsk.fusion, traceback
import os
import os, math

from . import extrudeUtils, sketchUtils

Expand Down Expand Up @@ -62,6 +62,168 @@ def simpleCylinder(
cylinderExtrude.name = "Simple cylinder extrude"
return cylinderExtrude.bodies.item(0)

def refinedMagnetHoleBody(
plane: adsk.core.Base,
planeOffset: float,
pocketRadius: float,
pocketDepth: float,
slotEdgePoint: adsk.core.Point3D,
centerPoint: adsk.core.Point3D,
targetComponent: adsk.fusion.Component,
):
"""Create a refined magnet hole cutout body (grizzie17's Gridfinity Refined design).

The pocket is a keyhole shape drawn as a single closed profile: a semicircular
pocket connected to a rectangular slot extending to the base edge.

Arguments:
plane: the base bottom face
planeOffset: offset from the bottom face to the TOP of the pocket
(negative = into the base, typically -(floor thickness))
pocketRadius: radius of the circular magnet pocket
pocketDepth: depth of the pocket (magnet height minus tolerance)
slotEdgePoint: the point on the base edge where the slot exits,
in model space (center of the slot width at the edge)
centerPoint: center of the magnet pocket in model space
"""
constructionPlaneInput = targetComponent.constructionPlanes.createInput()
constructionPlaneInput.setByOffset(plane, adsk.core.ValueInput.createByReal(planeOffset))
constructionPlane = targetComponent.constructionPlanes.add(constructionPlaneInput)

sketch: adsk.fusion.Sketch = targetComponent.sketches.add(constructionPlane)
sketch.name = "Refined magnet hole sketch"

ctr = sketch.modelToSketchSpace(centerPoint)
ctr.z = 0
edge = sketch.modelToSketchSpace(slotEdgePoint)
edge.z = 0

# Compute slot direction (center → edge) and perpendicular in sketch space
dx = edge.x - ctr.x
dy = edge.y - ctr.y
length = math.sqrt(dx * dx + dy * dy)
dirX = dx / length
dirY = dy / length
perpX = -dirY
perpY = dirX
r = pocketRadius

# Keyhole as a single closed profile:
# - Semicircular arc on the far side (away from slot)
# - Two parallel lines forming the slot sides
# - A closing line at the base edge
arcStart = adsk.core.Point3D.create(ctr.x + perpX * r, ctr.y + perpY * r, 0)
arcMid = adsk.core.Point3D.create(ctr.x - dirX * r, ctr.y - dirY * r, 0)
arcEnd = adsk.core.Point3D.create(ctr.x - perpX * r, ctr.y - perpY * r, 0)

edgeTop = adsk.core.Point3D.create(edge.x + perpX * r, edge.y + perpY * r, 0)
edgeBottom = adsk.core.Point3D.create(edge.x - perpX * r, edge.y - perpY * r, 0)

arcs = sketch.sketchCurves.sketchArcs
lines = sketch.sketchCurves.sketchLines

arcs.addByThreePoints(arcStart, arcMid, arcEnd)
lines.addByTwoPoints(arcEnd, edgeBottom)
lines.addByTwoPoints(edgeBottom, edgeTop)
lines.addByTwoPoints(edgeTop, arcStart)

pocketExtrude = extrudeUtils.simpleDistanceExtrude(
sketch.profiles.item(0),
adsk.fusion.FeatureOperations.NewBodyFeatureOperation,
-pocketDepth,
adsk.fusion.ExtentDirections.PositiveExtentDirection,
[],
targetComponent,
)
pocketExtrude.name = "Refined magnet pocket extrude"
return pocketExtrude.bodies.item(0)


def refinedPokeThrough(
plane: adsk.core.Base,
planeOffset: float,
depth: float,
pokeRadius: float,
slotEdgePoint: adsk.core.Point3D,
centerPoint: adsk.core.Point3D,
targetComponent: adsk.fusion.Component,
):
"""Create a poke-through slot for magnet removal with a toothpick.

Draws a stadium/oblong shape (two semicircles connected by lines) positioned
on the OPPOSITE side of the pocket from the insertion slot (toward the base
interior). This lets you push the magnet out from behind with a toothpick.

Positions match the OpenSCAD reference: near end at ~3.4mm from center,
far end at ~6.93mm from center, in the direction away from the base edge.
"""
constructionPlaneInput = targetComponent.constructionPlanes.createInput()
constructionPlaneInput.setByOffset(plane, adsk.core.ValueInput.createByReal(planeOffset))
constructionPlane = targetComponent.constructionPlanes.add(constructionPlaneInput)

sketch: adsk.fusion.Sketch = targetComponent.sketches.add(constructionPlane)
sketch.name = "Refined poke-through sketch"

ctr = sketch.modelToSketchSpace(centerPoint)
ctr.z = 0
edge = sketch.modelToSketchSpace(slotEdgePoint)
edge.z = 0

# Direction from center AWAY from the edge (toward base interior)
# This is the opposite of the slot direction
dx = ctr.x - edge.x
dy = ctr.y - edge.y
length = math.sqrt(dx * dx + dy * dy)
dirX = dx / length
dirY = dy / length
perpX = -dirY
perpY = dirX

# Position the poke-through slot along the interior direction,
# matching OpenSCAD: near end at 3.4mm, far end at 6.93mm from center
# As fractions of the center-to-edge distance (8mm):
nearFrac = 0.425
farFrac = 0.866
nearCenter = adsk.core.Point3D.create(
ctr.x + dirX * length * nearFrac, ctr.y + dirY * length * nearFrac, 0,
)
farCenter = adsk.core.Point3D.create(
ctr.x + dirX * length * farFrac, ctr.y + dirY * length * farFrac, 0,
)

pr = pokeRadius

# Stadium shape: two semicircles connected by two lines
# Near semicircle (closer to pocket center)
nearArcStart = adsk.core.Point3D.create(nearCenter.x + perpX * pr, nearCenter.y + perpY * pr, 0)
nearArcMid = adsk.core.Point3D.create(nearCenter.x - dirX * pr, nearCenter.y - dirY * pr, 0)
nearArcEnd = adsk.core.Point3D.create(nearCenter.x - perpX * pr, nearCenter.y - perpY * pr, 0)

# Far semicircle (toward base interior)
farArcStart = adsk.core.Point3D.create(farCenter.x - perpX * pr, farCenter.y - perpY * pr, 0)
farArcMid = adsk.core.Point3D.create(farCenter.x + dirX * pr, farCenter.y + dirY * pr, 0)
farArcEnd = adsk.core.Point3D.create(farCenter.x + perpX * pr, farCenter.y + perpY * pr, 0)

arcs = sketch.sketchCurves.sketchArcs
lines = sketch.sketchCurves.sketchLines

arcs.addByThreePoints(nearArcStart, nearArcMid, nearArcEnd)
lines.addByTwoPoints(nearArcEnd, farArcStart)
arcs.addByThreePoints(farArcStart, farArcMid, farArcEnd)
lines.addByTwoPoints(farArcEnd, nearArcStart)

pokeExtrude = extrudeUtils.simpleDistanceExtrude(
sketch.profiles.item(0),
adsk.fusion.FeatureOperations.NewBodyFeatureOperation,
-depth,
adsk.fusion.ExtentDirections.PositiveExtentDirection,
[],
targetComponent,
)
pokeExtrude.name = "Refined poke-through extrude"
return pokeExtrude.bodies.item(0)


def simpleBox(
plane: adsk.core.Base,
planeOffset: float,
Expand Down