diff --git a/freecad/gridfinity_workbench/const.py b/freecad/gridfinity_workbench/const.py
index 102ae15e..65d4bae6 100644
--- a/freecad/gridfinity_workbench/const.py
+++ b/freecad/gridfinity_workbench/const.py
@@ -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
diff --git a/freecad/gridfinity_workbench/feature_construction.py b/freecad/gridfinity_workbench/feature_construction.py
index 007a0a4d..065697ef 100644
--- a/freecad/gridfinity_workbench/feature_construction.py
+++ b/freecad/gridfinity_workbench/feature_construction.py
@@ -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)
@@ -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
@@ -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(
@@ -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
"
+ f"
0 to disable
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
"
+ f"
0 to disable
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),
)
diff --git a/freecad/gridfinity_workbench/features.py b/freecad/gridfinity_workbench/features.py
index 16fd6f3f..fa121958 100644
--- a/freecad/gridfinity_workbench/features.py
+++ b/freecad/gridfinity_workbench/features.py
@@ -20,8 +20,6 @@
)
from .version import __version__
-unitmm = fc.Units.Quantity("1 mm")
-
class FoundationGridfinity:
def __init__(self, obj: fc.DocumentObject) -> None:
@@ -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:
@@ -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
"
+ f"
0 to disable
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
"
+ f"
0 to disable
default = {const.STACKING_LIP_NOTCHES_RECESS} mm ",
+ )
+
class FullBin(FoundationGridfinity):
"""Gridfinity abstract FullBin object.
@@ -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))
@@ -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(
@@ -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))
@@ -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"))
diff --git a/freecad/gridfinity_workbench/grid_initial_layout.py b/freecad/gridfinity_workbench/grid_initial_layout.py
index 50af3b54..428d10ff 100644
--- a/freecad/gridfinity_workbench/grid_initial_layout.py
+++ b/freecad/gridfinity_workbench/grid_initial_layout.py
@@ -3,6 +3,7 @@
import FreeCAD as fc # noqa: N813
from . import const
+from .utils import GridfinityLayout
def _location_properties(obj: fc.DocumentObject) -> None:
@@ -114,7 +115,7 @@ def rectangle_layout_properties(obj: fc.DocumentObject, *, baseplate_default: bo
)
-def make_rectangle_layout(obj: fc.DocumentObject) -> list[list[bool]]:
+def make_rectangle_layout(obj: fc.DocumentObject) -> GridfinityLayout:
"""Generate Rectanble layout and calculate relevant parameters."""
if obj.GenerationLocation == "Centered at Origin":
if obj.Baseplate:
@@ -156,7 +157,7 @@ def custom_shape_layout_properties(obj: fc.DocumentObject, *, baseplate_default:
).Baseplate = baseplate_default
-def make_custom_shape_layout(obj: fc.DocumentObject, layout: list[list[bool]]) -> None:
+def make_custom_shape_layout(obj: fc.DocumentObject, layout: GridfinityLayout) -> None:
"""Calculate values for custom shape.
Args: