Skip to content
3 changes: 3 additions & 0 deletions freecad/gridfinity_workbench/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
STACKING_LIP_TOP_LEDGE = 0.4
STACKING_LIP_BOTTOM_CHAMFER = 0.7
STACKING_LIP_VERTICAL_SECTION = 1.8
STACKING_LIP_NOTCHES = False
STACKING_LIP_NOTCHES_CHAMFER = 0.6
STACKING_LIP_NOTCHES_RECESS = 0.6

RECESSED_TOP_DEPTH = 0
SEQUENTIAL_BRIDGING_LAYER_HEIGHT = 0.2
Expand Down
213 changes: 192 additions & 21 deletions freecad/gridfinity_workbench/feature_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ def make_complex_bin_base(
if obj.Baseplate:
baseplate_size_adjustment = obj.BaseplateTopLedgeWidth - obj.Clearance
else:
baseplate_size_adjustment = 0 * unitmm
baseplate_size_adjustment = zeromm

x_bt_cmf_width = (
(obj.xGridSize - obj.Clearance * 2)
Expand Down Expand Up @@ -1249,8 +1249,6 @@ def _stacking_lip_profile(obj: fc.DocumentObject) -> Part.Wire:
"""Create stacking lip profile wire."""
## Calculated Values
obj.StackingLipTopChamfer = obj.BaseProfileTopChamfer - obj.Clearance - obj.StackingLipTopLedge

## Stacking Lip Generation
x1 = obj.Clearance
x2 = x1 + obj.StackingLipTopLedge
x3 = x2 + obj.StackingLipTopChamfer
Expand All @@ -1261,28 +1259,99 @@ def _stacking_lip_profile(obj: fc.DocumentObject) -> Part.Wire:
z2 = obj.StackingLipBottomChamfer + obj.StackingLipVerticalSection
z3 = obj.StackingLipBottomChamfer
z4 = -obj.StackingLipVerticalSection
z5 = (
z4
z5 = z4 - abs(x4 - x5)
if obj.StackingLipNotches:
st = [
fc.Vector(x1, y, 0),
fc.Vector(x4, y, 0),
fc.Vector(x4, y, z4),
fc.Vector(x5, y, z5),
fc.Vector(x1, y, z5),
]
else:
st = [
fc.Vector(x1, y, 0),
fc.Vector(x1, y, z1),
fc.Vector(x2, y, z1),
fc.Vector(x3, y, z2),
fc.Vector(x3, y, z3),
fc.Vector(x4, y, 0),
fc.Vector(x4, y, z4),
fc.Vector(x5, y, z5),
fc.Vector(x5, y, 0),
]

stacking_lip_profile = Part.Wire(Part.Shape(utils.loop(st)).Edges)

return stacking_lip_profile


def _stacking_lip_plate(
obj: fc.DocumentObject,
layout: GridfinityLayout,
) -> Part.Shape:
"""Creaet complex shaped bin base."""
x_bt_cmf_width = (
(obj.xGridSize - obj.Clearance * 2)
- 2 * obj.StackingLipBottomChamfer
- 2 * obj.StackingLipTopChamfer
- 2 * obj.StackingLipTopLedge
)
y_bt_cmf_width = (
(obj.yGridSize - obj.Clearance * 2)
- 2 * obj.StackingLipBottomChamfer
- 2 * obj.StackingLipTopChamfer
- 2 * obj.StackingLipTopLedge
)
x_vert_width = (
(obj.xGridSize - obj.Clearance * 2)
- 2 * obj.StackingLipTopChamfer
- 2 * obj.StackingLipTopLedge
)
y_vert_width = (
(obj.yGridSize - obj.Clearance * 2)
- 2 * obj.StackingLipTopChamfer
- 2 * obj.StackingLipTopLedge
)

bottom_chamfer = utils.rounded_rectangle_chamfer(
x_bt_cmf_width,
y_bt_cmf_width,
zeromm,
obj.StackingLipBottomChamfer,
obj.BinOuterRadius
- obj.StackingLipTopLedge
- obj.StackingLipTopChamfer
- obj.StackingLipBottomChamfer
+ obj.WallThickness
- obj.StackingLipBottomChamfer,
)
st = [
fc.Vector(x1, y, 0),
fc.Vector(x1, y, z1),
fc.Vector(x2, y, z1),
fc.Vector(x3, y, z2),
fc.Vector(x3, y, z3),
fc.Vector(x4, y, 0),
fc.Vector(x4, y, z4),
fc.Vector(x5, y, z5),
fc.Vector(x5, y, 0),
]

stacking_lip_profile = Part.Wire(Part.Shape(utils.loop(st)).Edges)
vertical_section = utils.rounded_rectangle_extrude(
x_vert_width,
y_vert_width,
obj.StackingLipBottomChamfer,
obj.StackingLipVerticalSection,
obj.BinOuterRadius - obj.StackingLipTopLedge - obj.StackingLipTopChamfer,
)
assembly = bottom_chamfer.fuse(vertical_section)

return stacking_lip_profile
top_chamfer = utils.rounded_rectangle_chamfer(
x_vert_width,
y_vert_width,
obj.StackingLipBottomChamfer + obj.StackingLipVerticalSection,
obj.StackingLipTopChamfer,
obj.BinOuterRadius - obj.StackingLipTopLedge - obj.StackingLipTopChamfer,
)

assembly = bottom_chamfer.multiFuse([vertical_section, top_chamfer])

fuse_total = utils.copy_in_layout(assembly, layout, obj.xGridSize, obj.yGridSize)

return fuse_total.translate(
fc.Vector(
obj.xGridSize / 2,
obj.yGridSize / 2,
),
)


def stacking_lip_properties(
Expand Down Expand Up @@ -1338,18 +1407,120 @@ def stacking_lip_properties(
read_only=True,
).StackingLipVerticalSection = const.STACKING_LIP_VERTICAL_SECTION

## Gridfinity Non Standard Parameters
obj.addProperty(
"App::PropertyBool",
"StackingLipNotches",
"GridfinityNonStandard",
"Toggle the notches on the stacking lip on or off",
).StackingLipNotches = const.STACKING_LIP_NOTCHES
obj.addProperty(
"App::PropertyLength",
"StackingLipNotchesChamfer",
"GridfinityNonStandard",
"Chamfer on the notches of the Stacking lip<br>"
f" <br> 0 to disable<br> <br> default = {const.STACKING_LIP_NOTCHES_CHAMFER} mm ",
).StackingLipNotchesChamfer = const.STACKING_LIP_NOTCHES_CHAMFER
obj.addProperty(
"App::PropertyLength",
"StackingLipNotchesRecess",
"GridfinityNonStandard",
"Recess of the notches of the Stacking lip<br>"
f" <br> 0 to disable<br> <br> default = {const.STACKING_LIP_NOTCHES_RECESS} mm ",
).StackingLipNotchesRecess = const.STACKING_LIP_NOTCHES_RECESS


def make_stacking_lip(obj: fc.DocumentObject, bin_outside_shape: Part.Wire) -> Part.Shape:
def make_stacking_lip(
obj: fc.DocumentObject,
layout: GridfinityLayout,
bin_outside_shape: Part.Wire,
) -> Part.Shape:
"""Create stacking lip based on input bin shape.

Args:
obj (FreeCAD.DocumentObject): DocumentObject
layout (GridfinityLayout): layout of the bin
bin_outside_shape (Part.Wire): exterior wall of the bin

"""
wire = _stacking_lip_profile(obj)
stacking_lip = Part.Wire(bin_outside_shape).makePipe(wire)
stacking_lip = Part.makeSolid(stacking_lip)
if obj.StackingLipNotches:
height = (
obj.StackingLipBottomChamfer
+ obj.StackingLipVerticalSection
+ obj.StackingLipTopChamfer
)
cover = utils.rounded_rectangle_extrude(
obj.xTotalWidth,
obj.yTotalWidth,
0,
height,
obj.BinOuterRadius,
).translate(
fc.Vector(
obj.xTotalWidth / 2 + obj.Clearance,
obj.yTotalWidth / 2 + obj.Clearance,
),
)
base = _stacking_lip_plate(obj, layout)
cover = cover.cut(base)
offset = obj.StackingLipTopLedge + obj.StackingLipTopChamfer + obj.StackingLipBottomChamfer
cutout = utils.rounded_rectangle_extrude(
obj.xTotalWidth - offset * 2,
obj.yTotalWidth - offset * 2,
0,
height,
obj.BinOuterRadius - offset,
).translate(
fc.Vector(
obj.xTotalWidth / 2 + obj.Clearance,
obj.yTotalWidth / 2 + obj.Clearance,
),
)

if obj.StackingLipNotchesRecess > 0:
chamfer_offset = obj.StackingLipTopLedge + obj.StackingLipNotchesRecess
cutout_recess = utils.rounded_rectangle_chamfer(
obj.xTotalWidth - chamfer_offset * 2,
obj.yTotalWidth - chamfer_offset * 2,
height - obj.StackingLipNotchesRecess,
obj.StackingLipNotchesRecess,
obj.BinOuterRadius - obj.StackingLipTopLedge - obj.StackingLipNotchesRecess,
).translate(
fc.Vector(
obj.xTotalWidth / 2 + obj.Clearance,
obj.yTotalWidth / 2 + obj.Clearance,
),
)
cutout = cutout.fuse(cutout_recess)

if obj.StackingLipNotchesChamfer > 0:
chamfer_offset = (
obj.StackingLipTopLedge + obj.StackingLipTopChamfer + obj.StackingLipBottomChamfer
)
cutout_chamfer = utils.rounded_rectangle_chamfer(
obj.xTotalWidth - chamfer_offset * 2,
obj.yTotalWidth - chamfer_offset * 2,
height - obj.StackingLipNotchesRecess - obj.StackingLipNotchesChamfer,
obj.StackingLipNotchesChamfer,
obj.BinOuterRadius
- obj.StackingLipTopLedge
- obj.StackingLipNotchesRecess
- obj.StackingLipNotchesChamfer,
).translate(
fc.Vector(
obj.xTotalWidth / 2 + obj.Clearance,
obj.yTotalWidth / 2 + obj.Clearance,
),
)
cutout = cutout.fuse(cutout_chamfer)

cover = cover.cut(cutout)
stacking_lip = stacking_lip.fuse(cover)
stacking_lip = stacking_lip.removeSplitter()

stacking_lip = stacking_lip.translate(
fc.Vector(-obj.xLocationOffset, -obj.yLocationOffset),
)
Expand Down
64 changes: 58 additions & 6 deletions freecad/gridfinity_workbench/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
)
from .version import __version__

unitmm = fc.Units.Quantity("1 mm")


class FoundationGridfinity:
def __init__(self, obj: fc.DocumentObject) -> None:
Expand Down Expand Up @@ -54,7 +52,7 @@ def execute(self, fp: Part.Feature) -> None:
fp.Shape = gridfinity_shape

@abstractmethod
def generate_gridfinity_shape(self, fp: fc.DocumentObject) -> Part.Shape:
def generate_gridfinity_shape(self, obj: fc.DocumentObject) -> Part.Shape:
"""Generate the TopoShape of the object."""

def dumps(self) -> dict:
Expand All @@ -67,6 +65,59 @@ def loads(self, state: dict) -> None:
State argument required, otherwise expecting argument error message.
"""

def add_property_if_missing( # noqa: PLR0913
self,
obj: fc.DocumentObject,
default_value: object,
prop_type: str,
name: str,
group: str = "",
doc: str = "",
attr: int = 0,
read_only: bool = False, # noqa: FBT001, FBT002
hidden: bool = False, # noqa: FBT001, FBT002
) -> None:
if name not in obj.PropertiesList:
obj = obj.addProperty(
type=prop_type,
name=name,
group=group,
doc=doc,
attr=attr,
read_only=read_only,
hidden=hidden,
)
setattr(obj, name, default_value)

def onDocumentRestored(self, obj: fc.DocumentObject) -> None: # noqa: N802
if hasattr(obj, "StackingLip"):
self.add_property_if_missing(
obj,
const.STACKING_LIP_NOTCHES,
"App::PropertyBool",
"StackingLipNotches",
"GridfinityNonStandard",
"Toggle the notches on the stacking lip on or off",
)
self.add_property_if_missing(
obj,
const.STACKING_LIP_NOTCHES_CHAMFER,
"App::PropertyLength",
"StackingLipNotchesChamfer",
"GridfinityNonStandard",
"Chamfer on the notches of the Stacking lip<br>"
f" <br> 0 to disable<br> <br> default = {const.STACKING_LIP_NOTCHES_CHAMFER} mm ",
)
self.add_property_if_missing(
obj,
const.STACKING_LIP_NOTCHES_RECESS,
"App::PropertyLength",
"StackingLipNotchesRecess",
"GridfinityNonStandard",
"Recess of the notches of the Stacking lip<br> "
f"<br> 0 to disable<br> <br> default = {const.STACKING_LIP_NOTCHES_RECESS} mm ",
)


class FullBin(FoundationGridfinity):
"""Gridfinity abstract FullBin object.
Expand Down Expand Up @@ -124,7 +175,7 @@ def generate_gridfinity_shape(self, obj: fc.DocumentObject) -> Part.Shape:
fuse_total = fuse_total.cut(feat.make_blank_bin_recessed_top(obj, bin_inside_shape))

if obj.StackingLip:
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, bin_outside_shape))
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, layout, bin_outside_shape))

if obj.ScrewHoles or obj.MagnetHoles:
fuse_total = fuse_total.cut(feat.make_bin_bottom_holes(obj, layout))
Expand Down Expand Up @@ -184,6 +235,7 @@ def __init__(
feat.scoop_properties(obj, scoop_default=scoop_default)

def generate_gridfinity_shape(self, obj: fc.DocumentObject) -> Part.Shape:

layout = grid_initial_layout.make_rectangle_layout(obj)

bin_outside_shape = utils.create_rounded_rectangle(
Expand Down Expand Up @@ -215,7 +267,7 @@ def generate_gridfinity_shape(self, obj: fc.DocumentObject) -> Part.Shape:
fuse_total = fuse_total.cut(feat.make_compartments(obj, compartments))

if obj.StackingLip:
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, bin_outside_shape))
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, layout, bin_outside_shape))

if obj.ScrewHoles or obj.MagnetHoles:
fuse_total = fuse_total.cut(feat.make_bin_bottom_holes(obj, layout))
Expand Down Expand Up @@ -317,7 +369,7 @@ def generate_gridfinity_shape(self, obj: fc.DocumentObject) -> Part.Shape:
fuse_total = fuse_total.cut(feat.make_bin_bottom_holes(obj, layout))

if obj.StackingLip:
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, bin_outside_shape))
fuse_total = fuse_total.fuse(feat.make_stacking_lip(obj, layout, bin_outside_shape))

if obj.LabelShelfStyle != "Off":
fuse_total = fuse_total.fuse(feat.make_label_shelf(obj, "eco"))
Expand Down
Loading
Loading