From f379b0e690268b522ad93d54c470e9c0f88aef47 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 30 Jan 2023 17:50:30 -0500 Subject: [PATCH 01/10] Make the gridfinity boxes configurable with a parameter sheet --- .../GridFinityDividerBoxMaker.py | 609 ++++++++++++------ 1 file changed, 408 insertions(+), 201 deletions(-) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index 5b55ef5..434bcc8 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -5,17 +5,27 @@ from typing import Tuple import adsk.core, adsk.fusion, adsk.cam, traceback +defaultBoxName = "Box" +defaultSlotsWide = 2 +defaultSlotsDeep = 2 +defaultSlotsHigh = 1 +defaultDividerCount = 0 +defaultIncludeScoop = True +defaultIncludeLedge = True + +# global set of event handlers to keep them referenced for the duration of the command +handlers = [] +app = adsk.core.Application.get() +if app: + ui = app.userInterface + +newComp = None + # Why? SCALE = 0.1 -# User parameters -slotsWide = 2 -slotsDeep = 3 -slotsHigh = 1.5 -dividerCount = 5 - magnetDiameter = 6.5 * SCALE -magnetThiccness = 2.5 * SCALE +magnetThickness = 2.5 * SCALE # Sizes! # FIXME: make these strings, e.g. "42 mm" (maybe, some contexts that's not right for) @@ -25,7 +35,7 @@ nestingDepth = 5 * SCALE nestingRimWidth = 2.4 * SCALE nestingClearance = .25 * SCALE -wallThiccness = 1.2 * SCALE +wallThickness = 1.2 * SCALE holeOffset = 8 * SCALE # Derived sizes @@ -59,7 +69,6 @@ def createReal(r) -> adsk.core.ValueInput: def close(a, b): return abs(a - b) < 1e-5 * SCALE -# Geometry def createBaseRectSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: base_sketch = component.sketches.add(component.xZConstructionPlane) @@ -137,6 +146,7 @@ def createCurvedRect(component: adsk.fusion.Component, width: float, depth: floa # FIXME: not actually a path return path, sketch.profiles.item(0) + def createBaseSweepSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: b, _ = createCurvedRect(component, slotDimension, slotDimension, baseCornerRadius, 0) return b @@ -170,7 +180,7 @@ def rectPattern(body: adsk.fusion.BRepBody, wide: int, deep: int, dim: float) -> return pattern -def createRimSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: +def createRimSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion.Profile: cornerVerticalOffset = nestingClearance * .416 # Empirically determined from existing sketch sketch = component.sketches.add(component.xYConstructionPlane) lines = sketch.sketchCurves.sketchLines @@ -190,46 +200,46 @@ def createRimSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: return sketch.profiles.item(0) -def createIndentSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: +def createIndentSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion.Profile: sketch = component.sketches.add(component.xYConstructionPlane) lines = sketch.sketchCurves.sketchLines h = -slotDimension / 2 p = lambda x, y: createPoint(x, y , h) - # Note that this is wallThiccness below p4 in createRimSketch + # Note that this is wallThickness below p4 in createRimSketch x = nestingRimWidth + baseLip - nestingVerticalClearance - p0 = p(x, slotsHigh * slotDimension - wallThiccness) - p1 = p(x - wallThiccness, slotsHigh * slotDimension - 2 * wallThiccness) + p0 = p(x, slotsHigh * slotDimension - wallThickness) + p1 = p(x - wallThickness, slotsHigh * slotDimension - 2 * wallThickness) lines.addByTwoPoints(p0, p1) - p2 = p(x - wallThiccness, nestingDepth + wallThiccness + 1 * SCALE) + p2 = p(x - wallThickness, nestingDepth + wallThickness + 1 * SCALE) # FIXME: fillet this edge lines.addByTwoPoints(p1, p2) - p3 = p(x, nestingDepth + wallThiccness + 1 * SCALE) + p3 = p(x, nestingDepth + wallThickness + 1 * SCALE) lines.addByTwoPoints(p2, p3) lines.addByTwoPoints(p3, p0) return sketch.profiles.item(0) -def createLedgeSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: +def createLedgeSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion.Profile: angle = 54 sketch: adsk.fusion.Sketch = component.sketches.add(component.yZConstructionPlane) lines = sketch.sketchCurves.sketchLines - h = wallThiccness * 2 + h = wallThickness * 2 p = lambda x, y: createPoint(x, y , h) y = slotDimension * slotsHigh - p0 = p(wallThiccness, y) - p1 = p(wallThiccness + 16 * SCALE, y) + p0 = p(wallThickness, y) + p1 = p(wallThickness + 16 * SCALE, y) lines.addByTwoPoints(p0, p1) d = math.sin(angle * math.pi / 180) * 16 * SCALE - p2 = p(wallThiccness, y - d) + p2 = p(wallThickness, y - d) lines.addByTwoPoints(p1, p2) lines.addByTwoPoints(p2, p0) return sketch.profiles.item(0) -def createDividerSketch(component: adsk.fusion.Component, pos: float) -> adsk.fusion.Profile: +def createDividerSketch(component: adsk.fusion.Component, pos: float, slotsHigh, slotsDeep) -> adsk.fusion.Profile: sketch: adsk.fusion.Sketch = component.sketches.add(component.yZConstructionPlane) lines = sketch.sketchCurves.sketchLines p = lambda x, y: createPoint(x, y, pos) @@ -243,183 +253,380 @@ def createDividerSketch(component: adsk.fusion.Component, pos: float) -> adsk.fu lines.addByTwoPoints(p2, p3) lines.addByTwoPoints(p3, p0) - return sketch.profiles.item(0) - - + return sketch.profiles.item(0) + +class BoxCommandExecuteHandler(adsk.core.CommandEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + unitsMgr = app.activeProduct.unitsManager + command = args.firingEvent.sender + inputs = command.commandInputs + + box = Box() + for input in inputs: + if input.id == 'boxName': + box.boxName = input.value + elif input.id == 'slotsWide': + box.slotsWide = input.value + elif input.id == 'slotsDeep': + box.slotsDeep = input.value + elif input.id == 'slotsHigh': + box.slotsHigh = input.value + # ui.messageBox(str(box.slotsHigh)) + elif input.id == 'dividerCount': + box.dividerCount = input.value + elif input.id == 'includeScoop': + box.includeScoop = input.value + elif input.id == 'includeLedge': + box.includeLedge = input.value + + box.buildBox(); + + args.isValidResult = True + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class BoxCommandDestroyHandler(adsk.core.CommandEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + # when the command is done, terminate the script + # this will release all globals which will remove all event handlers + adsk.terminate() + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class BoxCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + cmd = args.command + cmd.isRepeatable = False + onExecute = BoxCommandExecuteHandler() + cmd.execute.add(onExecute) + onExecutePreview = BoxCommandExecuteHandler() + cmd.executePreview.add(onExecutePreview) + onDestroy = BoxCommandDestroyHandler() + cmd.destroy.add(onDestroy) + # keep the handler referenced beyond this function + handlers.append(onExecute) + handlers.append(onExecutePreview) + handlers.append(onDestroy) + + #define the inputs + inputs = cmd.commandInputs + inputs.addStringValueInput('boxName', 'Box Name', defaultBoxName) + + initSlotsWide = adsk.core.ValueInput.createByReal(defaultSlotsWide) + inputs.addIntegerSpinnerCommandInput('slotsWide', 'Slots Wide', 1, 20, 1, defaultSlotsWide) + + initSlotsDeep = adsk.core.ValueInput.createByReal(defaultSlotsDeep) + inputs.addIntegerSpinnerCommandInput('slotsDeep', 'Slots Deep', 1, 20, 1, defaultSlotsDeep) + + # I'd love to make this just MMs + initSlotsHigh = adsk.core.ValueInput.createByReal(defaultSlotsHigh) + inputs.addFloatSpinnerCommandInput('slotsHigh', 'Slots High', '', 0.1, 10.0, 0.01, defaultSlotsHigh) + + initDividerCount = adsk.core.ValueInput.createByReal(defaultDividerCount) + inputs.addIntegerSpinnerCommandInput('dividerCount', 'Divider Count', 0, 10, 1, defaultDividerCount) + + initIncludeScoop = adsk.core.ValueInput.createByReal(defaultIncludeScoop) + inputs.addBoolValueInput('includeScoop', 'Include Scoop?', True, '', defaultIncludeScoop) + + initIncludeLedge = adsk.core.ValueInput.createByReal(defaultIncludeLedge) + inputs.addBoolValueInput('includeLedge', 'Include Ledge?', True, '', defaultIncludeLedge) + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class Box: + def __init__(self): + self._boxName = defaultBoxName + self._slotsWide = defaultSlotsWide + self._slotsDeep = defaultSlotsDeep + self._slotsHigh = defaultSlotsHigh + self._dividerCount = defaultDividerCount + self._includeScoop = defaultIncludeScoop + self._includeLedge = defaultIncludeLedge + + #properties + @property + def boxName(self): + return self._boxName + @boxName.setter + def boxName(self, value): + self._boxName = value + + @property + def slotsWide(self): + return self._slotsWide + @slotsWide.setter + def slotsWide(self, value): + self._slotsWide = value + + @property + def slotsDeep(self): + return self._slotsDeep + @slotsDeep.setter + def slotsDeep(self, value): + self._slotsDeep = value + + @property + def slotsHigh(self): + return self._slotsHigh + @slotsHigh.setter + def slotsHigh(self, value): + self._slotsHigh = value + + @property + def dividerCount(self): + return self._dividerCount + @dividerCount.setter + def dividerCount(self, value): + self._dividerCount = value + + @property + def includeScoop(self): + return self._includeScoop + @includeScoop.setter + def includeScoop(self, value): + self._includeScoop = value + + @property + def includeLedge(self): + return self._includeLedge + @includeLedge.setter + def includeLedge(self, value): + self._includeLedge = value + + def buildBox(self): + try: + # Get the active design. + app = adsk.core.Application.get() + product = app.activeProduct + design = adsk.fusion.Design.cast(product) + ui = app.userInterface + + global component + + # Units + design.fusionUnitsManager.distanceDisplayUnits = adsk.fusion.DistanceUnits.MillimeterDistanceUnits + + # Main component + component = createComponent(design, self.boxName) + if component is None: + ui.messageBox('New component failed to create', 'New Component Failed') + return + + # Sketch base + base_rect_profile = createBaseRectSketch(component) + # Extrude + distance = createDistance(nestingDepth) + base = component.features.extrudeFeatures.addSimple(base_rect_profile, distance, NEW_BODY) + base_body = base.bodies.item(0) + base_body.name = "Base" + + # Magnet holes + magnet_holes_profile = createMagnetHolesSketch(component) + + # Extrude + distance = createDistance(magnetThickness) + component.features.extrudeFeatures.addSimple(magnet_holes_profile, distance, CUT) + + # Profile for the edge + edge_sketch = component.sketches.add(component.xYConstructionPlane) + lines = edge_sketch.sketchCurves.sketchLines + h = -slotDimension / 2 + p0 = createPoint(-1, 0, h) + p1a = createPoint(-1, nestingDepth, h) + lines.addByTwoPoints(p0, p1a) + p1 = createPoint(0, nestingDepth, h) + lines.addByTwoPoints(p1a, p1) + p2 = createPoint(nestingRimWidth + baseLip, 0, h) + lines.addByTwoPoints(p0, p2) + p3 = createPoint(nestingRimWidth, baseLip, h) + lines.addByTwoPoints(p2, p3) + p4 = createPoint(nestingRimWidth, nestingDepth - nestingRimWidth, h) + lines.addByTwoPoints(p1, p4) + lines.addByTwoPoints(p4, p3) + + # FIXME: this really offends me! + edge_profile = edge_sketch.profiles.item(0) + + # Path to sweep profile along (derived from baserect) + sweep_path_objects = createBaseSweepSketch(component) + sweep_path = component.features.createPath(sweep_path_objects) + + # Do the sweep + sweeps = component.features.sweepFeatures + sweep_input = sweeps.createInput(edge_profile, sweep_path, CUT) + sweep = sweeps.add(sweep_input) + + # Copy for the whole base + if self.slotsWide > 1 or self.slotsDeep > 1: + rectPattern(base_body, self.slotsWide, self.slotsDeep, slotDimension) + + # Now the box + box_path_objects, box_profile = createCurvedRect(component, self.slotsWide * slotDimension, self.slotsDeep * slotDimension, baseCornerRadius, nestingDepth) + distance = createDistance(self.slotsHigh * slotDimension - nestingDepth) + base = component.features.extrudeFeatures.addSimple(box_profile, distance, JOIN) + + # Rim + rim_profile = createRimSketch(component, self.slotsHigh) + box_path = component.features.createPath(box_path_objects) + sweep_input = sweeps.createInput(rim_profile, box_path, JOIN) + sweep = sweeps.add(sweep_input) + + # Put a fillet on the top edge + e = base_body.edges + top_edge = e.item(0) + for n in range(e.count): + edge = e.item(n) + bb = edge.boundingBox + mx = bb.maxPoint + mn = bb.minPoint + if mn.y > top_edge.boundingBox.minPoint.y: + #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') + top_edge = edge + fillet_edges = adsk.core.ObjectCollection.create() + fillet_edges.add(top_edge) + + fillets = component.features.filletFeatures + fillet_input = fillets.createInput() + fillet_radius = createDistance(.6 * SCALE) + fillet_input.addConstantRadiusEdgeSet(fillet_edges, fillet_radius, True) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + top_fillet = fillets.add(fillet_input) + + # Make the hole in the box + # Find the profile to extrude + f = base_body.faces + extrude_face = None + count = 0 + for n in range(f.count): + bb = f.item(n).boundingBox + mx = bb.maxPoint + mn = bb.minPoint + if close(mn.y, self.slotsHigh * slotDimension) and close(mx.y, self.slotsHigh * slotDimension): + #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') + count += 1 + extrude_face = f.item(n) + assert count == 1 + + # And extrude it + # FIXME: the + 1 * SCALE is ad hoc (but copied from the original) + distance = createDistance(-(self.slotsHigh * slotDimension - (nestingDepth + wallThickness + 1 * SCALE))) + component.features.extrudeFeatures.addSimple(extrude_face, distance, CUT) + + # Indent the lower part of the box to leave a rim around the top + indent_profile = createIndentSketch(component, self.slotsHigh) + sweep_input = sweeps.createInput(indent_profile, box_path, CUT) + sweep = sweeps.add(sweep_input) + + if self.includeLedge: + # Add the ledge + ledge_profile = createLedgeSketch(component, self.slotsHigh) + distance = createDistance(self.slotsWide * slotDimension - wallThickness * 4) + component.features.extrudeFeatures.addSimple(ledge_profile, distance, JOIN) + + if self.includeScoop: + # Add the curved scoop + edges = base_body.edges + ty = nestingDepth + wallThickness + 1 * SCALE + #print(ty) + fillet_edge = None + count = 0 + for n in range(edges.count): + edge = edges.item(n) + bb = edge.boundingBox + mx = bb.maxPoint + mn = bb.minPoint + # FIXME: where does -8.2354 come from? + if close(mn.y, ty) and close(mx.y, ty) and close(mn.x, baseCornerRadius) and close(mx.x, self.slotsWide * slotDimension - baseCornerRadius) and close(mn.z, -(self.slotsDeep * slotDimension - .1646)): + #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') + fillet_edge = edge + count += 1 + assert count == 1 + + fillet_input = fillets.createInput() + fillet_radius = createDistance(slotDimension * self.slotsHigh / 2) + edges = adsk.core.ObjectCollection.create() + edges.add(fillet_edge) + fillet_input.addConstantRadiusEdgeSet(edges, fillet_radius, False) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + fillets.add(fillet_input) + + # # Now we're a box. :-) + base_body.name = self.boxName + + # Finally, dividers + # FIXME: these end up not *quite* the same size + l = self.slotsWide * slotDimension - 2 * wallThickness + for n in range(self.dividerCount): + y = (n + 1) * l / (self.dividerCount + 1) + wallThickness / 2 + divider_profile = createDividerSketch(component, y, slotsHigh=self.slotsHigh, slotsDeep=self.slotsDeep) + distance = createDistance(wallThickness) + divider = component.features.extrudeFeatures.addSimple(divider_profile, distance, JOIN) + + edges = adsk.core.ObjectCollection.create() + #print(y) + for n in range(divider.faces.count): + f = divider.faces.item(n) + bb = f.boundingBox + mx = bb.maxPoint + mn = bb.minPoint + if (close(mn.x, y) and close(mx.x, y)) or (close(mn.x, y + wallThickness) and close(mx.x, y + wallThickness)): + #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') + for e in range(f.edges.count): + edges.add(f.edges.item(e)) + fillet_input = fillets.createInput() + fillet_radius = createDistance(.6 * SCALE) + fillet_input.addConstantRadiusEdgeSet(edges, fillet_radius, True) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + fillets.add(fillet_input) + except: + if ui: + ui.messageBox('Failed to compute the box. This is most likely because the input values define an invalid box.') + + def run(context): - # Get the active design. - app = adsk.core.Application.get() - product = app.activeProduct - design = adsk.fusion.Design.cast(product) - - # Units - design.fusionUnitsManager.distanceDisplayUnits = adsk.fusion.DistanceUnits.MillimeterDistanceUnits - - # Main component - component = createComponent(design, "Divider Box") - - # Sketch base - base_rect_profile = createBaseRectSketch(component) - # Extrude - distance = createDistance(nestingDepth) - base = component.features.extrudeFeatures.addSimple(base_rect_profile, distance, NEW_BODY) - base_body = base.bodies.item(0) - base_body.name = "Base" - - # Magnet holes - magnet_holes_profile = createMagnetHolesSketch(component) - # Extrude - distance = createDistance(magnetThiccness) - component.features.extrudeFeatures.addSimple(magnet_holes_profile, distance, CUT) - - # Profile for the edge - edge_sketch = component.sketches.add(component.xYConstructionPlane) - lines = edge_sketch.sketchCurves.sketchLines - h = -slotDimension / 2 - p0 = createPoint(-1, 0, h) - p1a = createPoint(-1, nestingDepth, h) - lines.addByTwoPoints(p0, p1a) - p1 = createPoint(0, nestingDepth, h) - lines.addByTwoPoints(p1a, p1) - p2 = createPoint(nestingRimWidth + baseLip, 0, h) - lines.addByTwoPoints(p0, p2) - p3 = createPoint(nestingRimWidth, baseLip, h) - lines.addByTwoPoints(p2, p3) - p4 = createPoint(nestingRimWidth, nestingDepth - nestingRimWidth, h) - lines.addByTwoPoints(p1, p4) - lines.addByTwoPoints(p4, p3) - # FIXME: this really offends me! - edge_profile = edge_sketch.profiles.item(0) - - # Path to sweep profile along (derived from baserect) - sweep_path_objects = createBaseSweepSketch(component) - sweep_path = component.features.createPath(sweep_path_objects) - - # Do the sweep - sweeps = component.features.sweepFeatures - sweep_input = sweeps.createInput(edge_profile, sweep_path, CUT) - sweep = sweeps.add(sweep_input) - - # Copy for the whole base - rectPattern(base_body, slotsWide, slotsDeep, slotDimension) - - # Now the box - box_path_objects, box_profile = createCurvedRect(component, slotsWide * slotDimension, slotsDeep * slotDimension, baseCornerRadius, nestingDepth) - distance = createDistance(slotsHigh * slotDimension - nestingDepth) - base = component.features.extrudeFeatures.addSimple(box_profile, distance, JOIN) - - # Rim - rim_profile = createRimSketch(component) - box_path = component.features.createPath(box_path_objects) - sweep_input = sweeps.createInput(rim_profile, box_path, JOIN) - sweep = sweeps.add(sweep_input) - - # Put a fillet on the top edge - e = base_body.edges - top_edge = e.item(0) - for n in range(e.count): - edge = e.item(n) - bb = edge.boundingBox - mx = bb.maxPoint - mn = bb.minPoint - if mn.y > top_edge.boundingBox.minPoint.y: - #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') - top_edge = edge - fillet_edges = adsk.core.ObjectCollection.create() - fillet_edges.add(top_edge) - - fillets = component.features.filletFeatures - fillet_input = fillets.createInput() - fillet_radius = createDistance(.6 * SCALE) - fillet_input.addConstantRadiusEdgeSet(fillet_edges, fillet_radius, True) - fillet_input.isG2 = False - fillet_input.isRollingBallCorner = True - top_fillet = fillets.add(fillet_input) - - # Make the hole in the box - # Find the profile to extrude - f = base_body.faces - extrude_face = None - count = 0 - for n in range(f.count): - bb = f.item(n).boundingBox - mx = bb.maxPoint - mn = bb.minPoint - if close(mn.y, slotsHigh * slotDimension) and close(mx.y, slotsHigh * slotDimension): - #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') - count += 1 - extrude_face = f.item(n) - assert count == 1 - - # And extrude it - # FIXME: the + 1 * SCALE is ad hoc (but copied from the original) - distance = createDistance(-(slotsHigh * slotDimension - (nestingDepth + wallThiccness + 1 * SCALE))) - component.features.extrudeFeatures.addSimple(extrude_face, distance, CUT) - - # Indent the lower part of the box to leave a rim around the top - indent_profile = createIndentSketch(component) - sweep_input = sweeps.createInput(indent_profile, box_path, CUT) - sweep = sweeps.add(sweep_input) - - # Add the ledge - ledge_profile = createLedgeSketch(component) - distance = createDistance(slotsWide * slotDimension - wallThiccness * 4) - component.features.extrudeFeatures.addSimple(ledge_profile, distance, JOIN) - - # Add the curved scoop - edges = base_body.edges - ty = nestingDepth + wallThiccness + 1 * SCALE - #print(ty) - fillet_edge = None - count = 0 - for n in range(edges.count): - edge = edges.item(n) - bb = edge.boundingBox - mx = bb.maxPoint - mn = bb.minPoint - # FIXME: where does -8.2354 come from? - if close(mn.y, ty) and close(mx.y, ty) and close(mn.x, baseCornerRadius) and close(mx.x, slotsWide * slotDimension - baseCornerRadius) and close(mn.z, -(slotsDeep * slotDimension - .1646)): - #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') - fillet_edge = edge - count += 1 - assert count == 1 - - fillet_input = fillets.createInput() - fillet_radius = createDistance(slotDimension * slotsHigh / 2) - edges = adsk.core.ObjectCollection.create() - edges.add(fillet_edge) - fillet_input.addConstantRadiusEdgeSet(edges, fillet_radius, False) - fillet_input.isG2 = False - fillet_input.isRollingBallCorner = True - fillets.add(fillet_input) - - - # Now we're a box. :-) - base_body.name = "Box" - - # Finally, dividers - # FIXME: these end up not *quite* the same size - l = slotsWide * slotDimension - 2 * wallThiccness - for n in range(dividerCount): - y = (n + 1) * l / (dividerCount + 1) + wallThiccness / 2 - divider_profile = createDividerSketch(component, y) - distance = createDistance(wallThiccness) - divider = component.features.extrudeFeatures.addSimple(divider_profile, distance, JOIN) - - edges = adsk.core.ObjectCollection.create() - #print(y) - for n in range(divider.faces.count): - f = divider.faces.item(n) - bb = f.boundingBox - mx = bb.maxPoint - mn = bb.minPoint - if (close(mn.x, y) and close(mx.x, y)) or (close(mn.x, y + wallThiccness) and close(mx.x, y + wallThiccness)): - #print(f'[{mn.x}, {mn.y}, {mn.z}] -> [{mx.x}, {mx.y}, {mx.z}]') - for e in range(f.edges.count): - edges.add(f.edges.item(e)) - fillet_input = fillets.createInput() - fillet_radius = createDistance(.6 * SCALE) - fillet_input.addConstantRadiusEdgeSet(edges, fillet_radius, True) - fillet_input.isG2 = False - fillet_input.isRollingBallCorner = True - fillets.add(fillet_input) + try: + product = app.activeProduct + design = adsk.fusion.Design.cast(product) + if not design: + ui.messageBox('It is not supported in current workspace, please change to MODEL workspace and try again.') + return + commandDefinitions = ui.commandDefinitions + + #check the command exists or not + # + cmdDef = commandDefinitions.itemById('GridfinityDividerBox') + if not cmdDef: + cmdDef = commandDefinitions.addButtonDefinition('GridfinityBox', + 'Create a Gridfinity Box', + 'Create a Gridfinity Box.') + + onCommandCreated = BoxCommandCreatedHandler() + cmdDef.commandCreated.add(onCommandCreated) + # keep the handler referenced beyond this function + handlers.append(onCommandCreated) + inputs = adsk.core.NamedValues.create() + + cmdDef.execute(inputs) + + # prevent this module from being terminate when the script returns, because we are waiting for event handlers to fire + adsk.autoTerminate(False) + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) From 533dd8de16e4cccb73472024687176d761bd305f Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Wed, 1 Feb 2023 10:53:42 -0500 Subject: [PATCH 02/10] also make the magnet holes optional --- .../GridFinityDividerBoxMaker.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index 434bcc8..587806d 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -12,6 +12,7 @@ defaultDividerCount = 0 defaultIncludeScoop = True defaultIncludeLedge = True +defaultIncludeMagnets = True # global set of event handlers to keep them referenced for the duration of the command handlers = [] @@ -281,6 +282,8 @@ def notify(self, args): box.includeScoop = input.value elif input.id == 'includeLedge': box.includeLedge = input.value + elif input.id == 'includeMagnets': + box.includeMagnets = input.value box.buildBox(); @@ -343,6 +346,9 @@ def notify(self, args): initIncludeLedge = adsk.core.ValueInput.createByReal(defaultIncludeLedge) inputs.addBoolValueInput('includeLedge', 'Include Ledge?', True, '', defaultIncludeLedge) + initIncludeMagnets = adsk.core.ValueInput.createByReal(defaultIncludeMagnets) + inputs.addBoolValueInput('includeMagnets', 'Include Magnets?', True, '', defaultIncludeMagnets) + except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) @@ -356,6 +362,7 @@ def __init__(self): self._dividerCount = defaultDividerCount self._includeScoop = defaultIncludeScoop self._includeLedge = defaultIncludeLedge + self._includeMagnets = defaultIncludeMagnets #properties @property @@ -407,6 +414,13 @@ def includeLedge(self): def includeLedge(self, value): self._includeLedge = value + @property + def includeMagnets(self): + return self._includeMagnets + @includeMagnets.setter + def includeMagnets(self, value): + self._includeMagnets = value + def buildBox(self): try: # Get the active design. @@ -434,12 +448,13 @@ def buildBox(self): base_body = base.bodies.item(0) base_body.name = "Base" - # Magnet holes - magnet_holes_profile = createMagnetHolesSketch(component) + if self.includeMagnets: + # Magnet hole sketch + magnet_holes_profile = createMagnetHolesSketch(component) - # Extrude - distance = createDistance(magnetThickness) - component.features.extrudeFeatures.addSimple(magnet_holes_profile, distance, CUT) + # Extrude for magnets + distance = createDistance(magnetThickness) + component.features.extrudeFeatures.addSimple(magnet_holes_profile, distance, CUT) # Profile for the edge edge_sketch = component.sketches.add(component.xYConstructionPlane) From 4bb9bca3712278683520c661d518fd22bccbcbbd Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Wed, 1 Feb 2023 11:54:50 -0500 Subject: [PATCH 03/10] name the sketches --- .../GridFinityDividerBoxMaker.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index 587806d..a7c0500 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -73,6 +73,7 @@ def close(a, b): def createBaseRectSketch(component: adsk.fusion.Component) -> adsk.fusion.Profile: base_sketch = component.sketches.add(component.xZConstructionPlane) + base_sketch.name = "Base Sketch" p0 = create2DPoint(0, 0) p1 = create2DPoint(slotDimension, slotDimension) base_rect = base_sketch.sketchCurves.sketchLines.addTwoPointRectangle(p0, p1) @@ -82,7 +83,7 @@ def createBaseRectSketch(component: adsk.fusion.Component) -> adsk.fusion.Profil def createMagnetHolesSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) - + sketch.name = "Magnet Holes Sketch" sketch.sketchCurves.sketchCircles.addByCenterRadius(createPoint(holeOffset, holeOffset, 0), magnetDiameter / 2.) sketch.sketchCurves.sketchCircles.addByCenterRadius(createPoint(slotDimension - holeOffset, holeOffset, 0), magnetDiameter / 2.) sketch.sketchCurves.sketchCircles.addByCenterRadius(createPoint(slotDimension - holeOffset, slotDimension - holeOffset, 0), magnetDiameter / 2.) @@ -94,9 +95,10 @@ def createMagnetHolesSketch(component: adsk.fusion.Component) -> adsk.core.Objec return circles -def createCurvedRect(component: adsk.fusion.Component, width: float, depth: float, radius: float, z: float) -> Tuple[adsk.core.ObjectCollection, adsk.fusion.Profile]: +def createCurvedRect(component: adsk.fusion.Component, name, width: float, depth: float, radius: float, z: float) -> Tuple[adsk.core.ObjectCollection, adsk.fusion.Profile]: path = adsk.core.ObjectCollection.create() sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) + sketch.name = name lines = sketch.sketchCurves.sketchLines p = lambda x, y: createPoint(x, y , z) @@ -149,7 +151,7 @@ def createCurvedRect(component: adsk.fusion.Component, width: float, depth: floa def createBaseSweepSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: - b, _ = createCurvedRect(component, slotDimension, slotDimension, baseCornerRadius, 0) + b, _ = createCurvedRect(component, "Base Sweep Sketch", slotDimension, slotDimension, baseCornerRadius, 0) return b # Note that this attempts to combins the new bodies with the original to give a single body - this only works if they touch @@ -184,6 +186,7 @@ def rectPattern(body: adsk.fusion.BRepBody, wide: int, deep: int, dim: float) -> def createRimSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion.Profile: cornerVerticalOffset = nestingClearance * .416 # Empirically determined from existing sketch sketch = component.sketches.add(component.xYConstructionPlane) + sketch.name = "Rim Sketch" lines = sketch.sketchCurves.sketchLines h = -slotDimension / 2 p = lambda x, y: createPoint(x, y , h) @@ -203,6 +206,7 @@ def createRimSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion. def createIndentSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusion.Profile: sketch = component.sketches.add(component.xYConstructionPlane) + sketch.name = "Indent Sketch" lines = sketch.sketchCurves.sketchLines h = -slotDimension / 2 p = lambda x, y: createPoint(x, y , h) @@ -225,6 +229,7 @@ def createLedgeSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusio angle = 54 sketch: adsk.fusion.Sketch = component.sketches.add(component.yZConstructionPlane) + sketch.name = "Ledge Sketch" lines = sketch.sketchCurves.sketchLines h = wallThickness * 2 p = lambda x, y: createPoint(x, y , h) @@ -458,6 +463,7 @@ def buildBox(self): # Profile for the edge edge_sketch = component.sketches.add(component.xYConstructionPlane) + edge_sketch.name = "Edge Profile Sketch" lines = edge_sketch.sketchCurves.sketchLines h = -slotDimension / 2 p0 = createPoint(-1, 0, h) @@ -480,7 +486,7 @@ def buildBox(self): sweep_path_objects = createBaseSweepSketch(component) sweep_path = component.features.createPath(sweep_path_objects) - # Do the sweep + # Do the sweeps sweeps = component.features.sweepFeatures sweep_input = sweeps.createInput(edge_profile, sweep_path, CUT) sweep = sweeps.add(sweep_input) @@ -490,7 +496,7 @@ def buildBox(self): rectPattern(base_body, self.slotsWide, self.slotsDeep, slotDimension) # Now the box - box_path_objects, box_profile = createCurvedRect(component, self.slotsWide * slotDimension, self.slotsDeep * slotDimension, baseCornerRadius, nestingDepth) + box_path_objects, box_profile = createCurvedRect(component, "Box Profile", self.slotsWide * slotDimension, self.slotsDeep * slotDimension, baseCornerRadius, nestingDepth) distance = createDistance(self.slotsHigh * slotDimension - nestingDepth) base = component.features.extrudeFeatures.addSimple(box_profile, distance, JOIN) @@ -628,7 +634,7 @@ def run(context): # cmdDef = commandDefinitions.itemById('GridfinityDividerBox') if not cmdDef: - cmdDef = commandDefinitions.addButtonDefinition('GridfinityBox', + cmdDef = commandDefinitions.addButtonDefinition('GridfinityDividerBox', 'Create a Gridfinity Box', 'Create a Gridfinity Box.') From 8ab7cdb3a00d8bc83d7946bfdedb2266021ab47b Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 7 Feb 2023 10:21:26 -0500 Subject: [PATCH 04/10] Ledge should go all of the way to the sides --- GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index a7c0500..20ea5bc 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -231,7 +231,7 @@ def createLedgeSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusio sketch: adsk.fusion.Sketch = component.sketches.add(component.yZConstructionPlane) sketch.name = "Ledge Sketch" lines = sketch.sketchCurves.sketchLines - h = wallThickness * 2 + h = wallThickness p = lambda x, y: createPoint(x, y , h) y = slotDimension * slotsHigh @@ -244,7 +244,7 @@ def createLedgeSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusio lines.addByTwoPoints(p2, p0) return sketch.profiles.item(0) - + def createDividerSketch(component: adsk.fusion.Component, pos: float, slotsHigh, slotsDeep) -> adsk.fusion.Profile: sketch: adsk.fusion.Sketch = component.sketches.add(component.yZConstructionPlane) lines = sketch.sketchCurves.sketchLines From b1fc544d901f3b188f6db5af13e2133573309c9b Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 7 Feb 2023 20:46:14 -0500 Subject: [PATCH 05/10] did not extrude enough --- GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index 20ea5bc..f533295 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -29,7 +29,6 @@ magnetThickness = 2.5 * SCALE # Sizes! -# FIXME: make these strings, e.g. "42 mm" (maybe, some contexts that's not right for) baseCornerRadius = 4 * SCALE baseLip = .8 * SCALE slotDimension = 42 * SCALE @@ -38,6 +37,7 @@ nestingClearance = .25 * SCALE wallThickness = 1.2 * SCALE holeOffset = 8 * SCALE +ledgeOffset = 0.2 * SCALE # Derived sizes nestingVerticalClearance = nestingClearance * 1.416 # Empirically determined from original sketch @@ -234,7 +234,7 @@ def createLedgeSketch(component: adsk.fusion.Component, slotsHigh) -> adsk.fusio h = wallThickness p = lambda x, y: createPoint(x, y , h) - y = slotDimension * slotsHigh + y = slotDimension * slotsHigh - ledgeOffset p0 = p(wallThickness, y) p1 = p(wallThickness + 16 * SCALE, y) lines.addByTwoPoints(p0, p1) @@ -253,9 +253,9 @@ def createDividerSketch(component: adsk.fusion.Component, pos: float, slotsHigh, p0 = p(0, nestingDepth) p1 = p(slotsDeep * slotDimension, nestingDepth) lines.addByTwoPoints(p0, p1) - p2 = p(slotsDeep * slotDimension, slotsHigh * slotDimension) + p2 = p(slotsDeep * slotDimension, slotsHigh * slotDimension - ledgeOffset) lines.addByTwoPoints(p1, p2) - p3 = p(0, slotsHigh * slotDimension) + p3 = p(0, slotsHigh * slotDimension - ledgeOffset) lines.addByTwoPoints(p2, p3) lines.addByTwoPoints(p3, p0) @@ -556,7 +556,7 @@ def buildBox(self): if self.includeLedge: # Add the ledge ledge_profile = createLedgeSketch(component, self.slotsHigh) - distance = createDistance(self.slotsWide * slotDimension - wallThickness * 4) + distance = createDistance(self.slotsWide * slotDimension - wallThickness * 2) component.features.extrudeFeatures.addSimple(ledge_profile, distance, JOIN) if self.includeScoop: From 5dd93b0ddf8e02eb8ae27694055317e732409ad6 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Fri, 23 Jun 2023 16:18:43 -0400 Subject: [PATCH 06/10] checkpoint, getting close for the holster --- .../GridFinityDividerBoxMaker.py | 41 +- RemoteHolster/RemoteHolsterMaker.py | 517 ++++++++++++++++++ copy-to-scripts-dir.sh | 16 + 3 files changed, 568 insertions(+), 6 deletions(-) create mode 100755 RemoteHolster/RemoteHolsterMaker.py create mode 100755 copy-to-scripts-dir.sh diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py index f533295..63cf495 100755 --- a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py @@ -5,14 +5,25 @@ from typing import Tuple import adsk.core, adsk.fusion, adsk.cam, traceback +# Foot is 6mm high +# defaultBoxName = "Box" defaultSlotsWide = 2 defaultSlotsDeep = 2 -defaultSlotsHigh = 1 + +# 52" Husky case +# +defaultSlotsHigh = 1.45 + +# 62" Husky case +# +# defaultSlotsHigh = 1.25 + defaultDividerCount = 0 +defaultBaseOnly = False defaultIncludeScoop = True defaultIncludeLedge = True -defaultIncludeMagnets = True +defaultIncludeMagnets = False # global set of event handlers to keep them referenced for the duration of the command handlers = [] @@ -25,8 +36,8 @@ # Why? SCALE = 0.1 -magnetDiameter = 6.5 * SCALE -magnetThickness = 2.5 * SCALE +magnetDiameter = 6.5 * SCALE * 0.75 +magnetThickness = 2.5 * SCALE * 0.75 # Sizes! baseCornerRadius = 4 * SCALE @@ -285,6 +296,8 @@ def notify(self, args): box.dividerCount = input.value elif input.id == 'includeScoop': box.includeScoop = input.value + elif input.id == 'baseOnly': + box.baseOnly = input.value elif input.id == 'includeLedge': box.includeLedge = input.value elif input.id == 'includeMagnets': @@ -340,7 +353,7 @@ def notify(self, args): # I'd love to make this just MMs initSlotsHigh = adsk.core.ValueInput.createByReal(defaultSlotsHigh) - inputs.addFloatSpinnerCommandInput('slotsHigh', 'Slots High', '', 0.1, 10.0, 0.01, defaultSlotsHigh) + inputs.addFloatSpinnerCommandInput('slotsHigh', 'Slots High', '', 0.25, 10.0, 0.01, defaultSlotsHigh) initDividerCount = adsk.core.ValueInput.createByReal(defaultDividerCount) inputs.addIntegerSpinnerCommandInput('dividerCount', 'Divider Count', 0, 10, 1, defaultDividerCount) @@ -348,6 +361,9 @@ def notify(self, args): initIncludeScoop = adsk.core.ValueInput.createByReal(defaultIncludeScoop) inputs.addBoolValueInput('includeScoop', 'Include Scoop?', True, '', defaultIncludeScoop) + initBaseOnly = adsk.core.ValueInput.createByReal(defaultBaseOnly) + inputs.addBoolValueInput('baseOnly', 'Base Only?', True, '', defaultBaseOnly) + initIncludeLedge = adsk.core.ValueInput.createByReal(defaultIncludeLedge) inputs.addBoolValueInput('includeLedge', 'Include Ledge?', True, '', defaultIncludeLedge) @@ -366,6 +382,7 @@ def __init__(self): self._slotsHigh = defaultSlotsHigh self._dividerCount = defaultDividerCount self._includeScoop = defaultIncludeScoop + self._baseOnly = defaultBaseOnly self._includeLedge = defaultIncludeLedge self._includeMagnets = defaultIncludeMagnets @@ -412,6 +429,13 @@ def includeScoop(self): def includeScoop(self, value): self._includeScoop = value + @property + def baseOnly(self): + return self._baseOnly + @baseOnly.setter + def baseOnly(self, value): + self._baseOnly = value + @property def includeLedge(self): return self._includeLedge @@ -497,6 +521,11 @@ def buildBox(self): # Now the box box_path_objects, box_profile = createCurvedRect(component, "Box Profile", self.slotsWide * slotDimension, self.slotsDeep * slotDimension, baseCornerRadius, nestingDepth) + if self.baseOnly: + distance = createDistance(1*SCALE) + base = component.features.extrudeFeatures.addSimple(box_profile, distance, JOIN) + return + distance = createDistance(self.slotsHigh * slotDimension - nestingDepth) base = component.features.extrudeFeatures.addSimple(box_profile, distance, JOIN) @@ -553,7 +582,7 @@ def buildBox(self): sweep_input = sweeps.createInput(indent_profile, box_path, CUT) sweep = sweeps.add(sweep_input) - if self.includeLedge: + if self.includeLedge and self.slotsHigh >= 0.43 : # Add the ledge ledge_profile = createLedgeSketch(component, self.slotsHigh) distance = createDistance(self.slotsWide * slotDimension - wallThickness * 2) diff --git a/RemoteHolster/RemoteHolsterMaker.py b/RemoteHolster/RemoteHolsterMaker.py new file mode 100755 index 0000000..209c36c --- /dev/null +++ b/RemoteHolster/RemoteHolsterMaker.py @@ -0,0 +1,517 @@ +# Author- Jamie Smith +# Description- Make a remote holster +# +import math +from typing import Tuple +import adsk.core, adsk.fusion, adsk.cam, traceback + +# Some Constants +# +# Don't know why this is here +SCALE = 0.1 + +# Default Values +# +defaultHolsterName = "Holster" +defaultRemoteWidth = 80.0 +defaultRemoteLength = 44.0 +defaultRemoteThickness = 15.0 +defaultFrontSlotWidth = 10.0 +defaultFrontHeight = 22.0 +defaultBackCornerRound = 4.0 +defaultFillet = 0.5 +defaultFrontSlotRound = 3.0 +defaultSideThickness = 3.0 +defaultBackThickness = 3.0 +defaultBottomThickness = 3.0 +defaultTolerance = 0.5 + +# Actual values +holsterName = defaultHolsterName +remoteWidth = defaultRemoteWidth * SCALE +remoteLength = defaultRemoteLength * SCALE +remoteThickness = defaultRemoteThickness * SCALE +frontSlotWidth = defaultFrontSlotWidth * SCALE +frontHeight = defaultFrontHeight * SCALE +backCornerRound = defaultBackCornerRound * SCALE +fillet = defaultFillet * SCALE +frontSlotRound = defaultFrontSlotRound * SCALE +sideThickness = defaultSideThickness * SCALE +backThickness = defaultBackThickness * SCALE +bottomThickness = defaultBottomThickness * SCALE +tolerance = defaultTolerance * SCALE + +# global set of event handlers to keep them referenced for the duration of the command +handlers = [] +app = adsk.core.Application.get() +if app: + ui = app.userInterface + +newComp = None + +# Consts +CUT = adsk.fusion.FeatureOperations.CutFeatureOperation +JOIN = adsk.fusion.FeatureOperations.JoinFeatureOperation +NEW_BODY = adsk.fusion.FeatureOperations.NewBodyFeatureOperation + +def createComponent(design: adsk.fusion.Design, name: str) -> adsk.fusion.Component: + rootComp = design.rootComponent + allOccs = rootComp.occurrences + newOcc = allOccs.addNewComponent(adsk.core.Matrix3D.create()) + comp = newOcc.component + comp.name = name + return comp + +def createPoint(x: float, y: float, z: float) -> adsk.core.Point3D: + return adsk.core.Point3D.create(x, y, z) + +def create2DPoint(x, y): + return createPoint(x, y, 0) + +def createDistance(d) -> adsk.core.ValueInput: + return adsk.core.ValueInput.createByReal(d) + +def createReal(r) -> adsk.core.ValueInput: + return adsk.core.ValueInput.createByReal(r) + +def close(a, b): + return abs(a - b) < 1e-5 * SCALE + +# Some basic utility commands +# +def createBaseRectSketch(component: adsk.fusion.Component, holster) -> adsk.fusion.Profile: + base_sketch = component.sketches.add(component.xYConstructionPlane) + base_sketch.name = "Base Sketch" + p0 = create2DPoint(0, 0) + p1 = create2DPoint((holster.remoteWidth + 2 * holster.sideThickness) * SCALE, (holster.remoteThickness + holster.sideThickness + holster.backThickness) * SCALE) + base_rect = base_sketch.sketchCurves.sketchLines.addTwoPointRectangle(p0, p1) + # FIXME: there must be a better way! + base_rect_profile = base_sketch.profiles.item(0) + return base_rect_profile + +def createCurvedRect(component: adsk.fusion.Component, name, width: float, depth: float, radius: float, z: float) -> Tuple[adsk.core.ObjectCollection, adsk.fusion.Profile]: + path = adsk.core.ObjectCollection.create() + sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) + sketch.name = name + lines = sketch.sketchCurves.sketchLines + p = lambda x, y: createPoint(x, y , z) + + p0 = p(radius, 0) + p1 = p(width - radius, 0) + l0 = lines.addByTwoPoints(p0, p1) + p2 = p(width, radius) + p3 = p(width, depth - radius) + l1 = lines.addByTwoPoints(p2, p3) + p4 = p(width - radius, depth) + p5 = p(radius, depth) + l2 = lines.addByTwoPoints(p4, p5) + p6 = p(0, depth - radius) + p7 = p(0, radius) + l3 = lines.addByTwoPoints(p6, p7) + + arcs = sketch.sketchCurves.sketchArcs + + p7c = p(radius, radius) + a0 = arcs.addByCenterStartSweep(p7c, p7, math.pi / 2) + # FIXME: you'd've thought all these merges made a single thing. You'd be wrong. + # The arcs end up joined to the lines they were started on but the merges do nothing. + #a0.endSketchPoint.merge(l0.startSketchPoint) + #path.add(a0) + p1c = p(width - radius, radius) + a1 = arcs.addByCenterStartSweep(p1c, p1, math.pi / 2) + #a1.endSketchPoint.merge(l1.startSketchPoint) + #path.add(a1) + p3c = p(width - radius, depth - radius) + a2 = arcs.addByCenterStartSweep(p3c, p3, math.pi / 2) + #a2.endSketchPoint.merge(l2.startSketchPoint) + #path.add(a2) + p5c = p(radius, depth - radius) + a3 = arcs.addByCenterStartSweep(p5c, p5, math.pi / 2) + #a3.endSketchPoint.merge(l3.startSketchPoint) + #path.add(a3) + + # These have to be in exactly the right order + path.add(l3) + path.add(a3) + path.add(l2) + path.add(a2) + path.add(l1) + path.add(a1) + path.add(l0) + path.add(a0) + + # FIXME: not actually a path + return path, sketch.profiles.item(0) + +def createBaseSweepSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: + b, _ = createCurvedRect(component, "Base Sweep Sketch", slotDimension, slotDimension, baseCornerRadius, 0) + return b + +class HolsterCommandExecuteHandler(adsk.core.CommandEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + unitsMgr = app.activeProduct.unitsManager + command = args.firingEvent.sender + inputs = command.commandInputs + + holster = Holster() + + for input in inputs: + if input.id == 'holsterName': + holster.holsterName = input.value + elif input.id == 'remoteWidth': + holster.remoteWidth = input.value + elif input.id == 'remoteLength': + holster.remoteLength = input.value + elif input.id == 'remoteThickness': + holster.remoteThickness = input.value + elif input.id == 'frontSlotWidth': + holster.frontSlotWidth = input.value + elif input.id == 'frontHeight': + holster.frontHeight = input.value + elif input.id == 'backCornerRound': + holster.backCornerRound = input.value + elif input.id == 'fillet': + holster.fillet = input.value + elif input.id == 'frontSlotRound': + holster.frontSlotRound = input.value + elif input.id == 'sideThickness': + holster.sideThickness = input.value + elif input.id == 'backThickness': + holster.backThickness = input.value + elif input.id == 'bottomThickness': + holster.bottomThickness = input.value + elif input.id == 'tolerance': + holster.tolerance = input.value + + holster.buildHolster(); + + args.isValidResult = True + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class HolsterCommandDestroyHandler(adsk.core.CommandEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + # when the command is done, terminate the script + # this will release all globals which will remove all event handlers + adsk.terminate() + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class HolsterCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): + def __init__(self): + super().__init__() + def notify(self, args): + try: + cmd = args.command + cmd.isRepeatable = False + onExecute = HolsterCommandExecuteHandler() + cmd.execute.add(onExecute) + onExecutePreview = HolsterCommandExecuteHandler() + cmd.executePreview.add(onExecutePreview) + onDestroy = HolsterCommandDestroyHandler() + cmd.destroy.add(onDestroy) + # keep the handler referenced beyond this function + handlers.append(onExecute) + handlers.append(onExecutePreview) + handlers.append(onDestroy) + + #define the inputs + inputs = cmd.commandInputs + + finestIncrement = 1.0 + + # JRS: These values / ranges are not set + # + inputs.addStringValueInput('holsterName', 'Holster Name', defaultHolsterName) + + initRemoteWidth = adsk.core.ValueInput.createByReal(defaultRemoteWidth) + inputs.addFloatSpinnerCommandInput('remoteWidth', 'Remote Width', '', 0.25, 100.0, finestIncrement, defaultRemoteWidth) + + initremoteLength = adsk.core.ValueInput.createByReal(defaultRemoteLength) + inputs.addFloatSpinnerCommandInput('remoteLength', 'Remote Height', '', 0.25, 100.0, finestIncrement, defaultRemoteLength) + + initRemoteThickness = adsk.core.ValueInput.createByReal(defaultRemoteThickness) + inputs.addFloatSpinnerCommandInput('remoteThickness', 'Remote Thickness', '', 0.25, 100.0, finestIncrement, defaultRemoteThickness) + + initFrontHeight = adsk.core.ValueInput.createByReal(defaultFrontHeight) + inputs.addFloatSpinnerCommandInput('frontHeight', 'Front Height', '', 0.25, 100.0, finestIncrement, defaultFrontHeight) + + initFrontSlotWidth = adsk.core.ValueInput.createByReal(defaultFrontSlotWidth) + inputs.addFloatSpinnerCommandInput('frontSlotWidth', 'Front Slot Width', '', 0.25, 100.0, finestIncrement, defaultFrontSlotWidth) + + initBackCornerRound = adsk.core.ValueInput.createByReal(defaultBackCornerRound) + inputs.addFloatSpinnerCommandInput('backCornerRound', 'Back Corner Round', '', 0.25, 100.0, finestIncrement, defaultBackCornerRound) + + initFillet = adsk.core.ValueInput.createByReal(defaultFillet) + inputs.addFloatSpinnerCommandInput('fillet', 'Overall Fillet', '', 0.25, 100.0, finestIncrement, defaultFillet) + + initFrontSlotRound = adsk.core.ValueInput.createByReal(defaultFrontSlotRound) + inputs.addFloatSpinnerCommandInput('frontSlotRound', 'Front Slot Round', '', 0.25, 100.0, finestIncrement, defaultFrontSlotRound) + + initSideThickness = adsk.core.ValueInput.createByReal(defaultSideThickness) + inputs.addFloatSpinnerCommandInput('sideThickness', 'Side Thickness', '', 0.25, 100.0, finestIncrement, defaultSideThickness) + + initBackThickness = adsk.core.ValueInput.createByReal(defaultBackThickness) + inputs.addFloatSpinnerCommandInput('backThickness', 'Back Thickness', '', 0.25, 100.0, finestIncrement, defaultBackThickness) + + initBottomThickness = adsk.core.ValueInput.createByReal(defaultBottomThickness) + inputs.addFloatSpinnerCommandInput('bottomThickness', 'Bottom Thickness', '', 0.25, 100.0, finestIncrement, defaultBottomThickness) + + initTolerance = adsk.core.ValueInput.createByReal(defaultTolerance) + inputs.addFloatSpinnerCommandInput('tolerance', 'Tolerance', '', 0.25, 100.0, finestIncrement, defaultTolerance) + + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +class Holster: + def __init__(self): + self._holsterName = defaultHolsterName + self._remoteWidth = defaultRemoteWidth + self._remoteLength = defaultRemoteLength + self._remoteThickness = defaultRemoteThickness + self._frontSlotWidth = defaultFrontSlotWidth + self._frontHeight = defaultFrontHeight + self._backCornerRound = defaultBackCornerRound + self._fillet = defaultFillet + self._frontSlotRound = defaultFrontSlotRound + self._sideThickness = defaultSideThickness + self._backThickness = defaultBackThickness + self._bottomThickness = defaultBottomThickness + self._tolerance = defaultTolerance + + # properties + # + @property + def holsterName(self): + return self._holsterName + @holsterName.setter + def holsterName(self, value): + self._holsterName = value + + @property + def remoteWidth(self): + return self._remoteWidth + @remoteWidth.setter + def remoteWidth(self, value): + self._remoteWidth = value + + @property + def remoteLength(self): + return self._remoteLength + @remoteLength.setter + def remoteLength(self, value): + self._remoteLength = value + + @property + def remoteThickness(self): + return self._remoteThickness + @remoteThickness.setter + def remoteThickness(self, value): + self._remoteThickness = value + + @property + def frontSlotWidth(self): + return self._frontSlotWidth + @frontSlotWidth.setter + def frontSlotWidth(self, value): + self._frontSlotWidth = value + + @property + def frontHeight(self): + return self._frontHeight + @frontHeight.setter + def frontHeight(self, value): + self._frontHeight = value + + @property + def backCornerRound(self): + return self._backCornerRound + @backCornerRound.setter + def backCornerRound(self, value): + self._backCornerRound = value + + @property + def fillet(self): + return self._fillet + @fillet.setter + def fillet(self, value): + self._fillet = value + + @property + def frontSlotRound(self): + return self._frontSlotRound + @frontSlotRound.setter + def frontSlotRound(self, value): + self._frontSlotRound = value + + @property + def sideThickness(self): + return self._sideThickness + @sideThickness.setter + def sideThickness(self, value): + self._sideThickness = value + + @property + def backThickness(self): + return self._backThickness + @backThickness.setter + def backThickness(self, value): + self._backThickness = value + + @property + def bottomThickness(self): + return self._bottomThickness + @bottomThickness.setter + def bottomThickness(self, value): + self._bottomThickness = value + + @property + def tolerance(self): + return self._tolerance + @tolerance.setter + def tolerance(self, value): + self._tolerance = value + + + def buildHolster(self): + try: + # Get the active design. + app = adsk.core.Application.get() + product = app.activeProduct + design = adsk.fusion.Design.cast(product) + ui = app.userInterface + + global component + + # Units + design.fusionUnitsManager.distanceDisplayUnits = adsk.fusion.DistanceUnits.MillimeterDistanceUnits + + # Main component + component = createComponent(design, self.holsterName) + + if component is None: + ui.messageBox('New component failed to create', 'New Component Failed') + return + + # This is the flow of how I built the holster by hand + # + # 1) create the base rectangle on the XY plane, (remoteWidth + 2*sideThickness) x (remoteThickness + sideThickness + backThickness) + # 2) extrude that rectangle to the remoteLength + bottomThickness + # 3) add rectangle of side thickness (OK, not exactly- could have a different back thickness) + # 4) extrude that offset to remoteLength (leaving BottomThickness) + # Sketch base + base_rect_profile = createBaseRectSketch(component, self) + distance = createDistance((self.remoteLength + self.bottomThickness) * SCALE) + # ui.messageBox("Dimensions %s x %s x %s" % (str(self.remoteLength), str(self.remoteWidth), str(self.remoteThickness))) + + # Extrude to full height + # + holster = component.features.extrudeFeatures.addSimple(base_rect_profile, distance, NEW_BODY) + holster_body = holster.bodies.item(0) + holster_body.name = "Holster" + + # Cut out the pocket + # + pocket_profile = createPocketSketch(component, self) + pocket_depth = createDistance(self.remoteLength * SCALE * -1) + component.features.extrudeFeatures.addSimple(pocket_profile, pocket_depth, CUT) + + # Push down the front + # + front_profile = createFrontSketch(component, self) + front_depth = createDistance((self.remoteLength - self.frontHeight) * SCALE * -1) + component.features.extrudeFeatures.addSimple(front_profile, front_depth, CUT) + + # Create Slot + # + slot_profile = createSlotSketch(component, self) + slot_depth = createDistance((self.remoteThickness + self.sideThickness) * SCALE) + component.features.extrudeFeatures.addSimple(slot_profile, slot_depth, CUT) + + except: + if ui: + ui.messageBox('Failed to compute the holster. This is most likely because the input values define an invalid holster.') + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +def createPocketSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: + sketch: adsk.fusion.Sketch = component.sketches.add(component.xYConstructionPlane) + sketch.name = "Pocket Sketch" + + p1 = createPoint((holster.sideThickness) * SCALE, (holster.sideThickness) * SCALE, (holster.remoteLength + holster.bottomThickness) * SCALE) + p2 = createPoint((holster.sideThickness + holster.remoteWidth) * SCALE, (holster.sideThickness + holster.remoteThickness) * SCALE, (holster.remoteLength + holster.bottomThickness) * SCALE) + + sketch.sketchCurves.sketchLines.addTwoPointRectangle(p1, p2) + + rect = adsk.core.ObjectCollection.create() + rect.add(sketch.profiles.item(0)) + + return rect + +def createFrontSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: + sketch: adsk.fusion.Sketch = component.sketches.add(component.xYConstructionPlane) + sketch.name = "Front Sketch" + + p1 = createPoint(0, 0, (holster.remoteLength + holster.bottomThickness) * SCALE) + p2 = createPoint((2 * holster.sideThickness + holster.remoteWidth) * SCALE, (holster.sideThickness + holster.remoteThickness) * SCALE, (holster.remoteLength + holster.bottomThickness) * SCALE) + + sketch.sketchCurves.sketchLines.addTwoPointRectangle(p1, p2) + + rect = adsk.core.ObjectCollection.create() + rect.add(sketch.profiles.item(0)) + + return rect + +def createSlotSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: + sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) + sketch.name = "Slot Sketch" + slotLeft = (holster.sideThickness + holster.remoteWidth - holster.frontSlotWidth)/2 + p1 = createPoint(slotLeft * SCALE, 0, 0) + p2 = createPoint((slotLeft + holster.frontSlotWidth) * SCALE, -1 * (holster.frontHeight + holster.bottomThickness) * SCALE, 0) + + sketch.sketchCurves.sketchLines.addTwoPointRectangle(p1, p2) + + rect = adsk.core.ObjectCollection.create() + rect.add(sketch.profiles.item(0)) + + return rect + +def run(context): + try: + product = app.activeProduct + design = adsk.fusion.Design.cast(product) + if not design: + ui.messageBox('It is not supported in current workspace, please change to MODEL workspace and try again.') + return + commandDefinitions = ui.commandDefinitions + + #check the command exists or not + # + cmdDef = commandDefinitions.itemById('RemoteHolster') + if not cmdDef: + cmdDef = commandDefinitions.addButtonDefinition('RemoteHolster', + 'Create a Remote Holster', + 'Create a Remote Holster.') + + onCommandCreated = HolsterCommandCreatedHandler() + cmdDef.commandCreated.add(onCommandCreated) + # keep the handler referenced beyond this function + handlers.append(onCommandCreated) + inputs = adsk.core.NamedValues.create() + + cmdDef.execute(inputs) + + # prevent this module from being terminate when the script returns, because we are waiting for event handlers to fire + adsk.autoTerminate(False) + except: + if ui: + ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) diff --git a/copy-to-scripts-dir.sh b/copy-to-scripts-dir.sh new file mode 100755 index 0000000..bc5f317 --- /dev/null +++ b/copy-to-scripts-dir.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Note that this might not be right for everyone +FUSION_360_API_DIR="/Users/$USER/Library/Application Support/Autodesk/Autodesk Fusion 360/API" +FUSION_360_SCRIPTS_DIR="${FUSION_360_API_DIR}/Scripts" + +if [ -d "${FUSION_360_API_DIR}" ] +then + mkdir -p "${FUSION_360_SCRIPTS_DIR}" + cd /Users/jamie/Projects/GitRepo/gfdb + cp GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py "${FUSION_360_SCRIPTS_DIR}" + cp RemoteHolster/RemoteHolsterMaker.py "${FUSION_360_SCRIPTS_DIR}" +else + echo "Fusion 360 API directory (${FUSION_360_API_DIR}) not found, aborting" + exit 9 +fi From eee50d9eeca35e84470b087f2204ba53936a5fdf Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 26 Jun 2023 12:10:33 -0400 Subject: [PATCH 07/10] need manifests to debug --- .../GridFinityDividerBoxMaker.manifest | 10 +++++++ .../RemoteHolsterMaker.manifest | 10 +++++++ .../RemoteHolsterMaker.py | 29 +++++++++++++++---- copy-to-scripts-dir.sh | 4 +-- 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.manifest create mode 100644 RemoteHolsterMaker/RemoteHolsterMaker.manifest rename {RemoteHolster => RemoteHolsterMaker}/RemoteHolsterMaker.py (94%) diff --git a/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.manifest b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/RemoteHolsterMaker/RemoteHolsterMaker.manifest b/RemoteHolsterMaker/RemoteHolsterMaker.manifest new file mode 100644 index 0000000..28ab33c --- /dev/null +++ b/RemoteHolsterMaker/RemoteHolsterMaker.manifest @@ -0,0 +1,10 @@ +{ + "autodeskProduct": "Fusion360", + "type": "script", + "author": "", + "description": { + "": "" + }, + "supportedOS": "windows|mac", + "editEnabled": true +} \ No newline at end of file diff --git a/RemoteHolster/RemoteHolsterMaker.py b/RemoteHolsterMaker/RemoteHolsterMaker.py similarity index 94% rename from RemoteHolster/RemoteHolsterMaker.py rename to RemoteHolsterMaker/RemoteHolsterMaker.py index 209c36c..ef57b36 100755 --- a/RemoteHolster/RemoteHolsterMaker.py +++ b/RemoteHolsterMaker/RemoteHolsterMaker.py @@ -3,6 +3,8 @@ # import math from typing import Tuple +# import sys +# sys.path.append("/Users/jamie/Library/Application Support/Autodesk/Autodesk Fusion 360/API/Python/defs") import adsk.core, adsk.fusion, adsk.cam, traceback # Some Constants @@ -143,9 +145,9 @@ def createCurvedRect(component: adsk.fusion.Component, name, width: float, depth # FIXME: not actually a path return path, sketch.profiles.item(0) -def createBaseSweepSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: - b, _ = createCurvedRect(component, "Base Sweep Sketch", slotDimension, slotDimension, baseCornerRadius, 0) - return b +# def createBaseSweepSketch(component: adsk.fusion.Component) -> adsk.core.ObjectCollection: +# b, _ = createCurvedRect(component, "Base Sweep Sketch", slotDimension, slotDimension, baseCornerRadius, 0) +# return b class HolsterCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self): @@ -252,7 +254,7 @@ def notify(self, args): inputs.addFloatSpinnerCommandInput('backCornerRound', 'Back Corner Round', '', 0.25, 100.0, finestIncrement, defaultBackCornerRound) initFillet = adsk.core.ValueInput.createByReal(defaultFillet) - inputs.addFloatSpinnerCommandInput('fillet', 'Overall Fillet', '', 0.25, 100.0, finestIncrement, defaultFillet) + inputs.addFloatSpinnerCommandInput('fillet', 'Overall Fillet', '', 0.25, 100.0, 0.01, defaultFillet) initFrontSlotRound = adsk.core.ValueInput.createByReal(defaultFrontSlotRound) inputs.addFloatSpinnerCommandInput('frontSlotRound', 'Front Slot Round', '', 0.25, 100.0, finestIncrement, defaultFrontSlotRound) @@ -437,7 +439,24 @@ def buildHolster(self): slot_profile = createSlotSketch(component, self) slot_depth = createDistance((self.remoteThickness + self.sideThickness) * SCALE) component.features.extrudeFeatures.addSimple(slot_profile, slot_depth, CUT) - + + e = holster_body.edges + + fillet_edges = adsk.core.ObjectCollection.create() + for n in range(e.count): + edge = e.item(n) + bb = edge.boundingBox + mx = bb.maxPoint + mn = bb.minPoint + fillet_edges.add(edge) + + fillets = component.features.filletFeatures + fillet_input = fillets.createInput() + fillet_radius = createDistance(self.fillet * SCALE) + fillet_input.addConstantRadiusEdgeSet(fillet_edges, fillet_radius, True) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + top_fillet = fillets.add(fillet_input) except: if ui: ui.messageBox('Failed to compute the holster. This is most likely because the input values define an invalid holster.') diff --git a/copy-to-scripts-dir.sh b/copy-to-scripts-dir.sh index bc5f317..a695826 100755 --- a/copy-to-scripts-dir.sh +++ b/copy-to-scripts-dir.sh @@ -8,8 +8,8 @@ if [ -d "${FUSION_360_API_DIR}" ] then mkdir -p "${FUSION_360_SCRIPTS_DIR}" cd /Users/jamie/Projects/GitRepo/gfdb - cp GridFinityDividerBoxMaker/GridFinityDividerBoxMaker.py "${FUSION_360_SCRIPTS_DIR}" - cp RemoteHolster/RemoteHolsterMaker.py "${FUSION_360_SCRIPTS_DIR}" + cp -r GridFinityDividerBoxMaker "${FUSION_360_SCRIPTS_DIR}" + cp -r RemoteHolsterMaker "${FUSION_360_SCRIPTS_DIR}/" else echo "Fusion 360 API directory (${FUSION_360_API_DIR}) not found, aborting" exit 9 From 2f2141a909594385ab7a53b78eff16cd17ec5c9a Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 27 Jun 2023 08:21:35 -0400 Subject: [PATCH 08/10] seems to work now --- RemoteHolsterMaker/RemoteHolsterMaker.py | 107 +++++++++++++++++++++-- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/RemoteHolsterMaker/RemoteHolsterMaker.py b/RemoteHolsterMaker/RemoteHolsterMaker.py index ef57b36..5676dd2 100755 --- a/RemoteHolsterMaker/RemoteHolsterMaker.py +++ b/RemoteHolsterMaker/RemoteHolsterMaker.py @@ -187,6 +187,8 @@ def notify(self, args): holster.bottomThickness = input.value elif input.id == 'tolerance': holster.tolerance = input.value + elif input.id == 'temp': + holster.temp = input.value holster.buildHolster(); @@ -271,6 +273,10 @@ def notify(self, args): initTolerance = adsk.core.ValueInput.createByReal(defaultTolerance) inputs.addFloatSpinnerCommandInput('tolerance', 'Tolerance', '', 0.25, 100.0, finestIncrement, defaultTolerance) + initTemp = adsk.core.ValueInput.createByReal(0) + inputs.addIntegerSpinnerCommandInput('temp', 'Temp', 0, 50, 1, 0) + + except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) @@ -290,6 +296,7 @@ def __init__(self): self._backThickness = defaultBackThickness self._bottomThickness = defaultBottomThickness self._tolerance = defaultTolerance + self._temp = 0 # properties # @@ -384,6 +391,12 @@ def tolerance(self): def tolerance(self, value): self._tolerance = value + @property + def temp(self): + return self._temp + @temp.setter + def temp(self, value): + self._temp = value def buildHolster(self): try: @@ -440,14 +453,69 @@ def buildHolster(self): slot_depth = createDistance((self.remoteThickness + self.sideThickness) * SCALE) component.features.extrudeFeatures.addSimple(slot_profile, slot_depth, CUT) - e = holster_body.edges - + # Round the back corners + # + edges = holster_body.edges fillet_edges = adsk.core.ObjectCollection.create() - for n in range(e.count): - edge = e.item(n) - bb = edge.boundingBox - mx = bb.maxPoint - mn = bb.minPoint + + for n in range(edges.count): + edge = edges.item(n) + if math.isclose(edge.length, self.backThickness * SCALE): + # Need to figure out where it is + bb = edge.boundingBox + maxZ = bb.maxPoint.z + minZ = bb.minPoint.z + if math.isclose(maxZ, minZ) and math.isclose(maxZ, (self.remoteLength + self.bottomThickness) * SCALE): + fillet_edges.add(edge) + + fillets = component.features.filletFeatures + fillet_input = fillets.createInput() + fillet_radius = createDistance(self.backCornerRound * SCALE) + fillet_input.addConstantRadiusEdgeSet(fillet_edges, fillet_radius, True) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + top_fillet = fillets.add(fillet_input) + + fillet_edges.clear() + for n in range(edges.count): + edge = edges.item(n) + if math.isclose(edge.length, self.sideThickness * SCALE): + # Need to figure out where it is + bb = edge.boundingBox + maxZ = bb.maxPoint.z + minZ = bb.minPoint.z + minY = bb.minPoint.y + + if math.isclose(maxZ, minZ) and math.isclose(maxZ, (self.frontHeight + self.bottomThickness) * SCALE) and math.isclose(minY, 0): + fillet_edges.add(edge) + + fillets = component.features.filletFeatures + fillet_input = fillets.createInput() + fillet_radius = createDistance(self.frontSlotRound * SCALE) + fillet_input.addConstantRadiusEdgeSet(fillet_edges, fillet_radius, True) + fillet_input.isG2 = False + fillet_input.isRollingBallCorner = True + top_fillet = fillets.add(fillet_input) + + self.includeScrewHoles = True + + if self.includeScrewHoles: + # Magnet hole sketch + screwHolesProfile = createScrewHolesSketch(component, self, 2) + + # Extrude for magnets + distance = createDistance(self.backThickness * SCALE) + component.features.extrudeFeatures.addSimple(screwHolesProfile, distance, CUT) + + screwHolesProfile = createScrewHolesSketch(component, self, 4) + distance = createDistance(self.backThickness / 3 * SCALE) + component.features.extrudeFeatures.addSimple(screwHolesProfile, distance, CUT) + + # Soften everything + # + fillet_edges.clear() + for n in range(edges.count): + edge = edges.item(n) fillet_edges.add(edge) fillets = component.features.filletFeatures @@ -457,11 +525,31 @@ def buildHolster(self): fillet_input.isG2 = False fillet_input.isRollingBallCorner = True top_fillet = fillets.add(fillet_input) + + except: if ui: ui.messageBox('Failed to compute the holster. This is most likely because the input values define an invalid holster.') ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) + +def createScrewHolesSketch(component: adsk.fusion.Component, holster, diameter) -> adsk.core.ObjectCollection: + sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) + sketch.name = "Screw Holes Sketch" + + holesCenter = (holster.sideThickness + holster.remoteWidth / 2) + holesBack = (holster.sideThickness + holster.remoteThickness) + holesSpace = (holster.bottomThickness + holster.remoteLength) / 4 * -1 + + sketch.sketchCurves.sketchCircles.addByCenterRadius(createPoint(holesCenter * SCALE, holesSpace * SCALE, holesBack * SCALE), diameter * SCALE) + sketch.sketchCurves.sketchCircles.addByCenterRadius(createPoint(holesCenter * SCALE, 3 * holesSpace * SCALE, holesBack * SCALE), diameter * SCALE) + + circles = adsk.core.ObjectCollection.create() + for n in range(2): + circles.add(sketch.profiles.item(n)) + + return circles + def createPocketSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: sketch: adsk.fusion.Sketch = component.sketches.add(component.xYConstructionPlane) sketch.name = "Pocket Sketch" @@ -479,7 +567,7 @@ def createPocketSketch(component: adsk.fusion.Component, holster) -> adsk.core.O def createFrontSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: sketch: adsk.fusion.Sketch = component.sketches.add(component.xYConstructionPlane) sketch.name = "Front Sketch" - + p1 = createPoint(0, 0, (holster.remoteLength + holster.bottomThickness) * SCALE) p2 = createPoint((2 * holster.sideThickness + holster.remoteWidth) * SCALE, (holster.sideThickness + holster.remoteThickness) * SCALE, (holster.remoteLength + holster.bottomThickness) * SCALE) @@ -493,7 +581,8 @@ def createFrontSketch(component: adsk.fusion.Component, holster) -> adsk.core.Ob def createSlotSketch(component: adsk.fusion.Component, holster) -> adsk.core.ObjectCollection: sketch: adsk.fusion.Sketch = component.sketches.add(component.xZConstructionPlane) sketch.name = "Slot Sketch" - slotLeft = (holster.sideThickness + holster.remoteWidth - holster.frontSlotWidth)/2 + + slotLeft = (2 * holster.sideThickness + holster.remoteWidth - holster.frontSlotWidth) / 2 p1 = createPoint(slotLeft * SCALE, 0, 0) p2 = createPoint((slotLeft + holster.frontSlotWidth) * SCALE, -1 * (holster.frontHeight + holster.bottomThickness) * SCALE, 0) From 1f80927293a99da109de4e6859ec5ac96707d943 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 27 Jun 2023 08:22:36 -0400 Subject: [PATCH 09/10] make the copy script more generic, yet still opinionated --- copy-to-scripts-dir.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copy-to-scripts-dir.sh b/copy-to-scripts-dir.sh index a695826..ae4d7ef 100755 --- a/copy-to-scripts-dir.sh +++ b/copy-to-scripts-dir.sh @@ -7,7 +7,7 @@ FUSION_360_SCRIPTS_DIR="${FUSION_360_API_DIR}/Scripts" if [ -d "${FUSION_360_API_DIR}" ] then mkdir -p "${FUSION_360_SCRIPTS_DIR}" - cd /Users/jamie/Projects/GitRepo/gfdb + cd /Users/$USER/Projects/GitRepo/gfdb cp -r GridFinityDividerBoxMaker "${FUSION_360_SCRIPTS_DIR}" cp -r RemoteHolsterMaker "${FUSION_360_SCRIPTS_DIR}/" else From 5cf36ce9e7c345e1b41f3ba9e69d0a85594481bf Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 27 Jun 2023 09:09:25 -0400 Subject: [PATCH 10/10] moved to new repo https://github.com/jamiesmith/fusion360scripts --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0a77848..f4ad406 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # gfdb GridFinity Divider Boxes in Fusion 360 +NOTE: This is no longer maintained, but has been copied to a new repo for my f360 utils, https://github.com/jamiesmith/fusion360scripts + ## Bugs - There are a few fillets missing.