diff --git a/commands/commandCreateBin/entry.py b/commands/commandCreateBin/entry.py index 30c9479..621c282 100644 --- a/commands/commandCreateBin/entry.py +++ b/commands/commandCreateBin/entry.py @@ -96,6 +96,15 @@ BIN_TAB_WIDTH_INPUT_ID = 'bin_tab_width' BIN_TAB_POSITION_INPUT_ID = 'bin_tab_position' BIN_TAB_ANGLE_INPUT_ID = 'bin_tab_angle' +BIN_TAB_METHOD_INPUT_ID = 'bin_tab_method' +BIN_TAB_ROOT_THICKNESS_INPUT_ID = 'bin_tab_root_thickness' +BIN_TAB_TIP_THICKNESS_INPUT_ID = 'bin_tab_tip_thickness' +BIN_TAB_UNIFORM_THICKNESS_INPUT_ID = 'bin_tab_uniform_thickness' +BIN_TAB_FILLET_UNIFORM_INPUT_ID = 'bin_tab_fillet_uniform' +BIN_TAB_FILLET_TOP_INPUT_ID = 'bin_tab_fillet_top' +BIN_TAB_FILLET_BOTTOM_INPUT_ID = 'bin_tab_fillet_bottom' +BIN_TAB_FILLET_BACK_INPUT_ID = 'bin_tab_fillet_back' + BIN_WITH_LIP_INPUT_ID = 'with_lip' BIN_WITH_LIP_NOTCHES_INPUT_ID = 'with_lip_notches' BIN_COMPARTMENT_REAL_DIMENSIONS_TABLE = "compartment_real_dimensions" @@ -161,7 +170,7 @@ def initDefaultUiState(): commandUIState.initValue(BIN_XY_CLEARANCE_INPUT_ID, const.BIN_XY_CLEARANCE, adsk.core.ValueCommandInput.classType()) commandUIState.initValue(BIN_WIDTH_INPUT_ID, 2, adsk.core.IntegerSpinnerCommandInput.classType()) commandUIState.initValue(BIN_LENGTH_INPUT_ID, 3, adsk.core.IntegerSpinnerCommandInput.classType()) - commandUIState.initValue(BIN_HEIGHT_INPUT_ID, 5, adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_HEIGHT_INPUT_ID, 5, adsk.core.IntegerSpinnerCommandInput.classType()) commandUIState.initValue(BIN_GENERATE_BODY_INPUT_ID, True, adsk.core.BoolValueCommandInput.classType()) commandUIState.initValue(BIN_TYPE_DROPDOWN_ID, BIN_TYPE_HOLLOW, adsk.core.DropDownCommandInput.classType()) @@ -181,6 +190,15 @@ def initDefaultUiState(): commandUIState.initValue(BIN_TAB_WIDTH_INPUT_ID, const.BIN_TAB_WIDTH, adsk.core.ValueCommandInput.classType()) commandUIState.initValue(BIN_TAB_POSITION_INPUT_ID, 0, adsk.core.ValueCommandInput.classType()) commandUIState.initValue(BIN_TAB_ANGLE_INPUT_ID, '45 deg', adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_METHOD_INPUT_ID, const.BIN_TAB_METHOD_ANGLE, adsk.core.DropDownCommandInput.classType()) + commandUIState.initValue(BIN_TAB_ROOT_THICKNESS_INPUT_ID, const.BIN_TAB_DEFAULT_ROOT_THICKNESS, adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID, const.BIN_TAB_DEFAULT_IS_UNIFORM, adsk.core.BoolValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_TIP_THICKNESS_INPUT_ID, const.BIN_TAB_DEFAULT_TIP_THICKNESS, adsk.core.ValueCommandInput.classType()) + # Fillets default to half the tip thickness (0.07) + commandUIState.initValue(BIN_TAB_FILLET_TOP_INPUT_ID, const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2, adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_FILLET_BOTTOM_INPUT_ID, const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2, adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_FILLET_BACK_INPUT_ID, 0.0, adsk.core.ValueCommandInput.classType()) + commandUIState.initValue(BIN_TAB_FILLET_UNIFORM_INPUT_ID, const.BIN_TAB_DEFAULT_IS_FILLET_UNIFORM, adsk.core.BoolValueCommandInput.classType()) commandUIState.initValue(BIN_GENERATE_BASE_INPUT_ID, True, adsk.core.BoolValueCommandInput.classType()) commandUIState.initValue(BIN_SCREW_HOLES_INPUT_ID, False, adsk.core.BoolValueCommandInput.classType()) @@ -369,8 +387,11 @@ def render_compartments_table(inputs: adsk.core.CommandInputs): compartmentsGroup: adsk.core.GroupCommandInput = commandUIState.getInput(BIN_COMPARTMENTS_GROUP_ID) binCompartmentsTable = compartmentsGroup.children.addTableCommandInput(BIN_COMPARTMENTS_TABLE_ID, "Compartments", 5, "1:1:1:1:1") addButton = compartmentsGroup.commandInputs.addBoolValueInput(BIN_COMPARTMENTS_TABLE_ADD_ID, "Add", False, "", False) + commandUIState.registerCommandInput(addButton) removeButton = compartmentsGroup.commandInputs.addBoolValueInput(BIN_COMPARTMENTS_TABLE_REMOVE_ID, "Remove", False, "", False) + commandUIState.registerCommandInput(removeButton) populateUniform = compartmentsGroup.commandInputs.addBoolValueInput(BIN_COMPARTMENTS_TABLE_UNIFORM_ID, "Reset to uniform", False, "", False) + commandUIState.registerCommandInput(populateUniform) binCompartmentsTable.addToolbarCommandInput(addButton) binCompartmentsTable.addToolbarCommandInput(removeButton) binCompartmentsTable.addToolbarCommandInput(populateUniform) @@ -548,8 +569,7 @@ def command_created(args: adsk.core.CommandCreatedEventArgs): commandUIState.registerCommandInput(binWidthInput) binLengthInput = binDimensionsGroup.children.addIntegerSpinnerCommandInput(BIN_LENGTH_INPUT_ID, 'Bin length, Y (u)', 1, 100, 1, commandUIState.getState(BIN_LENGTH_INPUT_ID)) commandUIState.registerCommandInput(binLengthInput) - binHeightInput = binDimensionsGroup.children.addValueInput(BIN_HEIGHT_INPUT_ID, 'Bin height, Z (u)', '', adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_HEIGHT_INPUT_ID))) - binHeightInput.minimumValue = 1 + binHeightInput = binDimensionsGroup.children.addIntegerSpinnerCommandInput(BIN_HEIGHT_INPUT_ID, 'Bin height, Z (u)', 1, 100, 1, int(commandUIState.getState(BIN_HEIGHT_INPUT_ID))) binHeightInput.isMinimumInclusive = True commandUIState.registerCommandInput(binHeightInput) @@ -615,14 +635,49 @@ def command_created(args: adsk.core.CommandCreatedEventArgs): commandUIState.registerCommandInput(binTabLengthInput) binTabWidthInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_WIDTH_INPUT_ID, 'Tab width (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_WIDTH_INPUT_ID))) commandUIState.registerCommandInput(binTabWidthInput) - binTabPostionInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_POSITION_INPUT_ID, 'Tab offset (u)', '', adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_POSITION_INPUT_ID))) - commandUIState.registerCommandInput(binTabPostionInput) - tabObverhangAngleInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_ANGLE_INPUT_ID, 'Tab overhang angle', 'deg', adsk.core.ValueInput.createByString(str(commandUIState.getState(BIN_TAB_ANGLE_INPUT_ID)))) - tabObverhangAngleInput.minimumValue = math.radians(30) - tabObverhangAngleInput.isMinimumInclusive = True - tabObverhangAngleInput.maximumValue = math.radians(65) - tabObverhangAngleInput.isMaximumInclusive = True - commandUIState.registerCommandInput(tabObverhangAngleInput) + binTabPositionInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_POSITION_INPUT_ID, 'Tab offset (u)', '', adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_POSITION_INPUT_ID))) + binTabPositionInput.minimumValue = 0.0 + binTabPositionInput.isMinimumInclusive = True + commandUIState.registerCommandInput(binTabPositionInput) + tabOverhangAngleInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_ANGLE_INPUT_ID, 'Tab overhang angle', 'deg', adsk.core.ValueInput.createByString(str(commandUIState.getState(BIN_TAB_ANGLE_INPUT_ID)))) + tabOverhangAngleInput.minimumValue = math.radians(30) + tabOverhangAngleInput.isMinimumInclusive = True + tabOverhangAngleInput.maximumValue = math.radians(65) + tabOverhangAngleInput.isMaximumInclusive = True + commandUIState.registerCommandInput(tabOverhangAngleInput) + + tabMethodDropdown = binTabFeaturesGroup.children.addDropDownCommandInput(BIN_TAB_METHOD_INPUT_ID, 'Tab construction method', adsk.core.DropDownStyles.LabeledIconDropDownStyle) + tabMethodDropdownDefaultValue = commandUIState.getState(BIN_TAB_METHOD_INPUT_ID) + tabMethodDropdown.listItems.add(const.BIN_TAB_METHOD_ANGLE, tabMethodDropdownDefaultValue == const.BIN_TAB_METHOD_ANGLE) + tabMethodDropdown.listItems.add(const.BIN_TAB_METHOD_DIMENSIONS, tabMethodDropdownDefaultValue == const.BIN_TAB_METHOD_DIMENSIONS) + commandUIState.registerCommandInput(tabMethodDropdown) + + rootThicknessInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_ROOT_THICKNESS_INPUT_ID, 'Root thickness (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_ROOT_THICKNESS_INPUT_ID))) + rootThicknessInput.minimumValue = 0.001 + rootThicknessInput.isMinimumInclusive = True + commandUIState.registerCommandInput(rootThicknessInput) + + uniformThicknessInput = binTabFeaturesGroup.children.addBoolValueInput(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID, 'Uniform thickness', True, '', commandUIState.getState(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID)) + commandUIState.registerCommandInput(uniformThicknessInput) + + tipThicknessInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_TIP_THICKNESS_INPUT_ID, 'Tip thickness (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_TIP_THICKNESS_INPUT_ID))) + tipThicknessInput.minimumValue = 0.001 + tipThicknessInput.isMinimumInclusive = True + commandUIState.registerCommandInput(tipThicknessInput) + + # Fillet Inputs + topFilletInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_FILLET_TOP_INPUT_ID, 'Top front fillet (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_FILLET_TOP_INPUT_ID))) + commandUIState.registerCommandInput(topFilletInput) + + uniformFilletInput = binTabFeaturesGroup.children.addBoolValueInput(BIN_TAB_FILLET_UNIFORM_INPUT_ID, 'Uniform fillet', True, '', commandUIState.getState(BIN_TAB_FILLET_UNIFORM_INPUT_ID)) + commandUIState.registerCommandInput(uniformFilletInput) + + bottomFilletInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_FILLET_BOTTOM_INPUT_ID, 'Bottom front fillet (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_FILLET_BOTTOM_INPUT_ID))) + commandUIState.registerCommandInput(bottomFilletInput) + + backFilletInput = binTabFeaturesGroup.children.addValueInput(BIN_TAB_FILLET_BACK_INPUT_ID, 'Back bottom fillet (mm)', defaultLengthUnits, adsk.core.ValueInput.createByReal(commandUIState.getState(BIN_TAB_FILLET_BACK_INPUT_ID))) + commandUIState.registerCommandInput(backFilletInput) + for input in binTabFeaturesGroup.children: if not input.id == BIN_HAS_TAB_INPUT_ID: input.isEnabled = commandUIState.getState(BIN_HAS_TAB_INPUT_ID) @@ -809,7 +864,7 @@ def onChangeValidate(): commandUIState.getInput(BIN_MAGNET_CUTOUTS_TABS_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 + commandUIState.getInput(BIN_SCREW_DIAMETER_INPUT).isEnabled = generateBase and commandUIState.getState(BIN_SCREW_HOLES_INPUT_ID) generateBody: bool = commandUIState.getState(BIN_GENERATE_BODY_INPUT_ID) binType: str = commandUIState.getState(BIN_TYPE_DROPDOWN_ID) @@ -827,13 +882,50 @@ def onChangeValidate(): generateScoop: bool = commandUIState.getState(BIN_HAS_SCOOP_INPUT_ID) commandUIState.getInput(BIN_SCOOP_MAX_RADIUS_INPUT_ID).isEnabled = generateScoop + + gridType: str = commandUIState.getState(BIN_COMPARTMENTS_GRID_TYPE_ID) + isCustomGrid = gridType == BIN_COMPARTMENTS_GRID_TYPE_CUSTOM + commandUIState.getInput(BIN_COMPARTMENTS_TABLE_ID).isVisible = isCustomGrid + commandUIState.getInput(BIN_COMPARTMENTS_TABLE_ADD_ID).isVisible = isCustomGrid + commandUIState.getInput(BIN_COMPARTMENTS_TABLE_REMOVE_ID).isVisible = isCustomGrid + commandUIState.getInput(BIN_COMPARTMENTS_TABLE_UNIFORM_ID).isVisible = isCustomGrid generateTab: bool = commandUIState.getState(BIN_HAS_TAB_INPUT_ID) + tabMethod: str = commandUIState.getState(BIN_TAB_METHOD_INPUT_ID) + isDimensionsMethod = tabMethod == const.BIN_TAB_METHOD_DIMENSIONS + isUniformThickness = commandUIState.getState(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID) + commandUIState.getInput(BIN_TAB_LENGTH_INPUT_ID).isEnabled = generateTab commandUIState.getInput(BIN_TAB_WIDTH_INPUT_ID).isEnabled = generateTab - commandUIState.getInput(BIN_TAB_ANGLE_INPUT_ID).isEnabled = generateTab commandUIState.getInput(BIN_TAB_POSITION_INPUT_ID).isEnabled = generateTab + commandUIState.getInput(BIN_TAB_ANGLE_INPUT_ID).isVisible = not isDimensionsMethod + commandUIState.getInput(BIN_TAB_ANGLE_INPUT_ID).isEnabled = generateTab and not isDimensionsMethod + + commandUIState.getInput(BIN_TAB_METHOD_INPUT_ID).isEnabled = generateTab + + commandUIState.getInput(BIN_TAB_ROOT_THICKNESS_INPUT_ID).isVisible = isDimensionsMethod + commandUIState.getInput(BIN_TAB_ROOT_THICKNESS_INPUT_ID).isEnabled = generateTab and isDimensionsMethod + + commandUIState.getInput(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID).isVisible = isDimensionsMethod + commandUIState.getInput(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID).isEnabled = generateTab and isDimensionsMethod + + commandUIState.getInput(BIN_TAB_TIP_THICKNESS_INPUT_ID).isVisible = isDimensionsMethod + commandUIState.getInput(BIN_TAB_TIP_THICKNESS_INPUT_ID).isEnabled = generateTab and isDimensionsMethod and not isUniformThickness + + isFilletUniform = commandUIState.getState(BIN_TAB_FILLET_UNIFORM_INPUT_ID) + commandUIState.getInput(BIN_TAB_FILLET_TOP_INPUT_ID).isVisible = generateTab + commandUIState.getInput(BIN_TAB_FILLET_TOP_INPUT_ID).isEnabled = generateTab + + commandUIState.getInput(BIN_TAB_FILLET_UNIFORM_INPUT_ID).isVisible = generateTab + commandUIState.getInput(BIN_TAB_FILLET_UNIFORM_INPUT_ID).isEnabled = generateTab + + commandUIState.getInput(BIN_TAB_FILLET_BOTTOM_INPUT_ID).isVisible = generateTab + commandUIState.getInput(BIN_TAB_FILLET_BOTTOM_INPUT_ID).isEnabled = generateTab and not isFilletUniform + + commandUIState.getInput(BIN_TAB_FILLET_BACK_INPUT_ID).isVisible = generateTab + commandUIState.getInput(BIN_TAB_FILLET_BACK_INPUT_ID).isEnabled = generateTab + compartmentsGridType: str = commandUIState.getState(BIN_COMPARTMENTS_GRID_TYPE_ID) commandUIState.getInput(BIN_COMPARTMENTS_TABLE_ID).isVisible = compartmentsGridType == BIN_COMPARTMENTS_GRID_TYPE_CUSTOM @@ -883,6 +975,7 @@ def generateBin(args: adsk.core.CommandEventArgs): binCompartmentsTable: adsk.core.TableCommandInput = inputs.itemById(BIN_COMPARTMENTS_TABLE_ID) compartmentsX: adsk.core.IntegerSpinnerCommandInput = inputs.itemById(BIN_COMPARTMENTS_GRID_BASE_WIDTH_ID) compartmentsY: adsk.core.IntegerSpinnerCommandInput = inputs.itemById(BIN_COMPARTMENTS_GRID_BASE_LENGTH_ID) + tabMethod: str = inputs.itemById(BIN_TAB_METHOD_INPUT_ID).selectedItem.name isHollow = binTypeDropdownInput.selectedItem.name == BIN_TYPE_HOLLOW isSolid = binTypeDropdownInput.selectedItem.name == BIN_TYPE_SOLID @@ -950,6 +1043,24 @@ def generateBin(args: adsk.core.CommandEventArgs): binBodyInput.tabWidth = binTabWidth.value binBodyInput.tabPosition = binTabPosition.value binBodyInput.tabOverhangAngle = binTabAngle.value + binBodyInput.tabMethod = tabMethod + binBodyInput.rootThickness = commandUIState.getState(BIN_TAB_ROOT_THICKNESS_INPUT_ID) + + isUniform = commandUIState.getState(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID) + if isUniform: + binBodyInput.tipThickness = binBodyInput.rootThickness + else: + binBodyInput.tipThickness = commandUIState.getState(BIN_TAB_TIP_THICKNESS_INPUT_ID) + + isFilletUniform = commandUIState.getState(BIN_TAB_FILLET_UNIFORM_INPUT_ID) + binBodyInput.tabFilletTop = commandUIState.getState(BIN_TAB_FILLET_TOP_INPUT_ID) + if isFilletUniform: + binBodyInput.tabFilletBottom = binBodyInput.tabFilletTop + else: + binBodyInput.tabFilletBottom = commandUIState.getState(BIN_TAB_FILLET_BOTTOM_INPUT_ID) + + binBodyInput.tabFilletBack = commandUIState.getState(BIN_TAB_FILLET_BACK_INPUT_ID) + binBodyInput.compartmentsByX = compartmentsX.value binBodyInput.compartmentsByY = compartmentsY.value @@ -1027,6 +1138,17 @@ def generateBin(args: adsk.core.CommandEventArgs): compartmentTabInput.width = binBodyInput.tabWidth compartmentTabInput.overhangAngle = binBodyInput.tabOverhangAngle compartmentTabInput.topClearance = const.BIN_TAB_TOP_CLEARANCE + + # Populate new fields + compartmentTabInput.tabMethod = commandUIState.getState(BIN_TAB_METHOD_INPUT_ID) + compartmentTabInput.rootThickness = commandUIState.getState(BIN_TAB_ROOT_THICKNESS_INPUT_ID) + + isUniform = commandUIState.getState(BIN_TAB_UNIFORM_THICKNESS_INPUT_ID) + if isUniform: + compartmentTabInput.tipThickness = compartmentTabInput.rootThickness + else: + compartmentTabInput.tipThickness = commandUIState.getState(BIN_TAB_TIP_THICKNESS_INPUT_ID) + tabBody = createGridfinityBinBodyTab(compartmentTabInput, gridfinityBinComponent) combineInput = combineFeatures.createInput(tabBody, commonUtils.objectCollectionFromList([binBody])) combineInput.operation = adsk.fusion.FeatureOperations.CutFeatureOperation diff --git a/lib/gridfinityUtils/binBodyGenerator.py b/lib/gridfinityUtils/binBodyGenerator.py index fa13ecc..872fd68 100644 --- a/lib/gridfinityUtils/binBodyGenerator.py +++ b/lib/gridfinityUtils/binBodyGenerator.py @@ -144,6 +144,12 @@ def createGridfinityBinBody( compartmentTabInput.width = input.tabWidth compartmentTabInput.overhangAngle = input.tabOverhangAngle compartmentTabInput.topClearance = const.BIN_TAB_TOP_CLEARANCE + compartmentTabInput.tabMethod = input.tabMethod + compartmentTabInput.rootThickness = input.rootThickness + compartmentTabInput.tipThickness = input.tipThickness + compartmentTabInput.tabFilletTop = input.tabFilletTop + compartmentTabInput.tabFilletBottom = input.tabFilletBottom + compartmentTabInput.tabFilletBack = input.tabFilletBack [compartmentMerges, compartmentCuts] = createCompartment( input.wallThickness, @@ -257,12 +263,17 @@ def createCompartment( if hasTab: tabBody = createGridfinityBinBodyTab(tabInput, targetComponent) - intersectTabInput = targetComponent.features.combineFeatures.createInput( - tabBody, - commonUtils.objectCollectionFromList([innerCutoutBody]), - ) - intersectTabInput.operation = adsk.fusion.FeatureOperations.IntersectFeatureOperation - intersectTabInput.isKeepToolBodies = True - intersectTabFeature = targetComponent.features.combineFeatures.add(intersectTabInput) - bodiesToMerge = bodiesToMerge + [body for body in list(intersectTabFeature.bodies) if not body.revisionId == innerCutoutBody.revisionId] + try: + intersectTabInput = targetComponent.features.combineFeatures.createInput( + tabBody, + commonUtils.objectCollectionFromList([innerCutoutBody]), + ) + intersectTabInput.operation = adsk.fusion.FeatureOperations.IntersectFeatureOperation + intersectTabInput.isKeepToolBodies = True + intersectTabFeature = targetComponent.features.combineFeatures.add(intersectTabInput) + bodiesToMerge = bodiesToMerge + [body for body in list(intersectTabFeature.bodies) if not body.revisionId == innerCutoutBody.revisionId] + except: + # Fallback if intersection fails (e.g. Dimensions method might not overlap cutout exactly or logic differs) + # Just add the tab body itself + bodiesToMerge.append(tabBody) return (bodiesToMerge, bodiesToSubtract) \ No newline at end of file diff --git a/lib/gridfinityUtils/binBodyGeneratorInput.py b/lib/gridfinityUtils/binBodyGeneratorInput.py index 40eed7c..6371e31 100644 --- a/lib/gridfinityUtils/binBodyGeneratorInput.py +++ b/lib/gridfinityUtils/binBodyGeneratorInput.py @@ -62,8 +62,14 @@ def __init__(self): self.tabOverhangAngle = const.BIN_TAB_OVERHANG_ANGLE self.tabPosition = 0 self.tabLength = 1 - self.tabWidth = const.BIN_TAB_WIDTH - self.compartments = [BinBodyCompartmentDefinition()] + self.tabOverhangAngle = 45 + self.tabMethod = const.BIN_TAB_METHOD_ANGLE + self.rootThickness = const.BIN_TAB_DEFAULT_ROOT_THICKNESS + self.tipThickness = const.BIN_TAB_DEFAULT_TIP_THICKNESS + self.tabFilletTop = const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2 + self.tabFilletBottom = const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2 + self.tabFilletBack = const.BIN_TAB_DEFAULT_FILLET_BACK + self.compartments: list[BinBodyCompartmentDefinition] = [] self.compartmentsByX = 1 self.compartmentsByY = 1 self.binCornerFilletRadius = const.BIN_CORNER_FILLET_RADIUS diff --git a/lib/gridfinityUtils/binBodyTabGenerator.py b/lib/gridfinityUtils/binBodyTabGenerator.py index 14a8b80..f16d676 100644 --- a/lib/gridfinityUtils/binBodyTabGenerator.py +++ b/lib/gridfinityUtils/binBodyTabGenerator.py @@ -37,64 +37,125 @@ def createGridfinityBinBodyTab( tabProfilePlane = targetComponent.constructionPlanes.add(tabProfilePlaneInput) tabSketch: adsk.fusion.Sketch = targetComponent.sketches.add(tabProfilePlane) tabSketch.name = "label tab sketch" + tabSketchLine = tabSketch.sketchCurves.sketchLines + constraints = tabSketch.geometricConstraints + dimensions = tabSketch.sketchDimensions + tabTopEdgeHeight = input.origin.z - input.topClearance - actualTabWidth = input.width + BIN_TAB_EDGE_FILLET_RADIUS / math.tan((math.radians(90) - input.overhangAngle) / 2) - actualTabHeight = actualTabWidth / math.tan(input.overhangAngle) - line1 = tabSketchLine.addByTwoPoints( - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight)), - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight - actualTabHeight)), - ) - line2 = tabSketchLine.addByTwoPoints( - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight)), - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y - actualTabWidth, tabTopEdgeHeight)), - ) - line3 = tabSketchLine.addByTwoPoints( - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight - actualTabHeight)), - tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y - actualTabWidth, tabTopEdgeHeight)), - ) + if input.tabMethod == const.BIN_TAB_METHOD_DIMENSIONS: + rootThickness = input.rootThickness + tipThickness = input.tipThickness + + y_back = input.origin.y + y_front = input.origin.y - input.width + z_top = tabTopEdgeHeight + z_root_bottom = tabTopEdgeHeight - rootThickness + z_tip_bottom = tabTopEdgeHeight - tipThickness + + p_top_back = adsk.core.Point3D.create(input.origin.x, y_back, z_top) + p_top_front = adsk.core.Point3D.create(input.origin.x, y_front, z_top) + p_bot_front = adsk.core.Point3D.create(input.origin.x, y_front, z_tip_bottom) + p_bot_back = adsk.core.Point3D.create(input.origin.x, y_back, z_root_bottom) - constraints: adsk.fusion.GeometricConstraints = tabSketch.geometricConstraints - dimensions: adsk.fusion.SketchDimensions = tabSketch.sketchDimensions - - # horizontal/vertical relative to local sketch XY coordinates - constraints.addHorizontal(line1) - constraints.addVertical(line2) - constraints.addCoincident(line1.startSketchPoint, line2.startSketchPoint) - constraints.addCoincident(line2.endSketchPoint, line3.endSketchPoint) - constraints.addCoincident(line1.endSketchPoint, line3.startSketchPoint) - - dimensions.addDistanceDimension( - tabSketch.originPoint, - line1.startSketchPoint, - adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, - line1.startSketchPoint.geometry, - True - ) + l_top = tabSketchLine.addByTwoPoints(tabSketch.modelToSketchSpace(p_top_back), tabSketch.modelToSketchSpace(p_top_front)) + l_front = tabSketchLine.addByTwoPoints(tabSketch.modelToSketchSpace(p_top_front), tabSketch.modelToSketchSpace(p_bot_front)) + l_bot = tabSketchLine.addByTwoPoints(tabSketch.modelToSketchSpace(p_bot_front), tabSketch.modelToSketchSpace(p_bot_back)) + + if input.tabFilletBack > 0.001: + epsilon = 0.0001 + y_back_shifted = y_back - epsilon + + p_wall_low = adsk.core.Point3D.create(input.origin.x, y_back_shifted, z_root_bottom - input.tabFilletBack * 10.0 - 1.0) + l_wall_temp = tabSketchLine.addByTwoPoints(tabSketch.modelToSketchSpace(p_bot_back), tabSketch.modelToSketchSpace(p_wall_low)) + + arc = tabSketch.sketchCurves.sketchArcs.addFillet(l_bot, l_bot.endSketchPoint.geometry, l_wall_temp, l_wall_temp.startSketchPoint.geometry, input.tabFilletBack) + + p1 = arc.startSketchPoint + p2 = arc.endSketchPoint + p_wall_tan = p1 if p1.geometry.x > p2.geometry.x else p2 + + sp_top_back = tabSketch.modelToSketchSpace(p_top_back) + + l_wall_temp.deleteMe() + + l_back = tabSketchLine.addByTwoPoints(p_wall_tan, sp_top_back) + + constraints.addCoincident(l_top.endSketchPoint, l_front.startSketchPoint) + constraints.addCoincident(l_front.endSketchPoint, l_bot.startSketchPoint) + + constraints.addTangent(l_back, arc) + + constraints.addCoincident(l_back.endSketchPoint, l_top.startSketchPoint) + + else: + l_back = tabSketchLine.addByTwoPoints(tabSketch.modelToSketchSpace(p_bot_back), tabSketch.modelToSketchSpace(p_top_back)) + + constraints.addCoincident(l_top.endSketchPoint, l_front.startSketchPoint) + constraints.addCoincident(l_front.endSketchPoint, l_bot.startSketchPoint) + constraints.addCoincident(l_bot.endSketchPoint, l_back.startSketchPoint) + constraints.addCoincident(l_back.endSketchPoint, l_top.startSketchPoint) + + # Dimensions + dimensions.addDistanceDimension(l_top.startSketchPoint, l_top.endSketchPoint, adsk.fusion.DimensionOrientations.HorizontalDimensionOrientation, l_top.startSketchPoint.geometry, True) + dimensions.addDistanceDimension(l_back.startSketchPoint, l_back.endSketchPoint, adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, l_back.startSketchPoint.geometry, True) + dimensions.addDistanceDimension(l_front.startSketchPoint, l_front.endSketchPoint, adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, l_front.startSketchPoint.geometry, True) - dimensions.addDistanceDimension( - tabSketch.originPoint, - line1.startSketchPoint, - adsk.fusion.DimensionOrientations.HorizontalDimensionOrientation, - line1.startSketchPoint.geometry, - True + else: + # Angle based (Default) + actualTabWidth = input.width + BIN_TAB_EDGE_FILLET_RADIUS / math.tan((math.radians(90) - input.overhangAngle) / 2) + actualTabHeight = actualTabWidth / math.tan(input.overhangAngle) + line1 = tabSketchLine.addByTwoPoints( + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight)), + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight - actualTabHeight)), ) - - dimensions.addDistanceDimension( - line2.startSketchPoint, - line2.endSketchPoint, - adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, - line2.endSketchPoint.geometry, - True + line2 = tabSketchLine.addByTwoPoints( + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight)), + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y - actualTabWidth, tabTopEdgeHeight)), ) - - dimensions.addAngularDimension( - line1, - line3, - line1.endSketchPoint.geometry, - True, + line3 = tabSketchLine.addByTwoPoints( + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y, tabTopEdgeHeight - actualTabHeight)), + tabSketch.modelToSketchSpace(adsk.core.Point3D.create(input.origin.x, input.origin.y - actualTabWidth, tabTopEdgeHeight)), ) + # horizontal/vertical relative to local sketch XY coordinates + constraints.addHorizontal(line1) + constraints.addVertical(line2) + constraints.addCoincident(line1.startSketchPoint, line2.startSketchPoint) + constraints.addCoincident(line2.endSketchPoint, line3.endSketchPoint) + constraints.addCoincident(line1.endSketchPoint, line3.startSketchPoint) + + dimensions.addDistanceDimension( + tabSketch.originPoint, + line1.startSketchPoint, + adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, + line1.startSketchPoint.geometry, + True + ) + + dimensions.addDistanceDimension( + tabSketch.originPoint, + line1.startSketchPoint, + adsk.fusion.DimensionOrientations.HorizontalDimensionOrientation, + line1.startSketchPoint.geometry, + True + ) + + dimensions.addDistanceDimension( + line2.startSketchPoint, + line2.endSketchPoint, + adsk.fusion.DimensionOrientations.VerticalDimensionOrientation, + line2.endSketchPoint.geometry, + True + ) + + dimensions.addAngularDimension( + line1, + line3, + line1.endSketchPoint.geometry, + True, + ) + tabExtrudeFeature = extrudeUtils.simpleDistanceExtrude( tabSketch.profiles.item(0), adsk.fusion.FeatureOperations.NewBodyFeatureOperation, @@ -106,14 +167,49 @@ def createGridfinityBinBodyTab( tabBody = tabExtrudeFeature.bodies.item(0) tabBody.name = 'label tab' - tabTopFace = faceUtils.getTopFace(tabBody) - roundedEdge = min([edge for edge in tabTopFace.edges if geometryUtils.isCollinearToX(edge)], key=lambda x: x.boundingBox.minPoint.y) - fillet = filletUtils.createFillet( - [roundedEdge], - BIN_TAB_EDGE_FILLET_RADIUS, - False, - targetComponent - ) - fillet.name = 'label tab fillet' + # Fillet both Top and Bottom edges. + # We find all X-collinear edges. + x_edges = [edge for edge in tabBody.edges if geometryUtils.isCollinearToX(edge)] + + # Find the minimum Y (Front of the tab) + if x_edges: + min_y = min(x_edges, key=lambda e: e.boundingBox.minPoint.y).boundingBox.minPoint.y + # Select all edges at that min_y (within float tolerance) + front_edges = [edge for edge in x_edges if abs(edge.boundingBox.minPoint.y - min_y) < 0.001] + + if len(front_edges) >= 2: + # Sort by Z (height) desc. Top edge is first. + front_edges_sorted = sorted(front_edges, key=lambda e: e.boundingBox.minPoint.z, reverse=True) + top_edge = front_edges_sorted[0] + bottom_edge = front_edges_sorted[1] + + # Apply Top Fillet + if input.tabFilletTop > 0.001: + filletTop = filletUtils.createFillet( + [top_edge], + input.tabFilletTop, + False, + targetComponent + ) + filletTop.name = 'label tab fillet top' + + # Apply Bottom Fillet + if input.tabFilletBottom > 0.001: + filletBottom = filletUtils.createFillet( + [bottom_edge], + input.tabFilletBottom, + False, + targetComponent + ) + filletBottom.name = 'label tab fillet bottom' + elif len(front_edges) == 1: + if input.tabFilletTop > 0.001: + fillet = filletUtils.createFillet( + front_edges, + input.tabFilletTop, + False, + targetComponent + ) + fillet.name = 'label tab fillet' return tabBody diff --git a/lib/gridfinityUtils/binBodyTabGeneratorInput.py b/lib/gridfinityUtils/binBodyTabGeneratorInput.py index d523108..8bd28a9 100644 --- a/lib/gridfinityUtils/binBodyTabGeneratorInput.py +++ b/lib/gridfinityUtils/binBodyTabGeneratorInput.py @@ -7,6 +7,12 @@ def __init__(self): self.overhangAngle = const.BIN_TAB_OVERHANG_ANGLE self.labelAngle = const.BIN_TAB_LABEL_ANGLE self.position = 0 + self._tabMethod = const.BIN_TAB_METHOD_ANGLE + self._rootThickness = const.BIN_TAB_DEFAULT_ROOT_THICKNESS + self._tipThickness = const.BIN_TAB_DEFAULT_TIP_THICKNESS + self._tabFilletTop = const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2 + self._tabFilletBottom = const.BIN_TAB_DEFAULT_TIP_THICKNESS / 2 + self._tabFilletBack = const.BIN_TAB_DEFAULT_FILLET_BACK @property def topClearance(self) -> float: @@ -56,4 +62,52 @@ def labelAngle(self) -> float: def labelAngle(self, value: float): self._tablabelAngle = value + @property + def tabMethod(self) -> str: + return self._tabMethod + + @tabMethod.setter + def tabMethod(self, value: str): + self._tabMethod = value + + @property + def rootThickness(self) -> float: + return self._rootThickness + + @rootThickness.setter + def rootThickness(self, value: float): + self._rootThickness = value + + @property + def tipThickness(self) -> float: + return self._tipThickness + + @tipThickness.setter + def tipThickness(self, value: float): + self._tipThickness = value + + @property + def tabFilletTop(self) -> float: + return self._tabFilletTop + + @tabFilletTop.setter + def tabFilletTop(self, value: float): + self._tabFilletTop = value + + @property + def tabFilletBottom(self) -> float: + return self._tabFilletBottom + + @tabFilletBottom.setter + def tabFilletBottom(self, value: float): + self._tabFilletBottom = value + + @property + def tabFilletBack(self) -> float: + return self._tabFilletBack + + @tabFilletBack.setter + def tabFilletBack(self, value: float): + self._tabFilletBack = value + \ No newline at end of file diff --git a/lib/gridfinityUtils/const.py b/lib/gridfinityUtils/const.py index fefb27e..6d1138e 100644 --- a/lib/gridfinityUtils/const.py +++ b/lib/gridfinityUtils/const.py @@ -17,11 +17,30 @@ BIN_COMPARTMENT_BOTTOM_THICKNESS = 0.1 BIN_BODY_CUTOUT_BOTTOM_FILLET_RADIUS = 0.2 + BIN_TAB_WIDTH = 1.3 BIN_TAB_OVERHANG_ANGLE = 45 BIN_TAB_LABEL_ANGLE = 0 BIN_TAB_EDGE_FILLET_RADIUS = 0.06 BIN_TAB_TOP_CLEARANCE = 0.05 +BIN_TAB_METHOD_ANGLE = 'Angle' +BIN_TAB_METHOD_DIMENSIONS = 'Dimensions' +BIN_TAB_METHOD_INPUT_ID = 'bin_tab_method' +BIN_TAB_ROOT_THICKNESS_INPUT_ID = 'bin_tab_root_thickness' +BIN_TAB_UNIFORM_THICKNESS_INPUT_ID = 'bin_tab_uniform_thickness' +BIN_TAB_TIP_THICKNESS_INPUT_ID = 'bin_tab_tip_thickness' + +BIN_TAB_FILLET_UNIFORM_INPUT_ID = 'bin_tab_fillet_uniform' +BIN_TAB_FILLET_TOP_INPUT_ID = 'bin_tab_fillet_top' +BIN_TAB_FILLET_BOTTOM_INPUT_ID = 'bin_tab_fillet_bottom' +BIN_TAB_FILLET_BACK_INPUT_ID = 'bin_tab_fillet_back' + +# Default values (Internal units are cm, so 0.14 = 1.4mm) +BIN_TAB_DEFAULT_ROOT_THICKNESS = 0.14 +BIN_TAB_DEFAULT_TIP_THICKNESS = 0.14 +BIN_TAB_DEFAULT_IS_UNIFORM = True +BIN_TAB_DEFAULT_IS_FILLET_UNIFORM = True +BIN_TAB_DEFAULT_FILLET_BACK = 0.0 BIN_SCOOP_MAX_RADIUS = 2.5