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: