def make_pusher_plate(self): pusher = CQ().add(self.pusher_plate).toPending().extrude(self.pusher_t) pusher = pusher.add(self.plate_mounts).toPending().cutThruAll() pusher = pusher.translate( (0, 0, self.pcb_thickness / 2 + self.slots_t + self.silicone_working_t)) return pusher
def make_heater_plate(self): heater = CQ().add(self.base_plate).toPending().extrude(self.heater_t) heater = heater.faces(">Z[-1]").workplane().add( self.plate_mounts).translate( (0, 0, self.heater_t)).toPending().cutBlind(-self.screw_depth) heater = heater.translate((0, 0, -self.heater_t - self.cu_base_t - self.pcb_thickness / 2 - self.pcb_spacer_h)) return (heater)
def mk_vgroove(cutter_path, entry_point, depth): """ for cutting grooves with a 90 degree countersink cutter""" half_profile = CQ('XZ').polyline([(0, 0), (depth, 0), (0, -depth)]).close() cutter = half_profile.revolve() cutter_split = cutter.split(keepTop=True) cutter_crosssection = cutter_split.faces('+Y') #TODO do this more generally cutter_crosssection_shift = cutter_crosssection.translate(entry_point) to_sweep = cutter_crosssection_shift.wires().toPending() sweep_result = to_sweep.sweep(cutter_path, combine=True, transition="round", sweepAlongWires=False, isFrenet=True) return sweep_result
def __init__(self): self.cu_towers = self.get_wires("cu_towers") self.base_plate = self.get_wires("base_plate") self.cu_base = self.get_wires("cu_base") self.plate_mounts = self.get_wires("plate_mounts") self.pusher_plate = self.get_wires("pusher_plate") self.slot_plate = self.get_wires("slot_plate") self.spacer_pcb = self.get_wires("spacer_pcb") self.silicone = self.get_wires("silicone") self.glass = self.get_wires("glass") self.dowels = self.get_wires("dowels") self.cu_nubs = self.get_wires("cu_nubs") self.cu_dowel_pf = self.get_wires("cu_dowel_pf") self.pcb = CQ().add( cadquery.importers.importStep(str(self.pcb_step_filepath))) self.vent_screw = CQ().add( cadquery.importers.importStep(str(self.vent_screw_filepath)))
def _make_neg(what): """makes a negative shape to be cut out of the parent walls""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what # fastener threaded holes # TODO: mark these holes as "M3-0.5 threaded" in the engineering drawing fhs = CQ().pushPoints(fhps).circle(fix_scr.tap_hole_diameters["Soft"] / 2).extrude(-wall_depth + pt_fix_wall_buffer) nwp = CQ().add(through_face) through = nwp.wires().toPending().extrude(-wall_depth) nwp2 = CQ().add(recess_face) recess = nwp2.wires().toPending().extrude(-part_thickness) neg = recess.union(through).union(fhs) return neg.findSolid().moved(base * loc)
def make_reservation(self, do_ventscrews: bool = False): if do_ventscrews: vss = self.get_ventscrews_a() sr = CQ().box(self.reserve_xy, self.reserve_xy, self.reserve_h, centered=(True, True, False)) sr = sr.translate( (0, 0, -self.pcb_thickness - self.pcb_spacer_h - self.cu_base_t)) wires = CQ().box(self.wire_slot_depth, 2.54 * 20, 2.54 * 2, centered=(False, True, False)).translate( (-self.reserve_xy / 2, 0, self.wire_slot_z + self.pcb_thickness / 2)) wiresA = wires.translate((0, self.wire_slot_offset, 0)) wiresB = wires.translate((0, -self.wire_slot_offset, 0)) sr = sr.cut(wiresA).cut(wiresB) if do_ventscrews: sr = sr.add(vss) # these next two lines are very expensive (and optional)! sr = sr.add(self.get_pcb()) #sr = CQ().union(sr) return sr
def make_tower_plate(self): towers = CQ().add(self.cu_base).toPending().extrude(self.cu_base_t) towers = towers.faces("<Z[-2]").workplane().add( self.cu_towers).translate( (0, 0, self.cu_base_t)).toPending().extrude(self.cu_tower_h) towers = towers.faces("<Z[-2]").workplane().add( self.cu_nubs).translate( (0, 0, self.cu_base_t)).toPending().extrude(self.cu_nub_h) towers = towers.add(self.plate_mounts).toPending().cutThruAll() towers = towers.add(self.cu_dowel_pf).toPending().cutThruAll() towers = towers.translate( (0, 0, -self.cu_base_t - self.pcb_thickness / 2 - self.pcb_spacer_h)) return (towers)
def _make_pcb(what): """build the actual passthrough PCB""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what pcb = CQ().workplane(offset=-wall_depth - board_inner_depth) pcb = pcb.rect(board_width, pcbt).extrude(until=board_inner_depth + wall_depth + board_outer_depth) pcb = pcb.edges("|Y").fillet(pcb_corner) # put in screws with holes hardware = cadquery.Assembly() pcb = pcb.faces(">Y").workplane(**u.cobb).rarray( board_width - 2 * block_width / 2, board_inner_depth + board_outer_depth + wall_depth - 2 * pt_pcb_mount_hole_offset[0], 2, 2).clearanceHole(pcb_scr, fit="Close", counterSunk=False, baseAssembly=hardware) if hw_asy is not None: hw_asy.add(hardware, loc=base * loc) # put in pin0 holes pcb = pcb.faces(">Y").workplane(**u.copo, origin=(0, 0, 0)).pushPoints(p0pts).circle( pin0_holed / 2).cutThruAll() return pcb.findSolid().moved(base * loc)
class Badger(object): cu_base_t = 3 pcb_spacer_h = 1.6 reserve_xy = 200 reserve_h = 20 wire_slot_depth = 28 # depth from edge wire_slot_offset = 46.5 # offset y wire_slot_z = 2.095 # offset z heater_t = 20 pusher_t = 4 glass_t = 1.1 silicone_t = 0.508 # uncompressed silicone thickness (0.02 in https://www.mcmaster.com/86915K22/) compressed_silicone_fraction = 0.8 # let's say it'll compress to 0.8 of its initial thickness silicone_working_t = silicone_t * compressed_silicone_fraction # working silicone thickness (just sets up where the glass and the pusher end up in the model) pcb_thickness = 1.6 min_min_height = 0.8 # pins are fully depressed on a surface this far from the PCB cu_tower_h = pcb_spacer_h + pcb_thickness + min_min_height # 0.8 here just ensures the 0921 pins can never bottom out # thus the thermal pad material can be any thickness 0 through pin working travel (about 1.4mm, but lets say 1.3mm to be safe) slots_t = min_min_height + glass_t cu_nub_h = pcb_spacer_h + pcb_thickness - 0.3 dowel_height = 16 glass_xy = 25 screw_depth = 7 # ventscrew coordinates screw_spots = [ (-93, 93), (-31, 93), (31, 93), (93, 93), (93, 31), (93, -31), (93, -93), (31, -93), (-31, -93), (-93, -93), (-93, 0), ] gs = globals() ls = locals() print(gs) print(ls) dxf_filepath = Path(__file__).parent / "drawings" / "2d.dxf" pcb_step_filepath = Path(__file__).parent / "components" / "pcb.step" vent_screw_filepath = Path( __file__).parent / "components" / "vent_screw.step" def __init__(self): self.cu_towers = self.get_wires("cu_towers") self.base_plate = self.get_wires("base_plate") self.cu_base = self.get_wires("cu_base") self.plate_mounts = self.get_wires("plate_mounts") self.pusher_plate = self.get_wires("pusher_plate") self.slot_plate = self.get_wires("slot_plate") self.spacer_pcb = self.get_wires("spacer_pcb") self.silicone = self.get_wires("silicone") self.glass = self.get_wires("glass") self.dowels = self.get_wires("dowels") self.cu_nubs = self.get_wires("cu_nubs") self.cu_dowel_pf = self.get_wires("cu_dowel_pf") self.pcb = CQ().add( cadquery.importers.importStep(str(self.pcb_step_filepath))) self.vent_screw = CQ().add( cadquery.importers.importStep(str(self.vent_screw_filepath))) def get_wires(self, layername): """returns the wires from the given dxf layer""" # list of of all layers in the dxf dxf_layernames = [ "0", "base_plate", "connector", "cu_base", "cu_dowel_pf", "cu_nubs", "cu_towers", "Defpoints", "dims", "dowels", "glass", "metal_mask", "pcb", "pin_holes", "plate_mounts", "pusher_plate", "silicone", "slot_plate", "spacer_pcb", ] to_exclude = [k for k in dxf_layernames if layername != k] dxf_obj = cadquery.importers.importDXF(str(self.dxf_filepath), exclude=to_exclude) return (dxf_obj.wires()) def make_heater_plate(self): heater = CQ().add(self.base_plate).toPending().extrude(self.heater_t) heater = heater.faces(">Z[-1]").workplane().add( self.plate_mounts).translate( (0, 0, self.heater_t)).toPending().cutBlind(-self.screw_depth) heater = heater.translate((0, 0, -self.heater_t - self.cu_base_t - self.pcb_thickness / 2 - self.pcb_spacer_h)) return (heater) def make_tower_plate(self): towers = CQ().add(self.cu_base).toPending().extrude(self.cu_base_t) towers = towers.faces("<Z[-2]").workplane().add( self.cu_towers).translate( (0, 0, self.cu_base_t)).toPending().extrude(self.cu_tower_h) towers = towers.faces("<Z[-2]").workplane().add( self.cu_nubs).translate( (0, 0, self.cu_base_t)).toPending().extrude(self.cu_nub_h) towers = towers.add(self.plate_mounts).toPending().cutThruAll() towers = towers.add(self.cu_dowel_pf).toPending().cutThruAll() towers = towers.translate( (0, 0, -self.cu_base_t - self.pcb_thickness / 2 - self.pcb_spacer_h)) return (towers) def get_ventscrews_a(self): ventscrew = self.vent_screw.translate((0, 0, -1.7)) ventscrews = CQ().pushPoints(self.screw_spots).eachpoint( lambda loc: ventscrew.val().moved(loc), True) return ventscrews def get_ventscrews_b(self): ventscrew = self.vent_screw.translate((0, 0, 3.3)) ventscrews = CQ().pushPoints(self.screw_spots).eachpoint( lambda loc: ventscrew.val().moved(loc), True) return ventscrews def make_reservation(self, do_ventscrews: bool = False): if do_ventscrews: vss = self.get_ventscrews_a() sr = CQ().box(self.reserve_xy, self.reserve_xy, self.reserve_h, centered=(True, True, False)) sr = sr.translate( (0, 0, -self.pcb_thickness - self.pcb_spacer_h - self.cu_base_t)) wires = CQ().box(self.wire_slot_depth, 2.54 * 20, 2.54 * 2, centered=(False, True, False)).translate( (-self.reserve_xy / 2, 0, self.wire_slot_z + self.pcb_thickness / 2)) wiresA = wires.translate((0, self.wire_slot_offset, 0)) wiresB = wires.translate((0, -self.wire_slot_offset, 0)) sr = sr.cut(wiresA).cut(wiresB) if do_ventscrews: sr = sr.add(vss) # these next two lines are very expensive (and optional)! sr = sr.add(self.get_pcb()) #sr = CQ().union(sr) return sr def make_silicone(self): silicone = CQ().add(self.silicone).toPending().extrude(self.silicone_t) silicone = silicone.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h + self.cu_tower_h)) return silicone def make_dowels(self): dowels = CQ().add(self.dowels).toPending().extrude(self.dowel_height) dowels = dowels.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h - self.cu_base_t)) return dowels def make_glass(self): glass = CQ().add(self.glass).toPending().extrude(self.glass_t) glass = glass.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h + self.cu_tower_h + self.silicone_working_t)) return glass def make_spacer_pcb(self): spacer = CQ().add(self.spacer_pcb).toPending().extrude( self.pcb_spacer_h) spacer = spacer.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h)) return (spacer) def get_pcb(self): return self.pcb def make_pusher_plate(self): pusher = CQ().add(self.pusher_plate).toPending().extrude(self.pusher_t) pusher = pusher.add(self.plate_mounts).toPending().cutThruAll() pusher = pusher.translate( (0, 0, self.pcb_thickness / 2 + self.slots_t + self.silicone_working_t)) return pusher def make_slot_plate(self): slots = CQ().add(self.slot_plate).toPending().extrude(self.slots_t) slots = slots.add(self.plate_mounts).toPending().cutThruAll() slots = slots.translate((0, 0, self.pcb_thickness / 2)) return (slots) def build(self, do_ventscrews: bool = False): s = self asy = cadquery.Assembly() # the spacer PCB spacer = s.make_spacer_pcb() asy.add(spacer, name="spacer", color=cadquery.Color("DARKGREEN")) # the towerplate tp = self.make_tower_plate() asy.add(tp, name="towers", color=cadquery.Color("GOLDENROD")) # dowels dwl = self.make_dowels() asy.add(dwl, name="dowels", color=cadquery.Color("BLACK")) # silicone sil = self.make_silicone() asy.add(sil, name="silicone", color=cadquery.Color("WHITE")) # glass glass = self.make_glass() asy.add(glass, name="glass", color=cadquery.Color("SKYBLUE")) # the heater base plate heater = s.make_heater_plate() asy.add(heater, name="heater", color=cadquery.Color("MATRAGRAY")) # the spring pin PCB pcb = self.get_pcb() asy.add(pcb, name="pcb", color=cadquery.Color("brown")) if do_ventscrews: # the vent screws vss_a = self.get_ventscrews_a() vss_b = self.get_ventscrews_b() asy.add(vss_a.add(vss_b), name="ventscrew") # the alignment slot plate slots = self.make_slot_plate() asy.add(slots, name="sample_slots", color=cadquery.Color("GRAY45")) pusher = self.make_pusher_plate() asy.add(pusher, name="pusher", color=cadquery.Color("GRAY28")) reserve = self.make_reservation(do_ventscrews=do_ventscrews) asy.add(reserve, name="space_reservation") return asy
def mk_groove( self: cq.Workplane, vdepth: float = 0, ring_cs: float = 0, follow_pending_wires: bool = True, ring_id: float = None, gland_x: float = None, gland_y: float = None, compression_ratio: float = 0.25, gland_fill_ratio: float = 0.7, clean: bool = True, hardware: cadquery.Assembly = None, ) -> cq.Workplane: """ for cutting grooves set vdepth > 0 to cut a vgroove of that depth otherwise set ring_cs > 0 to cut an o-ring groove (the diameter of the cross section of the o-ring) follow_pending_wires = True, will mean the grooves will be cut according to pending wires/faces if that's false, we'll make a groove for a specific o-ring and you must set all off the following: ring_id, the innter diameter of the ring to be used (bore diameter) gland_x = the spacing in x between the centers of the gland (rounded) rectangle gland_y = the spacing in y between the centers of the gland (rounded) rectangle the fillets at the gland corners will be determined to ensure the ring fits, they will be equal if a hardware assembly is provided, o-oring hardware will be added to it """ def _make_one_groove(wp, _wire, _vdepth, _ring_cs, _compression_ratio, _gland_fill_ratio): cp_tangent = _wire.tangentAt(0) # tangent to cutter_path cp_start = _wire.startPoint() build_plane = cq.Plane(origin=cp_start, normal=cp_tangent, xDir=wp.plane.zDir) if _vdepth > 0: # we'll cut a vgroove this deep half_profile = CQ(build_plane).polyline([(0, 0), (-_vdepth, 0), (0, _vdepth)]).close() elif ring_cs > 0: # we'll cut an o-ring groove, ring_cs is the diameter of the cross section of the o-ring # according to https://web.archive.org/web/20220512010502/https://www.globaloring.com/o-ring-groove-design/ gland_height = get_gland_height(_ring_cs, _compression_ratio) gland_width = get_gland_width(_ring_cs, _compression_ratio, _gland_fill_ratio) half_profile = CQ(build_plane).polyline([ (0, 0), (-gland_height, 0), (-gland_height, gland_width / 2), (0, gland_width / 2) ]).close() else: raise ValueError("One of vdepth or ring_cs must be larger than 0") cutter = half_profile.revolve(axisEnd=(1, 0, 0)) cutter_split = cutter.split(keepTop=True) faces = cutter_split.faces().vals() for face in faces: # find the right face to sweep with facenorm = face.normalAt() dotval = facenorm.dot(cp_tangent) if abs( (abs(dotval) - 1) ) <= 0.001: # allow for small errors in orientation calculation cutter_crosssection = face break else: raise ValueError("Unable to find a cutter cross-section") # make the squished o-ring hardware if (ring_cs > 0) and (hardware is not None): ring_sweep_wire = CQ(build_plane).center( -gland_height / 2, 0).ellipse(gland_height / 2, ring_cs / 2 * (1 + _compression_ratio)).wires().toPending() hardware.add( ring_sweep_wire.sweep(_wire, combine=True, transition="round", isFrenet=True).findSolid()) to_sweep = CQ(cutter_crosssection).wires().toPending() return to_sweep.sweep(_wire).findSolid() s = self.findSolid() if follow_pending_wires: faces = self._getFaces() for face in faces: wire = face.outerWire() logger = logging.getLogger(__name__) logger.info( f"Made an o-ring gland for ring length {wire.Length()}mm and diameter {ring_cs}mm" ) sweep_result = _make_one_groove( wp=self, _wire=wire, _vdepth=vdepth, _ring_cs=ring_cs, _compression_ratio=compression_ratio, _gland_fill_ratio=gland_fill_ratio) s = s.cut(sweep_result) if clean: s = s.clean() else: # we'll need to make our own path wire then, given the user specs # ensure the user passed in the right stuff assert vdepth == 0 assert ring_cs > 0 assert ring_id is not None assert gland_x is not None assert gland_y is not None wire_length = 2 * math.pi * (ring_id / 2 + ring_cs / 2) square_length = 2 * gland_x + 2 * gland_y if wire_length > square_length: raise ValueError( "The o-ring circumference is too big for the given x and y gland dims" ) r = (wire_length - 2 * gland_x - 2 * gland_y) / (2 * math.pi - 8) if (2 * r > gland_x) or (2 * r > gland_y): raise ValueError( "The o-ring circumference is too small for the given x and y gland dims" ) logger = logging.getLogger(__name__) logger.info( f"Using path bend radius {r}mm, that's an uncompressed cord inner radius of {r-ring_cs/2} (and the min is {ring_cs*3})" ) wire = CQ(self.plane).rect(gland_x, gland_y).wires().val() wire = wire.fillet2D(r, wire.Vertices()) sweep_result = _make_one_groove(wp=self, _wire=wire, _vdepth=vdepth, _ring_cs=ring_cs, _compression_ratio=compression_ratio, _gland_fill_ratio=gland_fill_ratio) s = s.cut(sweep_result) if clean: s = s.clean() return self.newObject([s])
def make_slot_plate(self): slots = CQ().add(self.slot_plate).toPending().extrude(self.slots_t) slots = slots.add(self.plate_mounts).toPending().cutThruAll() slots = slots.translate((0, 0, self.pcb_thickness / 2)) return (slots)
def _make_one_groove(wp, _wire, _vdepth, _ring_cs, _compression_ratio, _gland_fill_ratio): cp_tangent = _wire.tangentAt(0) # tangent to cutter_path cp_start = _wire.startPoint() build_plane = cq.Plane(origin=cp_start, normal=cp_tangent, xDir=wp.plane.zDir) if _vdepth > 0: # we'll cut a vgroove this deep half_profile = CQ(build_plane).polyline([(0, 0), (-_vdepth, 0), (0, _vdepth)]).close() elif ring_cs > 0: # we'll cut an o-ring groove, ring_cs is the diameter of the cross section of the o-ring # according to https://web.archive.org/web/20220512010502/https://www.globaloring.com/o-ring-groove-design/ gland_height = get_gland_height(_ring_cs, _compression_ratio) gland_width = get_gland_width(_ring_cs, _compression_ratio, _gland_fill_ratio) half_profile = CQ(build_plane).polyline([ (0, 0), (-gland_height, 0), (-gland_height, gland_width / 2), (0, gland_width / 2) ]).close() else: raise ValueError("One of vdepth or ring_cs must be larger than 0") cutter = half_profile.revolve(axisEnd=(1, 0, 0)) cutter_split = cutter.split(keepTop=True) faces = cutter_split.faces().vals() for face in faces: # find the right face to sweep with facenorm = face.normalAt() dotval = facenorm.dot(cp_tangent) if abs( (abs(dotval) - 1) ) <= 0.001: # allow for small errors in orientation calculation cutter_crosssection = face break else: raise ValueError("Unable to find a cutter cross-section") # make the squished o-ring hardware if (ring_cs > 0) and (hardware is not None): ring_sweep_wire = CQ(build_plane).center( -gland_height / 2, 0).ellipse(gland_height / 2, ring_cs / 2 * (1 + _compression_ratio)).wires().toPending() hardware.add( ring_sweep_wire.sweep(_wire, combine=True, transition="round", isFrenet=True).findSolid()) to_sweep = CQ(cutter_crosssection).wires().toPending() return to_sweep.sweep(_wire).findSolid()
def make_spacer_pcb(self): spacer = CQ().add(self.spacer_pcb).toPending().extrude( self.pcb_spacer_h) spacer = spacer.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h)) return (spacer)
def make_glass(self): glass = CQ().add(self.glass).toPending().extrude(self.glass_t) glass = glass.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h + self.cu_tower_h + self.silicone_working_t)) return glass
def make_dowels(self): dowels = CQ().add(self.dowels).toPending().extrude(self.dowel_height) dowels = dowels.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h - self.cu_base_t)) return dowels
def build(self, stacks_to_build: List[str] = [""]): if stacks_to_build == [""]: # build them all by default stacks_to_build = [x["name"] for x in self.stacks] drawing_layers_needed = [] for stack_instructions in self.stacks: if stack_instructions["name"] in stacks_to_build: for stack_layer in stack_instructions["layers"]: drawing_layers_needed += stack_layer["drawing_layer_names"] if "edge_case" in stack_layer: drawing_layers_needed.append(stack_layer["edge_case"]) drawing_layers_needed_unique = list(set(drawing_layers_needed)) # all the faces we'll need here layers = self.get_layers(self.sources, drawing_layers_needed_unique) self._layers = layers stacks = {} for stack_instructions in self.stacks: asy = cadquery.Assembly() # asy = None if stack_instructions["name"] in stacks_to_build: asy.name = stack_instructions["name"] z_base = 0 for stack_layer in stack_instructions["layers"]: t = stack_layer["thickness"] boundary_layer_name = stack_layer["drawing_layer_names"][ 0] # boundary layer must always be the first one listed layer_comp = cadquery.Compound.makeCompound( layers[boundary_layer_name].faces().vals()) if "array" in stack_layer: array_points = stack_layer["array"] else: array_points = [(0, 0, 0)] if len(stack_layer["drawing_layer_names"]) == 1: wp = CQ().sketch().push(array_points).face( layer_comp, mode="a", ignore_selection=False) else: wp = CQ().sketch().face(layer_comp, mode="a", ignore_selection=False) wp = wp.finalize().extrude( t) # the workpiece base is now made if len(stack_layer["drawing_layer_names"]) > 1: wp = wp.faces(">Z").workplane( centerOption="ProjectedOrigin").sketch() for drawing_layer_name in stack_layer[ "drawing_layer_names"][1:]: layer_comp = cadquery.Compound.makeCompound( layers[drawing_layer_name].faces().vals()) wp = wp.push(array_points).face( layer_comp, mode="a", ignore_selection=False) wp = wp.faces() if "edge_case" in stack_layer: edge_layer_name = stack_layer["edge_case"] layer_comp = cadquery.Compound.makeCompound( layers[edge_layer_name].faces().vals()) es = CQ().sketch().face(layer_comp) wp = wp.face(es.faces(), mode="i") wp = wp.clean() # wp = wp.finalize().cutThruAll() # this is a fail, but should work. if it's not a fail is slower than the below line wp = wp.finalize().extrude(-t, combine="cut") # give option to override calculated z_base if "z_base" in stack_layer: z_base = stack_layer["z_base"] new = wp.translate([0, 0, z_base]) asy.add(new, name=stack_layer["name"], color=cadquery.Color(stack_layer["color"])) z_base = z_base + t stacks[stack_instructions["name"]] = asy return stacks
def mkbase( aso: cadquery.Assembly, thickness: float, cshift, extents, hps, screw: SocketHeadCapScrew, pedistal_height: float, zbase: float, subs_boost: float, ): """the thermal base""" plate_name = "thermal_plate" vac_name = "vacuum_chuck" color = cadquery.Color("GOLD") fillet_outer = 2 fillet_inner = 10 chamfer = 1 corner_screw_depth = 4.5 pedistal_xy = (161, 152) pedistal_fillet = 10 dowelpts = [(-73, -66), (73, 66)] dowel_nominal_d = 3 # marked on drawing for pressfit with ⌀3K7 # vac chuck clamp screws vacscrew_length = 20 vacscrew = CounterSunkScrew(size="M6-1", fastener_type="iso14581", length=vacscrew_length, simple=no_threads) # SHK-M6-20-V2-A4 vacclamppts = [(-73, -54.75), (-73, 54.75), (73, -54.75), (73, 54.75)] # slot plate clamp screws spscrew_length = 8 spscrew = CounterSunkScrew(size="M3-0.5", fastener_type="iso14581", length=spscrew_length, simple=no_threads) # SHK-M3-8-V2-A4 # setscrew clamping stuff setscrew_len = 30 screw_well_depth = 3 setscrew_recess = pedistal_height + screw_well_depth setscrew = SetScrew(size="M6-1", fastener_type="iso4026", length=setscrew_len, simple=no_threads) # SSU-M6-30-A2 setscrewpts = [(-73, -43.5), (73, 43.5)] # waterblock nuts and holes wb_w = 177.8 wb_mount_offset_from_edge = 7.25 wb_mount_offset = wb_w / 2 - wb_mount_offset_from_edge waterblock_mount_nut = HexNutWithFlange( size="M6-1", fastener_type="din1665", simple=no_threads) # HFFN-M6-A2 wb_mount_points = [ (120, wb_mount_offset), (120, -wb_mount_offset), (-129, wb_mount_offset), (-129, -wb_mount_offset), ] # make the base chunk wp = CQ().workplane(**u.copo, offset=zbase).sketch() wp = wp.push([cshift]).rect(extents[0], extents[1], mode="a") wp = wp.finalize().extrude(thickness) wp: cadquery.Workplane # shouldn't have to do this (needed for type hints) # cut for waterblock mnt ears ear_square = 2 * wb_mount_offset wp = wp.faces("<X").workplane(**u.cobb).rect( xLen=extents[1] - 2 * ear_square, yLen=thickness, centered=True).cutBlind(-(extents[0] - wall_outer[0]) / 2) wp = wp.faces(">X").workplane(**u.cobb).rect( xLen=extents[1] - 2 * ear_square, yLen=thickness, centered=True).cutBlind(-(extents[0] - wall_outer[0]) / 2) wp = wp.edges("|Z exc (<<X or >>X)").fillet(fillet_inner) wp = wp.edges("|Z and (<<X or >>X)").fillet(fillet_outer) # pedistal wp = wp.faces(">Z").workplane(**u.copo, origin=( 0, 0, 0)).sketch().rect( *pedistal_xy).reset().vertices().fillet(pedistal_fillet) wp = wp.finalize().extrude(pedistal_height) hardware = cq.Assembly(None) # a place to keep the harware # corner screws wp = wp.faces("<Z").workplane(**u.copo, offset=-corner_screw_depth).pushPoints( hps).clearanceHole( fastener=screw, fit="Close", baseAssembly=hardware) wp = wp.faces("<Z[-2]").wires().toPending().extrude( corner_screw_depth, combine="cut") # make sure the recessed screw is not buried # dowel holes wp = wp.faces(">Z").workplane(**u.copo).pushPoints(dowelpts).hole( dowel_nominal_d + dowel3_delta_press, depth=pedistal_height) # waterblock mounting wp = wp.faces(">Z[-2]").workplane( **u.copo).pushPoints(wb_mount_points).clearanceHole( fastener=waterblock_mount_nut, counterSunk=False, fit="Loose", baseAssembly=hardware) # vac chuck stuff # split wp = wp.faces(">Z[-2]").workplane(**u.copo).split( keepTop=True, keepBottom=True).clean() btm_piece = wp.solids("<Z").first().edges("not %CIRCLE").chamfer( chamfer) top_piece = wp.solids(">Z").first().edges("not %CIRCLE").chamfer( chamfer) # hole array n_array_x = 4 n_array_y = 5 x_spacing = 35 y_spacing = 29 x_start = (n_array_x - 1) / 2 y_start = (n_array_y - 1) / 2 n_sub_array_x = 8 n_sub_array_y = 2 x_spacing_sub = 3 y_spacing_sub = 10 x_start_sub = (n_sub_array_x - 1) / 2 y_start_sub = (n_sub_array_y - 1) / 2 hole_d = 1 hole_cskd = 1.1 csk_ang = 45 # compute all the vac chuck vent hole points vac_hole_pts = [] # where the vac holes are drilled street_centers = [] # the distribution street y values for i in range(n_array_x): for j in range(n_array_y): for k in range(n_sub_array_x): for l in range(n_sub_array_y): ctrx = (i - x_start) * x_spacing ctry = (j - y_start) * y_spacing offx = (k - x_start_sub) * x_spacing_sub offy = (l - y_start_sub) * y_spacing_sub vac_hole_pts.append((ctrx + offx, ctry + offy)) street_centers.append((0, ctry + offy)) street_centers = list(set(street_centers)) # prune duplicates # boost substrates up so they can't slip under raise_square = (25, 25) raise_fillet = 1 top_piece = CQ(top_piece.findSolid()).faces(">Z").workplane( **u.copo).sketch().rarray( x_spacing, y_spacing, n_array_x, n_array_y).rect(*raise_square).reset().vertices().fillet( raise_fillet).finalize().extrude(subs_boost) # drill all the vac holes top_piece = top_piece.faces(">Z").workplane( **u.copo).pushPoints(vac_hole_pts).cskHole(diameter=hole_d, cskDiameter=hole_cskd, cskAngle=csk_ang) # clamping setscrew threaded holes top_piece = top_piece.faces(">Z").workplane().pushPoints( setscrewpts).tapHole( setscrew, depth=setscrew_recess, baseAssembly=hardware ) # bug prevents this from working correctly, workaround below # clamping setscrew downbumps in the thermal plate btm_piece = CQ(btm_piece.findSolid()).faces(">Z").workplane( **u.copo).pushPoints(setscrewpts).circle( vacscrew.clearance_hole_diameters["Close"] / 2).cutBlind(-screw_well_depth) # vac chuck clamping screws top_piece = top_piece.faces(">Z[-2]").workplane( **u.copo, origin=(0, 0, 0)).pushPoints(vacclamppts).clearanceHole( vacscrew, fit="Close", baseAssembly=hardware) # next line is a hack to make absolutely sure the screws are recessed top_piece = top_piece.faces(">Z[-2]").workplane( **u.copo, origin=(0, 0, 0)).pushPoints(vacclamppts).cskHole( vacscrew.clearance_hole_diameters["Close"], cskDiameter=vacscrew.head_diameter + 1, cskAngle=vacscrew.screw_data["a"]) btm_piece = btm_piece.faces(">Z").workplane(**u.copo, origin=( 0, 0, 0)).pushPoints(vacclamppts).tapHole( setscrew, depth=vacscrew_length - pedistal_height + 1) # threaded holes to attach to # mod the slot plate to include csk screws for clamping for name, part in asys["squirrel"].traverse(): if name == "slot_plate": sp_clamp_pts = [(p[0], p[1] + 5) for p in vacclamppts] sp = part.obj vch_shift_y = -37 vch_shift_x = 3 sp = sp.faces(">Z").workplane(**u.copo, origin=( 0, 0, 0)).rarray(vacclamppts[3][0] * 2 + vch_shift_x, vacclamppts[3][1] * 2 + vch_shift_y, 2, 2).clearanceHole(spscrew, fit="Close", baseAssembly=hardware) # next line is a hack to make absolutely sure the screws are recessed sp = sp.faces(">Z").workplane(**u.copo, origin=( 0, 0, 0)).rarray( vacclamppts[3][0] * 2 + vch_shift_x, vacclamppts[3][1] * 2 + vch_shift_y, 2, 2).cskHole(spscrew.clearance_hole_diameters["Close"], cskDiameter=spscrew.head_diameter + 1, cskAngle=spscrew.screw_data["a"]) part.obj = sp # make threaded holes to attach to, TODO: mark these as M3x0.5 threaded holes in engineering drawing top_piece = top_piece.faces(">Z[-2]").workplane( **u.copo, origin=(0, 0, 0)).rarray(vacclamppts[3][0] * 2 + vch_shift_x, vacclamppts[3][1] * 2 + vch_shift_y, 2, 2).tapHole(spscrew, depth=spscrew_length - 1, counterSunk=False) # compute the hole array extents for o-ring path finding sub_x_length = (n_sub_array_x - 1) * x_spacing_sub + hole_d array_x_length = (n_array_x - 1) * x_spacing + sub_x_length sub_y_length = (n_sub_array_y - 1) * y_spacing_sub + hole_d array_y_length = (n_array_y - 1) * y_spacing + sub_y_length # for the vac chuck fitting vac_fitting_chuck_offset = -0.5 * y_spacing fitting_tap_depth = 20 top_piece = top_piece.faces(">X").workplane(**u.cobb).center( vac_fitting_chuck_offset, 0).tapHole(vac_fitting_screw, depth=fitting_tap_depth) vac_chuck_fitting = cadquery.Assembly(a_vac_fitting.rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=-5), name="chuck_vac_fitting") hardware.add(vac_chuck_fitting, loc=top_piece.plane.location, name="vac chuck fitting") # handle the valve, part number 435-8101 a_valve = u.import_step( wrk_dir.joinpath("components", "VHK2-04F-04F.step")) # a_valve = a_valve.rotate(axisStartPoint=(0, 0, 0), axisEndPoint=(0, 1, 0), angleDegrees=90).translate((0, 7.5, 9)) a_valve = a_valve.translate((0, 7.5, 9)) valve_mnt_spacing = 16.5 valve_mnt_screw_length = 30 valve_body_width = 18 valve_mnt_hole_depth = 15 valve_mnt_screw = PanHeadScrew( size="M4-0.7", fastener_type="iso14583", length=valve_mnt_screw_length) # SHP-M4-30-V2-A4 btm_piece = btm_piece.faces(">X[-2]").workplane(**u.cobb).rarray( valve_mnt_spacing, 1, 2, 1).tapHole(valve_mnt_screw, depth=valve_mnt_hole_depth, counterSunk=False) # cut threaded holes btm_piece = btm_piece.faces(">X[-2]").workplane(**u.cobb).rarray( valve_mnt_spacing, 1, 2, 1).tapHole(valve_mnt_screw, depth=valve_mnt_screw_length - valve_body_width, counterSunk=False, baseAssembly=aso) # add screws aso.add(a_valve, loc=btm_piece.plane.location, name="valve") # handle the elbow, part number 306-5993 an_elbow = u.import_step( wrk_dir.joinpath("components", "3182_04_00.step")) an_elbow = an_elbow.rotate(axisStartPoint=(0, 0, 0), axisEndPoint=(0, 1, 0), angleDegrees=-90).rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=90) # rotate the elbow btm_pln = btm_piece.faces(">X[-2]").workplane( **u.cobb, offset=valve_body_width / 2).center(-26.65, 7.5) # position the elbow aso.add(an_elbow, loc=btm_pln.plane.location, name="elbow") # vac distribution network zdrill_loc = (pedistal_xy[0] / 2 - fitting_tap_depth, 0.5 * y_spacing) zdrill_r = 3 zdrill_depth = -pedistal_height / 2 - 2.5 top_piece = top_piece.faces("<Z").workplane(**u.cobb).pushPoints( [zdrill_loc]).circle(zdrill_r).cutBlind(zdrill_depth) highway_depth = 3 highway_width = 6 street_depth = 2 street_width = 1 top_piece = top_piece.faces("<Z").workplane(**u.cobb).sketch().push([ (zdrill_loc[0] / 2, zdrill_loc[1]) ]).slot(w=zdrill_loc[0], h=highway_width).finalize().cutBlind(-highway_depth) top_piece = top_piece.faces("<Z").workplane(**u.cobb).sketch().slot( w=pedistal_xy[0] - 2 * fitting_tap_depth, h=highway_width, angle=90).finalize().cutBlind(-highway_depth) # cut center highway top_piece = top_piece.faces("<Z").workplane( **u.cobb).sketch().push(street_centers).slot( w=array_x_length - hole_d, h=street_width).finalize().cutBlind( -street_depth) # cut streets # padding to keep the oring groove from bothering the vac holes groove_x_pad = 8 groove_y_pad = 16 # that's part number 196-4941 o_ring_thickness = 2 o_ring_inner_diameter = 170 # cut the o-ring groove top_piece = top_piece.faces("<Z").workplane(**u.cobb).mk_groove( ring_cs=o_ring_thickness, follow_pending_wires=False, ring_id=o_ring_inner_diameter, gland_x=array_x_length + groove_x_pad, gland_y=array_y_length + groove_y_pad, hardware=hardware) # cut the electrical contact screw mount holes vc_e_screw_spacing = 15 vc_e_screw_center_offset = 10 vc_e_screw_hole_depth = 12 vc_e_screw_screw_length = 8 vc_e_srew_type = "M3-0.5" e_dummy = SetScrew(vc_e_srew_type, fastener_type="iso4026", length=vc_e_screw_screw_length, simple=no_threads) # mark these chuck electrical connection screw holes in engineering drawing as M3x0.5 top_piece = top_piece.faces("<X").workplane(**u.cobb).center( vc_e_screw_center_offset, 0).rarray(vc_e_screw_spacing, 1, 2, 1).tapHole(e_dummy, depth=vc_e_screw_hole_depth) aso.add(btm_piece, name=plate_name, color=color) aso.add(top_piece, name=vac_name, color=color) aso.add(hardware.toCompound(), name="hardware", color=cadquery.Color(hardware_color))
def main(): # define where we'll read shapes from sources = [ Path.cwd().parent / "oxford" / "master.dxf", Path.cwd().parent / "oxford" / "derivatives" / "5x5_cluster_master.dxf", ] support_thickness = 0.65 feature_thickness = 0.2 shim_thickness = 0.5 support_color = "GOLDENROD" feature_color = "GRAY28" shim_color = "DARKGREEN" # define how we'll tile things spacing5 = 30 array5 = [(x * spacing5, y * spacing5, 0) for x, y in itertools.product(range(-2, 3), range(-2, 3))] instructions = [] instructions.append({ "name": "active_mask_stack", "layers": [ { "name": "active_support", "color": support_color, "thickness": support_thickness, "drawing_layer_names": [ "glass_extents", "aggressive_support_active", ], }, { "name": "active_feature", "color": feature_color, "thickness": feature_thickness, "drawing_layer_names": [ "glass_extents", "active_layer", ], }, { "name": "spacer_shim", "color": shim_color, "thickness": shim_thickness, "drawing_layer_names": [ "glass_extents", "spacer_shim", ], }, ], }) instructions.append({ "name": "active_mask_stack_5x5", "layers": [ { "name": "active_support", "color": support_color, "thickness": support_thickness, "drawing_layer_names": [ "outline_5x5", "aggressive_support_active", ], "edge_case": "inner_outline_5x5", "array": array5, }, { "name": "active_feature", "color": feature_color, "thickness": feature_thickness, "drawing_layer_names": [ "outline_no_alignment_5x5", "active_layer", ], "edge_case": "inner_outline_5x5", "array": array5, }, { "name": "spacer_shim", "color": shim_color, "thickness": shim_thickness, "drawing_layer_names": [ "outline_no_alignment_5x5", "spacer_shim", ], "edge_case": "inner_outline_5x5", "array": array5, }, ], }) instructions.append({ "name": "metal_mask_stack", "layers": [ { "name": "metal_support", "color": support_color, "thickness": support_thickness, "drawing_layer_names": [ "glass_extents", "aggressive_metal_support_tc_metal", "aggressive_metal_support_small_upper", "aggressive_metal_support_large_lower", ], }, { "name": "metal_feature", "color": feature_color, "thickness": feature_thickness, "drawing_layer_names": [ "glass_extents", "tc_metal", "pixel_electrodes_small_upper", "pixel_electrodes_large_lower", ], }, { "name": "spacer_shim_thin", "color": shim_color, "thickness": shim_thickness, "drawing_layer_names": [ "glass_extents", "spacer_shim_thin", ], }, ], }) instructions.append({ "name": "metal_mask_stack_5x5", "layers": [ { "name": "metal_support", "color": support_color, "thickness": support_thickness, "drawing_layer_names": [ "outline_5x5", "aggressive_metal_support_tc_metal", "aggressive_metal_support_small_upper", "aggressive_metal_support_large_lower", ], "edge_case": "inner_outline_5x5", "array": array5, }, { "name": "metal_feature", "color": feature_color, "thickness": feature_thickness, "drawing_layer_names": [ "outline_no_alignment_5x5", "tc_metal", "pixel_electrodes_small_upper", "pixel_electrodes_large_lower", ], "edge_case": "inner_outline_5x5", "array": array5, }, { "name": "spacer_shim_thin", "color": shim_color, "thickness": shim_thickness, "drawing_layer_names": [ "outline_no_alignment_5x5", "spacer_shim_thin", ], "edge_case": "inner_outline_5x5", "array": array5, }, ], }) ttt = TwoDToThreeD(instructions=instructions, sources=sources) to_build = ["active_mask_stack", "metal_mask_stack"] # to_build = ["active_mask_stack_5x5"] # to_build = [""] # all of them asys = ttt.build(to_build) # asy: cadquery.Assembly = list(asys.values())[0] # TODO:take more than the first value for stack_name, asy in asys.items(): if "show_object" in globals(): # we're in cq-editor assembly_mode = True # at the moment, when true we can't select/deselect subassembly parts if assembly_mode: show_object(asy) else: for key, val in asy.traverse(): shapes = val.shapes if shapes != []: c = cq.Compound.makeCompound(shapes) odict = {} if val.color is not None: co = val.color.wrapped.GetRGB() rgb = (co.Red(), co.Green(), co.Blue()) odict["color"] = rgb show_object(c.locate(val.loc), name=val.name, options=odict) else: # save assembly asy.save( str(Path(__file__).parent / "output" / f"{stack_name}.step")) asy.save( str(Path(__file__).parent / "output" / f"{stack_name}.glb"), "GLTF") # cadquery.exporters.assembly.exportCAF(asy, str(Path(__file__).parent / "output" / f"{stack_name}.std")) # cq.Shape.exportBrep(cq.Compound.makeCompound(itertools.chain.from_iterable([x[1].shapes for x in asy.traverse()])), str(Path(__file__).parent / "output" / f"{stack_name}.brep")) save_individual_stls = False save_individual_steps = False save_individual_breps = False save_individual_dxfs = True # save each shape individually for key, val in asy.traverse(): shapes = val.shapes if shapes != []: c = cq.Compound.makeCompound(shapes) if save_individual_stls == True: cadquery.exporters.export( c.locate(val.loc), str( Path(__file__).parent / "output" / f"{stack_name}-{val.name}.stl")) if save_individual_steps == True: cadquery.exporters.export( c.locate(val.loc), str( Path(__file__).parent / "output" / f"{stack_name}-{val.name}.step")) if save_individual_breps == True: cq.Shape.exportBrep( c.locate(val.loc), str( Path(__file__).parent / "output" / f"{stack_name}-{val.name}.brep")) if save_individual_dxfs == True: cl = c.locate(val.loc) bb = cl.BoundingBox() zmid = (bb.zmin + bb.zmax) / 2 nwp = CQ("XY", origin=(0, 0, zmid)).add(cl) dxface = nwp.section() cadquery.exporters.export( dxface, str( Path(__file__).parent / "output" / f"{stack_name}-{val.name}.dxf"), cadquery.exporters.ExportTypes.DXF)
def lid(): """Lid for cooling block.""" lid = cq.Workplane("XY").box(lid_l, lid_w, lid_h) lid = lid.translate((0, 0, (block_h + lid_h) / 2)) # add cut for ground ring ground_ring_cut = cq.Workplane("XY").box(ground_ring_lid_cut_l, ground_ring_lid_cut_w, lid_h) ground_ring_cut = ground_ring_cut.translate(( -(lid_l - ground_ring_lid_cut_l) / 2, -(lid_w - ground_ring_lid_cut_w) / 2, (block_h + lid_h) / 2, )) ground_ring_cut = ground_ring_cut.edges(">X and >Y and |Z").fillet( ground_screw_lid_cut_r) lid = lid.cut(ground_ring_cut) # add holes for lid fasteners lid = lid.faces(">Z").workplane(centerOption="CenterOfBoundBox") lid = lid.pushPoints(cs_holes) lid = lid.cskHole(2 * cs_screw_clearance_r, 2 * cs_screw_cap_r, cs_screw_angle, cs_screw_tread_h) # add holes for extrusion screws lid = lid.faces(">Z").workplane(centerOption="CenterOfBoundBox") lid = lid.pushPoints(extrusion_holes) lid = lid.hole(2 * extrusion_screw_clearance_r) # add holes for wire passthroughs lid = lid.faces(">Z").workplane(centerOption="CenterOfBoundBox") lid = lid.pushPoints(pt_hole_centers) lid = lid.hole(2 * pt_hole_clearance_r) # add cuts to turn wire passthroughs into U-shapes u_cut = cq.Workplane("XY").box(pt_u_x, pt_u_y, lid_h) for x, y in pt_u_centers: _u_cut = u_cut _u_cut = _u_cut.translate((x, y, (block_h + lid_h) / 2)) lid = lid.cut(_u_cut) # for making ure the chamfer cutting tool can get down to where it needs to be chamfer_allowance = (CQ().copyWorkplane( lid.faces(">Z").workplane( centerOption="CenterOfBoundBox")).pushPoints(cs_holes).circle( lid_chamfer_clearance).extrude(lid_water_thread_length - lid_h)) # for a pocket in the middle to reduce weight recess = (CQ().copyWorkplane( lid.faces(">Z").workplane(centerOption="CenterOfBoundBox", invert=True)).box(lid_recess_xy, lid_recess_xy, lid_recess_depth, centered=(True, True, False))) # for a block around the hose connections to add more connector thread purchase thread_block = (CQ().copyWorkplane( lid.faces("<Z").workplane( centerOption="CenterOfBoundBox", invert=True)).center(0, water_port_hole_centers[0][1]).box( lid_threadblock_xy[0], lid_threadblock_xy[1], lid_water_thread_length, centered=(True, True, False)).cut(chamfer_allowance).edges( '|Z').fillet(lid_fillet_r)) # cut the thread block from the recess negative and then fillet that # this allows the result to have fillets that are manufacturable recess = recess.cut(thread_block).edges('|Z').fillet(lid_fillet_r) # cut out the recess then add the thread block lid = lid.cut(recess).union(thread_block) # add holes for water ports (drill holes up form bottom) lid = lid.faces("<Z").workplane(centerOption="CenterOfBoundBox", invert=True) lid = lid.pushPoints(water_port_hole_centers) lid = lid.circle(water_port_thread_tap_r).cutThruAll() return lid
def make_oringer( self: cq.Workplane, board_width: float = 84.12, board_inner_depth: float = 9.271, board_outer_depth: float = 9.271, part_thickness: float = 0, wall_depth: float = 0, screw="M3-0.5", pt_asy: cadquery.Assembly = None, pcb_asy: cadquery.Assembly = None, hw_asy: cadquery.Assembly = None, ) -> cq.Workplane: logger = logging.getLogger(__name__) if wall_depth == 0: # if depth is not given do our best to find it wall_depth = u.find_length(self, along="normal", bb_method=False) if part_thickness == 0: # if thickness is not given, use half the wall thickness part_thickness = wall_depth / 2 pcbt = 1.6 # pcb thickness washert = 0.5 # washer thickness screw_nominal_d = 3 screw_head_nominal_d = 6 # header specific for making the pin0 holes, probably doesn't belong here, but it's too convenient... non_notch_side_chunk_width = 0.381 # is 0.15 in non_chunk_con_width = 8.89 # is 0.35 in pin0_offsetx = non_chunk_con_width / 2 + non_notch_side_chunk_width + 2.54 / 2 pin0_offsety_25 = 2.54 * (25 - 1) / 2 pin0_offsety_20 = 2.54 * (20 - 1) / 2 pin0_holed = 1 p0pts = [ ] # the pin pin 1 points for the two connectors (for checking the correctness of the PCB designs) p0pts.append((pin0_offsety_25, pin0_offsetx)) p0pts.append((-pin0_offsety_20 + 2.5 * 2.54, -(wall_depth + pin0_offsetx))) block_width = 7 block_height_nominal = 6 support_block = (block_width, block_height_nominal - washert ) # actual support block (leaves room for washer) pcb_corner = 2 pt_pcb_mount_hole_offset = (4.445, block_width / 2) # from corners pcb_scr_len = 12 # SHP-M3-12-V2-A2, round(block_height_nominal + pcbt + 4) pt_fix_scr_len = 10 # SHK-M3-10-V2-A2, round(wall_depth * 0.8) pt_fix_wall_buffer = 1 # amount of wall to leave behind the threaded screw hole fix_scr = CounterSunkScrew(size=screw, fastener_type="iso14581", length=pt_fix_scr_len) pcb_scr = PanHeadScrew(size=screw, fastener_type="iso14583", length=pcb_scr_len) # washer = CheeseHeadWasher(size=screw, fastener_type="iso7092") # nylock nut = HNN-M3-A2 oring_cs = 1 # oring thickness min_radius = oring_cs * 3 # min inner bend radius min_wall = 0.8 # walls should not be mfg'd thinner than this min_gap = 0.25 # cutting tolerances prevent smaller gaps between things gland_width = groovy.get_gland_width(oring_cs) # effective_gland_width = (round(gland_width * 100) + 1) / 100 # rounded up to the nearest 0.01mm # logger.info(f"Using {effective_gland_width=}") # some important radii for construction to ensure we don't overbend the o-ring minr1 = min_radius - min_wall minr2 = min_radius + min_wall + gland_width # actual support block centers sbpts = [] sbx = board_width / 2 - block_width / 2 sby = ((-pcbt / 2 - washert) + (-pcbt / 2 - block_height_nominal)) / 2 sbpts.append((sbx, sby)) sbpts.append((-sbx, sby)) in_off = minr1 * 2**-0.5 - min_gap * 2**0.5 / 2 # exact inward offset to get min_gap spacing with minr1 fillets ffo = minr1 * (1 - 2**-0.5) + min_gap * (2**0.5 / 2) co_tw = minr1 * 2 + min_gap # width of the thin part of the cutout (so that the cutting tool can easily fit) max_slot_y = ffo + pcbt + block_height_nominal + ffo # width at the slot at its max if co_tw > max_slot_y: co_tw = max_slot_y scy = -pcbt / 2 - block_height_nominal + in_off # y coordinate for the inner, small radius circle scx = board_width / 2 - block_width + in_off # small, inner circle x value tcy = pcbt / 2 - in_off # top circles center y value tcx = board_width / 2 - in_off # top circles center c values tcpts = [] # top circle points tcpts.append((tcx, tcy)) tcpts.append((-tcx, tcy)) ocpts1 = [] # outer circle points for positive x ocpts1.append((tcx, tcy)) ocpts1.append((tcx, scy)) ocpts2 = [] # outer circle points for negative x ocpts2.append((-tcx, tcy)) ocpts2.append((-tcx, scy)) bcpts1 = [] # bottom circle points for positive x bcpts1.append((tcx, scy)) bcpts1.append((scx, scy)) bcpts2 = [] # bottom circle points for negative x bcpts2.append((-tcx, scy)) bcpts2.append((-scx, scy)) icpts1 = [] # inner circle points for positive x icpts1.append((scx, scy)) icpts1.append((scx, tcy)) icpts2 = [] # inner circle points for negative x icpts2.append((-scx, scy)) icpts2.append((-scx, tcy)) swp = CQ().sketch() # need support block shapes to fill in gaps swp.push(sbpts).rect(*support_block) # the fillets at the bottom collide and should be unified with a circle, but the ones at the sides don't if (2 * minr1 > 2 * ffo + block_width) and (2 * minr1 < max_slot_y): scx = board_width / 2 - block_width / 2 # new center point for circles tcpts = [] # top circle points tcpts.append((scx, tcy)) tcpts.append((-scx, tcy)) ocpts1 = [] # outer circle points for positive x ocpts1.append((scx, tcy)) ocpts1.append((scx, scy)) ocpts2 = [] # outer circle points for negative x ocpts2.append((-scx, tcy)) ocpts2.append((-scx, scy)) # make the right circle hull swp.push(ocpts1).circle( minr1, mode="c", tag="c").reset().edges(tag="c").hull().clean().reset() # make the left circle hull swp.push(ocpts2).circle( minr1, mode="c", tag="d").reset().edges(tag="d").hull().clean().reset() # the fillets at the side collide and should be unified with a circle elif 2 * minr1 >= max_slot_y: cpts = [] # center points for new circles scy = (pcbt / 2 + (-pcbt / 2 - block_height_nominal)) / 2 tcpts = [] # top circle points tcpts.append((tcx, scy)) tcpts.append((-tcx, scy)) # normal case, no fillets collide else: # make the right outer circle hull swp.push(ocpts1).circle( minr1, mode="c", tag="c").reset().edges(tag="c").hull().clean().reset() # make the left outer circle hull swp.push(ocpts2).circle( minr1, mode="c", tag="d").reset().edges(tag="d").hull().clean().reset() # make the right bottom circle hull swp.push(bcpts1).circle( minr1, mode="c", tag="e").reset().edges(tag="e").hull().clean().reset() # make the left bottom circle hull swp.push(bcpts2).circle( minr1, mode="c", tag="f").reset().edges(tag="f").hull().clean().reset() bcy = pcbt / 2 + ffo - (co_tw + minr2 ) # y coordinate for the large radius circle # make the top circle hull swp.push(tcpts).circle( minr1, mode="c", tag="g").reset().edges(tag="g").hull().clean().reset() # do all the big circle stuff only if the outer fillets haven't merged if not (2 * minr1 > max_slot_y): o = scy - bcy # opposite triangle side length (along y) h = minr2 + minr1 # hypotenuse if o < 0: # the circles have moved apart: big one above small one (and the trig breaks) a = h elif o > h: # the circles have moved apart (and the trig breaks) a = h # adjacent (along x) else: a = h * math.cos(math.asin(o / h)) # a = o/math.tan(math.asin(o/h)) # adjacent (along x) bcx = scx - a # big circle x # bcy = pcbt/2+ffo-(co_tw+minr2) bcpts = [] bcpts.append((-bcx, bcy)) bcpts.append((bcx, bcy)) swp.push([(-scx, scy), (scx, scy)]).circle(minr1).clean().reset() if o < 0: # the circles have moved apart: big one above small one scy = bcy if 2 * minr1 < 2 * ffo + block_width: # the bottom fillets haven't merged # make the left inner circle hull swp.push(icpts1).circle( minr1, mode="c", tag="h").reset().edges(tag="h").hull().clean().reset() # make the right inner circle hull swp.push(icpts2).circle( minr1, mode="c", tag="i").reset().edges(tag="i").hull().clean().reset() swp.polygon([(-bcx, bcy), (bcx, bcy), (scx, scy), (scx, 0), (-scx, 0), (-scx, scy), (-bcx, bcy)]).clean().reset() swp.push(bcpts).circle( minr2, mode="s").clean().reset() # cut away the large circles swp.push([(0, bcy)]).rect( 2 * bcx, minr2 * 2, mode="s").clean().reset() # cut away the space between the circles through_face = swp.finalize().extrude(-1).faces( ">>Z").val() # get just the face for the through cut swp = swp.wires().offset(min_wall + gland_width / 2).clean().reset() # inner edge of ogland o_face = swp.finalize().extrude(-1).faces( ">>Z").val() # get just the face for the oring path wire # passthrough face pfw = min_wall + gland_width + min_wall + ffo + board_width + ffo + min_wall + gland_width + min_wall # passthrough face width pfha = min_wall + gland_width + min_wall + screw_nominal_d + ( screw_head_nominal_d / 2 - screw_nominal_d / 2) + min_wall # passthrough face height above cutout top edge pfhb1 = co_tw + min_wall + gland_width + min_wall + screw_nominal_d + ( screw_head_nominal_d / 2 - screw_nominal_d / 2) + min_wall # passthrough face height below cutout top edge pfhb2 = ffo + pcbt + block_height_nominal + ffo + min_wall + gland_width + min_wall # passthrough face height below cutout top edge if limited by support block clearance if pfhb2 > pfhb1: # if the part below the support blocks would be lower, use that to determine the face height pfhb = pfhb2 else: pfhb = pfhb1 pfha_pcb = pfha + pcbt / 2 + ffo # passthrough face height above PCB middle pfh = pfha + pfhb # passthrough face height pfx = 0 pfy = -pfh / 2 + pfha_pcb pf_ctr = (pfx, pfy) # passthrough face center pfdim = (pfw, pfh) # passthrough face dims pf_fillets = 5 # fillets to corners of passthrough face pfwp = CQ().sketch() # make passthrough face sketch workplane pfwp = pfwp.push([pf_ctr]).rect(*pfdim).reset() pfwp = pfwp.vertices().fillet(pf_fillets).clean().reset() # swp = swp.wires().offset(gland_width / 2 + min_wall).clean().reset() # edge of passthrough part # + screw_nominal_d + (screw_head_nominal_d / 2 - screw_nominal_d / 2) + min_wall passthrough_face = pfwp.finalize().extrude(-1).faces( ">>Z").val() # get just the face for the passthrough part pfwp = pfwp.wires().offset(min_gap).clean().reset() # edge of recess_cut recess_face = pfwp.finalize().extrude(-1).faces( ">>Z").val() # get just the face for the recess # fastening screw hole points fhps = [] fhps.append(((board_width - 2 * block_width / 2) / 2, pcbt / 2 + ffo + min_wall + gland_width + min_wall + fix_scr.clearance_hole_diameters["Close"] / 2)) fhps.append((-(board_width - 2 * block_width / 2) / 2, pcbt / 2 + ffo + min_wall + gland_width + min_wall + fix_scr.clearance_hole_diameters["Close"] / 2)) fhps.append((bcx, pcbt / 2 + ffo - co_tw - min_wall - gland_width - min_wall - fix_scr.clearance_hole_diameters["Close"] / 2)) fhps.append((-bcx, pcbt / 2 + ffo - co_tw - min_wall - gland_width - min_wall - fix_scr.clearance_hole_diameters["Close"] / 2)) def _make_pcb(what): """build the actual passthrough PCB""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what pcb = CQ().workplane(offset=-wall_depth - board_inner_depth) pcb = pcb.rect(board_width, pcbt).extrude(until=board_inner_depth + wall_depth + board_outer_depth) pcb = pcb.edges("|Y").fillet(pcb_corner) # put in screws with holes hardware = cadquery.Assembly() pcb = pcb.faces(">Y").workplane(**u.cobb).rarray( board_width - 2 * block_width / 2, board_inner_depth + board_outer_depth + wall_depth - 2 * pt_pcb_mount_hole_offset[0], 2, 2).clearanceHole(pcb_scr, fit="Close", counterSunk=False, baseAssembly=hardware) if hw_asy is not None: hw_asy.add(hardware, loc=base * loc) # put in pin0 holes pcb = pcb.faces(">Y").workplane(**u.copo, origin=(0, 0, 0)).pushPoints(p0pts).circle( pin0_holed / 2).cutThruAll() return pcb.findSolid().moved(base * loc) def _make_pt(what): """build a passthrough component""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what hardware = cadquery.Assembly() passthrough = CQ().add(passthrough_face) passthrough = passthrough.wires().toPending().extrude( -part_thickness) # extrude the bulk slotd = pcbt + 2 * min_gap passthrough = passthrough.workplane( centerOption="ProjectedOrigin").slot2D( length=board_width + slotd / 2, diameter=slotd, angle=0).cutThruAll() # cut the pcb slot # TODO: retool some geometry because this cutout could possibly interfere with the oring gland for thick PCBs # cut the oring groove cq.Workplane.mk_groove = groovy.mk_groove oring_path = o_face.outerWire().translate((0, 0, -part_thickness)) passthrough = passthrough.faces("<<Z").workplane( **u.copo).add(oring_path).toPending().mk_groove(ring_cs=oring_cs, hardware=hardware) # cut the fastening screw holes passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).pushPoints(fhps).clearanceHole(fix_scr, fit="Close", baseAssembly=hardware) # add the support towers in_post_length = wall_depth + board_inner_depth passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).sketch().push(sbpts).rect( *support_block).finalize().extrude(-in_post_length) passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).sketch().push(sbpts).rect( *support_block).finalize().extrude(board_outer_depth) # mount holes pcb_center_z = ((board_outer_depth) - (wall_depth + board_inner_depth)) / 2 passthrough = passthrough.faces("+Y").faces(">>Z").workplane( **u.copo, origin=(0, 0, pcb_center_z)).rarray( board_width - 2 * pt_pcb_mount_hole_offset[1], board_inner_depth + board_outer_depth + wall_depth - 2 * pt_pcb_mount_hole_offset[0], 2, 2).clearanceHole(pcb_scr, fit="Close", counterSunk=False) passthrough = passthrough.edges("<<Z or >>Z").edges("|Y").fillet( pcb_corner) passthrough = passthrough.edges( "<<Z[-1] or <<Z[-2] or <<Z[-3] or >>Z[-1] or >>Z[-2] or >>Z[-3]" ).chamfer(0.5) if hw_asy is not None: hw_asy.add(hardware, loc=base * loc) return passthrough.findSolid().moved(base * loc) def _make_neg(what): """makes a negative shape to be cut out of the parent walls""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what # fastener threaded holes # TODO: mark these holes as "M3-0.5 threaded" in the engineering drawing fhs = CQ().pushPoints(fhps).circle(fix_scr.tap_hole_diameters["Soft"] / 2).extrude(-wall_depth + pt_fix_wall_buffer) nwp = CQ().add(through_face) through = nwp.wires().toPending().extrude(-wall_depth) nwp2 = CQ().add(recess_face) recess = nwp2.wires().toPending().extrude(-part_thickness) neg = recess.union(through).union(fhs) return neg.findSolid().moved(base * loc) rslt = self.each(_make_neg, useLocalCoordinates=False, combine="cut", clean=True) # pass out the passthrough geometry if pt_asy is not None: passthroughs = self.each(_make_pt, useLocalCoordinates=False, combine=False).vals() for i, passthrough in enumerate(passthroughs): pt_asy.add(passthrough.Solids()[0], name=f"passthrough {i}") # pass out the pcb geometry if pcb_asy is not None: # pcbs = self.eachpoint(lambda loc: _make_pcb().moved(loc), useLocalCoordinates=True, combine=False).vals() pcbs = self.each(_make_pcb, useLocalCoordinates=False, combine=False).vals() for i, pcb in enumerate(pcbs): pcb_asy.add(pcb.Solids()[0], name=f"pcb {i}") return rslt
def _makeNegative(center): """ Generates the pocket shape we'll be cutting out """ # the connector pocket's dimensions pocket_w = connector_width + con_clearance * 2 pocket_l = con_len + con_clearance * 2 pocket_d = connector_height + con_clearance # find the source thing's thickness at the cut point swiss = CQ(self.plane).add(self.findSolid()) cheese = CQ(self.plane).add(self.findSolid()).pushPoints( [center]).circle(0.5).cutThruAll() core = swiss.cut(cheese) this_thikness = u.find_length(core, along="normal", bb_method=False) min_thickness = pocket_d + min_gp_connector_pocket_spacing + gp_depth # check that it's thick enough here if this_thikness < min_thickness: raise (ValueError( f"The part is too thin (thickness = {this_thikness}) at {center} to cut the PCB pocket" )) # make a box to subtract from start_box = CQ("XY").box(100, 100, this_thikness, centered=(True, True, False)) # make a simple pocket that we'll use for the chamfer operation result = start_box.faces("<Z").rect(pocket_w, pocket_l).cutBlind(pocket_d) # chamfer the connector edge result = result.faces("<Z").edges("not(<X or >X or <Y or >Y)").chamfer( chamfer_length) # now make the undercut pocket CQ.undercutRelief2D = u.undercutRelief2D result = result.faces("<Z").workplane().undercutRelief2D( pocket_l, pocket_w, diameter=r * 2, angle=90, kind=kind).cutBlind(pocket_d) # cut out the glue pocket slot_width = c.pcb_thickness + 2 * gp_buffer slot_len = pcb_len + slot_width result = result.faces(">Z").workplane().slot2D( slot_len, slot_width, angle=90).cutBlind(-gp_depth) # cut out the pcb passthrough slot slot_width = c.pcb_thickness + 2 * pcb_clearance slot_len = pcb_len + slot_width result = result.faces(">Z").workplane().slot2D(slot_len, slot_width, angle=90).cutThruAll() # invert the geometry and rotate the negative negative = start_box.cut(result) negative = negative.rotate((0, 0, 0), (0, 0, 1), angle) to_cut = negative.findSolid().locate(center) to_cut = to_cut.mirror("XY") return to_cut
def _make_pt(what): """build a passthrough component""" # this copies some logic in the eachpoint() function so that we can use each() which is safer base_plane = self.plane base = base_plane.location if isinstance(what, (cq.Vector, cq.Shape)): loc = base.inverse * cq.Location(base_plane, what.Center()) elif isinstance(what, cq.Sketch): loc = base.inverse * cq.Location(base_plane, what._faces.Center()) else: loc = what hardware = cadquery.Assembly() passthrough = CQ().add(passthrough_face) passthrough = passthrough.wires().toPending().extrude( -part_thickness) # extrude the bulk slotd = pcbt + 2 * min_gap passthrough = passthrough.workplane( centerOption="ProjectedOrigin").slot2D( length=board_width + slotd / 2, diameter=slotd, angle=0).cutThruAll() # cut the pcb slot # TODO: retool some geometry because this cutout could possibly interfere with the oring gland for thick PCBs # cut the oring groove cq.Workplane.mk_groove = groovy.mk_groove oring_path = o_face.outerWire().translate((0, 0, -part_thickness)) passthrough = passthrough.faces("<<Z").workplane( **u.copo).add(oring_path).toPending().mk_groove(ring_cs=oring_cs, hardware=hardware) # cut the fastening screw holes passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).pushPoints(fhps).clearanceHole(fix_scr, fit="Close", baseAssembly=hardware) # add the support towers in_post_length = wall_depth + board_inner_depth passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).sketch().push(sbpts).rect( *support_block).finalize().extrude(-in_post_length) passthrough = passthrough.faces(">Z").workplane( **u.copo, origin=(0, 0, 0)).sketch().push(sbpts).rect( *support_block).finalize().extrude(board_outer_depth) # mount holes pcb_center_z = ((board_outer_depth) - (wall_depth + board_inner_depth)) / 2 passthrough = passthrough.faces("+Y").faces(">>Z").workplane( **u.copo, origin=(0, 0, pcb_center_z)).rarray( board_width - 2 * pt_pcb_mount_hole_offset[1], board_inner_depth + board_outer_depth + wall_depth - 2 * pt_pcb_mount_hole_offset[0], 2, 2).clearanceHole(pcb_scr, fit="Close", counterSunk=False) passthrough = passthrough.edges("<<Z or >>Z").edges("|Y").fillet( pcb_corner) passthrough = passthrough.edges( "<<Z[-1] or <<Z[-2] or <<Z[-3] or >>Z[-1] or >>Z[-2] or >>Z[-3]" ).chamfer(0.5) if hw_asy is not None: hw_asy.add(hardware, loc=base * loc) return passthrough.findSolid().moved(base * loc)
def make_thing(self, nx=5, ny=5): s = self co = "CenterOfBoundBox" fudge = 1 unit_x = s.x_nom + s.xy_extra + s.x_spacing unit_y = s.y_nom + s.xy_extra + s.y_spacing x_len = nx * unit_x y_len = ny * unit_y z_len = s.shelf_height + s.wall_height one_void = CQ().box(s.x_nom + s.xy_extra, s.y_nom + s.xy_extra, s.shelf_height, centered=(True, True, False)).edges("|Z").fillet( s.cut_tool_diameter / 2) tweezer_void = CQ().box(unit_x + fudge, s.tweezer_allowance_width, s.wall_height + s.tweezer_allowance_depth, centered=(True, True, False)) CQ.undercutRelief2D = tbutil.undercutRelief2D h00 = CQ().box(x_len, y_len, z_len, centered=(True, True, False)) # limits box #h01 = h00.faces('>Z').workplane().rarray(x_len/nx, y_len/ny, nx, ny, center=True).rect(unit_x+fudge, s.tweezer_allowance_width).cutBlind(-s.wall_height-s.tweezer_allowance_depth) # x tweezer cuts #h02 = h01.faces('>Z').workplane().rarray(x_len/nx, y_len/ny, nx, ny, center=True).rect(s.tweezer_allowance_width, unit_y+fudge).cutBlind(-s.wall_height-s.tweezer_allowance_depth) # y tweezer cuts h01 = h00.faces('>Z').workplane().rarray( x_len / nx, y_len / ny, nx, ny, center=True).undercutRelief2D( s.y_nom + s.xy_extra, s.y_nom + s.xy_extra, diameter=s.cut_tool_diameter).cutBlind( -s.wall_height) # substrate pockets all_voids = h00.faces('<Z').workplane(invert=True).rarray( x_len / nx, y_len / ny, nx, ny, center=True).eachpoint( lambda l: one_void.val().located(l)) # voids under h02 = h01.cut(all_voids) tweezer_voids = h00.faces('>Z').workplane(invert=True).rarray( x_len / nx, y_len / ny, nx, ny, center=True).eachpoint( lambda l: tweezer_void.val().located(l)) # tweezer voids h03 = h02.cut(tweezer_voids) tweezer_voids2 = h00.faces('>Z').workplane(invert=True).rarray( x_len / nx, y_len / ny, nx, ny, center=True).eachpoint(lambda l: tweezer_void.rotate( (0, 0, 0), (0, 0, 1), 90).val().located(l)) # tweezer voids h04 = h03.cut(tweezer_voids2) return h04
def get_ventscrews_b(self): ventscrew = self.vent_screw.translate((0, 0, 3.3)) ventscrews = CQ().pushPoints(self.screw_spots).eachpoint( lambda loc: ventscrew.val().moved(loc), True) return ventscrews
def build(self, stacks_to_build: List[str] = [""]): if stacks_to_build == [""]: # build them all by default stacks_to_build = [x["name"] for x in self.stacks] drawing_layers_needed = [] for stack_instructions in self.stacks: if stack_instructions["name"] in stacks_to_build: for stack_layer in stack_instructions["layers"]: drawing_layers_needed += stack_layer["drawing_layer_names"] if "edge_case" in stack_layer: drawing_layers_needed.append(stack_layer["edge_case"]) drawing_layers_needed_unique = list(set(drawing_layers_needed)) # all the wires we'll need here wires = self.get_wires(self.sources, drawing_layers_needed_unique) stacks = {} for stack_instructions in self.stacks: # asy = cadquery.Assembly() asy = None if stack_instructions["name"] in stacks_to_build: # asy.name = stack_instructions["name"] z_base = 0 for stack_layer in stack_instructions["layers"]: t = stack_layer["thickness"] boundary_layer_name = stack_layer["drawing_layer_names"][ 0] # boundary layer must always be the first one listed w0 = wires[boundary_layer_name][0] wp = CQ().sketch().face(w0) for w in wires[boundary_layer_name][1::]: wp = wp.face(w, mode="s") wp = wp.finalize().extrude(t) # the workpiece is now made wp = wp.faces(">Z").sketch() if "array" in stack_layer: array_points = stack_layer["array"] else: array_points = [(0, 0, 0)] for drawing_layer_name in stack_layer[ "drawing_layer_names"][1:]: some_wires = wires[drawing_layer_name] for awire in some_wires: wp = wp.push(array_points).face( awire, mode="a", ignore_selection=False) wp = wp.faces() if "edge_case" in stack_layer: edge_wire = wires[stack_layer["edge_case"]][0] wp = wp.face(edge_wire, mode="i") wp = wp.clean() # wp = wp.finalize().cutThruAll() # this is a fail, but should work wp = wp.finalize().extrude(-t, combine="cut") new = wp.translate([0, 0, z_base]) if asy is None: # some silly hack needed to work around https://github.com/CadQuery/cadquery/issues/993 asy = cadquery.Assembly(new, name=stack_layer["name"], color=cadquery.Color( stack_layer["color"])) # asy.name = stack_instructions["name"] else: asy.add(new, name=stack_layer["name"], color=cadquery.Color(stack_layer["color"])) z_base = z_base + t stacks[stack_instructions["name"]] = asy return stacks
def make_silicone(self): silicone = CQ().add(self.silicone).toPending().extrude(self.silicone_t) silicone = silicone.translate( (0, 0, -self.pcb_thickness / 2 - self.pcb_spacer_h + self.cu_tower_h)) return silicone
def mkwalls( aso: cadquery.Assembly, height: float, cshift, extents, hps, zbase: float, ): """the chamber walls""" name = "walls" color = cadquery.Color("GRAY55") thickness = 12 inner = (extents[0] - 2 * thickness, extents[1] - 2 * thickness) inner_shift = cshift outer_fillet = 2 inner_fillet = 6 chamfer = 0.75 nut = HexNut(size="M5-0.8", fastener_type="iso4033") # HNN-M5-A2 flat_to_flat = math.sin(60 * math.pi / 180) * nut.nut_diameter + 0.25 gas_fitting_hole_diameter = 20.6375 # 13/16" gas_fitting_recess = 6.35 gas_fitting_flat_to_flat = 22.22 + 0.28 gas_fitting_diameter = 25.66 + 0.34 back_holes_shift = 45 back_holes_spacing = 27 front_holes_spacing = 75 fitting_step_xy = ( 3, 15) # dims of the little step for the vac fitting alignment fitting_step_center = (-fitting_step_xy[0] / 2 + inner[0] / 2 + cshift[0], extents[1] / 2 - fitting_step_xy[1] / 2 - thickness) wp = CQ().workplane(offset=zbase).sketch() wp = wp.push([cshift ]).rect(extents[0], extents[1], mode="a").reset().vertices().fillet(outer_fillet) wp = wp.push([inner_shift]).rect(inner[0], inner[1], mode="s").reset() dummy_xy = (fitting_step_xy[0], inner[1]) dummy_center = (fitting_step_center[0], 0) wp = wp.push([dummy_center]).rect( *dummy_xy, mode="a") # add on a dummy bit that we'll mostly subtract away wp = wp.finalize().extrude(height).edges("|Z").fillet(inner_fillet) sub_xy = (40, inner[1] - fitting_step_xy[1]) sub_center = (-sub_xy[0] / 2 + inner[0] / 2 + cshift[0], -fitting_step_xy[1] / 2) wp2 = CQ().workplane(offset=zbase).sketch().push([sub_center ]).rect(*sub_xy, mode="a") wp2 = wp2.finalize().extrude(height).edges("|Z").fillet(inner_fillet) wp = wp.cut(wp2) # wp = CQ().workplane(offset=zbase).sketch() # wp = wp.push([cshift]).rect(extents[0], extents[1], mode="a").reset().vertices().fillet(outer_fillet) # wp = wp.push([inner_shift]).rect(inner[0], inner[1], mode="s") # .reset().vertices().fillet(inner_fillet) # wp = wp.finalize().extrude(height) wp: cadquery.Workplane # shouldn't have to do this (needed for type hints) wall_hardware = cq.Assembly(None, name="wall_hardware") # corner holes (with nuts and nut pockets) wp = wp.faces(">Z").workplane( **u.copo, offset=-nut.nut_thickness).pushPoints(hps).clearanceHole( fastener=nut, fit="Close", counterSunk=False, baseAssembly=wall_hardware) wp = wp.faces(">Z").workplane(**u.copo).sketch().push(hps[0:4:3]).rect( flat_to_flat, nut.nut_diameter, angle=45).reset().push( hps[1:3]).rect(flat_to_flat, nut.nut_diameter, angle=-45).reset().vertices().fillet( nut.nut_diameter / 4).finalize().cutBlind(-nut.nut_thickness) # chamfers wp = wp.faces(">Z").edges(">>X").chamfer(chamfer) # gas holes with recesses wp = wp.faces("<X").workplane(**u.cobb).center( back_holes_shift, 0).rarray(back_holes_spacing, 1, 2, 1).hole(diameter=gas_fitting_hole_diameter, depth=thickness) # wp = wp.faces("<X").workplane(**u.cobb).center(back_holes_shift, 0).sketch().rarray(back_holes_spacing, 1, 2, 1).rect(gas_fitting_diameter, gas_fitting_flat_to_flat).reset().vertices().fillet(gas_fitting_diameter / 4).finalize().cutBlind(-gas_fitting_recess) wp = wp.faces("<X").workplane(**u.cobb).center( back_holes_shift, 0).sketch().rect( 2 * gas_fitting_diameter / 2 + back_holes_spacing, gas_fitting_flat_to_flat).reset().vertices().fillet( gas_fitting_diameter / 4).finalize().cutBlind( -gas_fitting_recess) # unify the back holes wp = wp.faces(">X").workplane(**u.cobb).rarray( front_holes_spacing, 1, 2, 1).hole(diameter=gas_fitting_hole_diameter, depth=thickness) wp = wp.faces(">X").workplane(**u.cobb).sketch().rarray( front_holes_spacing, 1, 2, 1).rect(gas_fitting_diameter, gas_fitting_flat_to_flat).reset().vertices().fillet( gas_fitting_diameter / 4).finalize().cutBlind(-gas_fitting_recess) # that's part number polymax 230X2N70 o_ring_thickness = 2 o_ring_inner_diameter = 230 ooffset = 17 # two times the o-ring path's center offset from the outer edge of the walls # cut the lid o-ring groove wp = wp.faces(">Z").workplane(**u.cobb).mk_groove( ring_cs=o_ring_thickness, follow_pending_wires=False, ring_id=o_ring_inner_diameter, gland_x=extents[0] - ooffset, gland_y=extents[1] - ooffset, hardware=wall_hardware) # cut the base o-ring groove wp = wp.faces("<Z").workplane(**u.cobb).mk_groove( ring_cs=o_ring_thickness, follow_pending_wires=False, ring_id=o_ring_inner_diameter, gland_x=extents[0] - ooffset, gland_y=extents[1] - ooffset, hardware=wall_hardware) # get pipe fitting geometry a_pipe_fitting = u.import_step( wrk_dir.joinpath( "components", "5483T93_Miniature Nickel-Plated Brass Pipe Fitting.step")) a_pipe_fitting = a_pipe_fitting.translate( (0, 0, -6.35 - gas_fitting_recess)) pipe_fitting_asy = cadquery.Assembly(a_pipe_fitting.rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=30), name="one_pipe_fitting") # move the pipe fittings to their wall holes wppf = wp.faces(">X").workplane(**u.cobb).center( front_holes_spacing / 2, 0) pipe_fitting_asy.loc = wppf.plane.location wall_hardware.add(pipe_fitting_asy, name="front_right_gas_fitting") wppf = wppf.center(-front_holes_spacing, 0) pipe_fitting_asy.loc = wppf.plane.location wall_hardware.add(pipe_fitting_asy, name="front_left_gas_fitting") wppf = wp.faces("<X").workplane(**u.cobb).center( back_holes_shift + back_holes_spacing / 2, 0) pipe_fitting_asy.loc = wppf.plane.location wall_hardware.add(pipe_fitting_asy, name="rear_left_gas_fitting") wppf = wppf.center(-back_holes_spacing, 0) pipe_fitting_asy.loc = wppf.plane.location wall_hardware.add(pipe_fitting_asy, name="rear_right_gas_fitting") # get bonded washer geometry, part 229-6277 bonded_washer = u.import_step( wrk_dir.joinpath("components", "hutchinson_ljf_207242.stp")) bonded_washer = bonded_washer.rotate(axisStartPoint=(0, 0, 0), axisEndPoint=(0, 1, 0), angleDegrees=90).translate( (0, 0, 1.25)) bonded_washer_asy = cadquery.Assembly(bonded_washer, name="one_bonded_washer") # move bonded washers to their wall holes washer_thickness = 2.5 wpbw = wp.faces(">X").workplane(**u.cobb, offset=-thickness - washer_thickness).center( -front_holes_spacing / 2, 0) bonded_washer_asy.loc = wpbw.plane.location wall_hardware.add(bonded_washer_asy, name="front_right_bonded_washer") wpbw = wpbw.center(front_holes_spacing, 0) bonded_washer_asy.loc = wpbw.plane.location wall_hardware.add(bonded_washer_asy, name="front_left_bonded_washer") wpbw = wp.faces("<X[-5]").workplane(**u.cobb).center( -back_holes_shift - back_holes_spacing / 2, 0) bonded_washer_asy.loc = wpbw.plane.location wall_hardware.add(bonded_washer_asy, name="rear_right_bonded_washer") wpbw = wpbw.center(back_holes_spacing, 0) bonded_washer_asy.loc = wpbw.plane.location wall_hardware.add(bonded_washer_asy, name="rear_left_bonded_washer") aso.add(wall_hardware.toCompound(), name="wall_hardware", color=cadquery.Color(hardware_color)) # passthrough details pcb_scr_head_d_safe = 6 n_header_pins = 50 header_length = n_header_pins / 2 * 2.54 + 7.62 # n*0.1 + 0.3 inches support_block_width = 7 pt_pcb_width = 2 * (support_block_width / 2 + pcb_scr_head_d_safe / 2) + header_length pt_pcb_outer_depth = 8.89 + 0.381 # 0.35 + 0.15 inches pt_pcb_inner_depth = 8.89 + 0.381 # 0.35 + 0.15 inches pt_center_offset = 28.65 # so that the internal passthrough connector aligns with the one in the chamber # make the electrical passthrough pt_asy = cadquery.Assembly( ) # this will hold the passthrough part that gets created # pcb_asy = cadquery.Assembly() # this will hold the pcb part that gets created pcb_asy = None # dont generate the base PCB (will probably later import the detailed board model) hw_asy = cadquery.Assembly( ) # this will hold the pcb part that gets created ptt = 5.5 # passthrough thickness, reduce a bit from default (which was half wall thickness) to prevent some thin walls close to an o-ring gland wp = wp.faces("<X").workplane(**u.cobb).center( -pt_center_offset, 0).make_oringer(board_width=pt_pcb_width, board_inner_depth=pt_pcb_inner_depth, board_outer_depth=pt_pcb_outer_depth, wall_depth=thickness, part_thickness=ptt, pt_asy=pt_asy, pcb_asy=pcb_asy, hw_asy=hw_asy) # insert passthrough into assembly for asyo in pt_asy.traverse(): part = asyo[1] if isinstance(part.obj, cadquery.occ_impl.shapes.Solid): aso.add(part.obj, name=asyo[0], color=color) if pcb_asy is not None: # insert pcb into assembly for asyo in pcb_asy.traverse(): # insert only one solid object part = asyo[1] if isinstance(part.obj, cadquery.occ_impl.shapes.Solid): aso.add(part.obj, name=asyo[0], color=cadquery.Color("DARKGREEN")) # insert hardware into assembly aso.add(hw_asy.toCompound(), name="passthrough hardware") # add in little detailed PCB a_little_pcb = u.import_step( wrk_dir.joinpath("components", "pt_pcb.step")).translate( (0, 0, -pcb_thickness / 2)) # shift pcb to be z-centered little_pcb = cadquery.Assembly(a_little_pcb.rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 1, 0), angleDegrees=90).rotate(axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=90), name="small detailed pcb") asys["squirrel"].add(little_pcb, loc=wp.plane.location, name="little pcb") # for the vac chuck fittings rotation_angle = -155 # degrees vac_fitting_wall_offset = extents[ 1] / 2 - thickness - inner_fillet - 4 # mounting location offset from center wp = wp.faces(">X").workplane(**u.cobb).center( vac_fitting_wall_offset, 0).tapHole(vac_fitting_screw, depth=thickness + fitting_step_xy[0]) vac_chuck_fitting = cadquery.Assembly(a_vac_fitting.rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=rotation_angle), name="outer_wall_vac_fitting") aso.add(vac_chuck_fitting, loc=wp.plane.location, name="vac chuck fitting (wall outer)") nwp = wp.faces(">X").workplane(**u.cobb, invert=True, offset=thickness + fitting_step_xy[0]).center( vac_fitting_wall_offset, 0) vac_chuck_fitting = cadquery.Assembly(a_vac_fitting.rotate( axisStartPoint=(0, 0, 0), axisEndPoint=(0, 0, 1), angleDegrees=-rotation_angle), name="inner_wall_vac_fitting") aso.add(vac_chuck_fitting, loc=nwp.plane.location, name="vac chuck fitting (wall inner)") aso.add(wp, name=name, color=color) # add the walls bulk
def outputter(cls, asys, wrk_dir, save_dxfs=False, save_stls=False, save_steps=False, save_breps=False, save_vrmls=False): """do output tasks on a dictionary of assemblies""" for stack_name, asy in asys.items(): if "show_object" in globals(): # we're in cq-editor assembly_mode = True # at the moment, when true we can't select/deselect subassembly parts if assembly_mode: show_object(asy) else: for key, val in asy.traverse(): shapes = val.shapes if shapes != []: c = cq.Compound.makeCompound(shapes) odict = {} if val.color is not None: co = val.color.wrapped.GetRGB() rgb = (co.Red(), co.Green(), co.Blue()) odict["color"] = rgb show_object(c.locate(val.loc), name=val.name, options=odict) else: Path.mkdir(wrk_dir / "output", exist_ok=True) # save assembly asy.save(str(wrk_dir / "output" / f"{stack_name}.step")) # asy.save(str(wrk_dir / "output" / f"{stack_name}.brep")) asy.save(str(wrk_dir / "output" / f"{stack_name}.xml"), "XML") # asy.save(str(wrk_dir / "output" / f"{stack_name}.vtkjs"), "VTKJS") # stupid workaround for gltf export bug: https://github.com/CadQuery/cadquery/issues/993 asy2 = None # for path, child in asy._flatten().items(): for child in asy.children: # if "/" in path: if asy2 is None: asy2 = cadquery.Assembly(child.obj, name=child.name, color=child.color) else: asy2.add(child.obj, name=child.name, color=child.color) asy2.save(str(wrk_dir / "output" / f"{stack_name}.glb"), "GLTF") # cadquery.exporters.assembly.exportCAF(asy, str(wrk_dir / "output" / f"{stack_name}.std")) # cq.Shape.exportBrep(cq.Compound.makeCompound(itertools.chain.from_iterable([x[1].shapes for x in asy.traverse()])), str(wrk_dir / "output" / f"{stack_name}.brep")) # save each shape individually for key, val in asy.traverse(): shapes = val.shapes if shapes != []: c = cq.Compound.makeCompound(shapes) if save_stls == True: cadquery.exporters.export( c.locate(val.loc), str(wrk_dir / "output" / f"{stack_name}-{val.name}.stl"), cadquery.exporters.ExportTypes.STL) if save_steps == True: cadquery.exporters.export( c.locate(val.loc), str(wrk_dir / "output" / f"{stack_name}-{val.name}.step"), cadquery.exporters.ExportTypes.STEP) if save_breps == True: cq.Shape.exportBrep( c.locate(val.loc), str(wrk_dir / "output" / f"{stack_name}-{val.name}.brep")) if save_vrmls == True: cadquery.exporters.export( c.locate(val.loc), str(wrk_dir / "output" / f"{stack_name}-{val.name}.wrl"), cadquery.exporters.ExportTypes.VRML) if save_dxfs == True: cl = c.locate(val.loc) bb = cl.BoundingBox() zmid = (bb.zmin + bb.zmax) / 2 nwp = CQ("XY", origin=(0, 0, zmid)).add(cl) dxface = nwp.section() cadquery.exporters.export( dxface, str(wrk_dir / "output" / f"{stack_name}-{val.name}.dxf"), cadquery.exporters.ExportTypes.DXF)