def sa_cap(Usize=1): # MODIFIED TO NOT HAVE THE ROTATION. NEEDS ROTATION DURING ASSEMBLY sa_length = 18.25 bw2 = Usize * sa_length / 2 bl2 = sa_length / 2 m = 0 pw2 = 6 * Usize + 1 pl2 = 6 if Usize == 1: m = 17 / 2 k1 = sl.polygon([[bw2, bl2], [bw2, -bl2], [-bw2, -bl2], [-bw2, bl2]]) k1 = sl.linear_extrude(height=0.1, twist=0, convexity=0, center=True)(k1) k1 = sl.translate([0, 0, 0.05])(k1) k2 = sl.polygon([[pw2, pl2], [pw2, -pl2], [-pw2, -pl2], [-pw2, pl2]]) k2 = sl.linear_extrude(height=0.1, twist=0, convexity=0, center=True)(k2) k2 = sl.translate([0, 0, 12.0])(k2) if m > 0: m1 = sl.polygon([[m, m], [m, -m], [-m, -m], [-m, m]]) m1 = sl.linear_extrude(height=0.1, twist=0, convexity=0, center=True)(m1) m1 = sl.translate([0, 0, 6.0])(m1) key_cap = sl.hull()(k1, k2, m1) else: key_cap = sl.hull()(k1, k2) # key_cap = sl.translate([0, 0, 5 + plate_thickness])(key_cap) key_cap = sl.color([220 / 255, 163 / 255, 163 / 255, 1])(key_cap) return key_cap
def snap_shaft(): """Return snap shaft and hole geometry in a list of PolyMeshes.""" s_pts = solid.polygon( points=[ [0.0, 0.0], [4.95, 0.0], [4.95, 3.0], [2.45, 3.0], [1.95, 3.5], [1.95, 5.675], [2.45, 6.175], [2.45, 7.425], [1.95, 7.925], [0.75, 7.925], [0.75, 3.5], [0.0, 3.0], [0.0, 0.0] ] ) sh_gen = ( solid.rotate_extrude(convexity=10)(s_pts) - solid.translate([0, 0, 8.5])(solid.cube([1.5, 10, 10], center=True)) - solid.translate([0, 0, 3.75])( solid.rotate([90, 0, 0])( solid.cylinder(r=0.75, h=10, center=True) ) ) ) h_pts = solid.polygon( points=[ [2.5, 0], [2, 0.5], [2.0, 2.675], [2.5, 3.175], [ 4.95, 3.175], [4.95, 0], [2.5, 0] ] ) # added hole so it will subtract geometry from arm as necessary h_gen = solid.translate([0, 0, 3])( solid.rotate_extrude(convexity=10)(h_pts)) return [PolyMesh(generator=sh_gen), PolyMesh(generator=h_gen)]
def makeRegister(board, jigFrameSize, jigThickness, pcbThickness, outerBorder, innerBorder, tolerance, topSide): bBox = findBoardBoundingBox(board) centerpoint = rectCenter(bBox) top = jigThickness - fromMm(0.15) pcbBottom = jigThickness - pcbThickness outerPolygon, holes = createOuterPolygon(board, jigFrameSize, outerBorder) outerRing = outerPolygon.exterior.coords if topSide: outerRing = mirrorX(outerRing, centerpoint[0]) body = solid.linear_extrude(height=top, convexity=10)(solid.polygon(outerRing)) innerRing = createOffsetPolygon(board, -innerBorder).exterior.coords if topSide: innerRing = mirrorX(innerRing, centerpoint[0]) innerCutout = solid.utils.down(jigThickness)(solid.linear_extrude( height=3 * jigThickness, convexity=10)(solid.polygon(innerRing))) registerRing = createOffsetPolygon(board, tolerance).exterior.coords if topSide: registerRing = mirrorX(registerRing, centerpoint[0]) registerCutout = solid.utils.up(jigThickness - pcbThickness)( solid.linear_extrude(height=jigThickness, convexity=10)(solid.polygon(registerRing))) register = body - innerCutout - registerCutout for hole in holes: register = register - solid.translate([hole[0], hole[1], top])( m2countersink()) return solid.scale(toMm(1))(solid.translate( [-centerpoint[0], -centerpoint[1], 0])(register))
def process(outline_file, solderpaste_file, stencil_thickness=0.2, include_ledge=True, ledge_height=1.2, ledge_gap=0.0, increase_hole_size_by=0.0): outline_shape = create_outline_shape(outline_file) cutout_polygon = create_cutouts( solderpaste_file, increase_hole_size_by=increase_hole_size_by) if ledge_gap: # Add a gap between the ledge and the stencil outline_shape = offset_shape(outline_shape, ledge_gap) outline_polygon = polygon(outline_shape) stencil = linear_extrude(height=stencil_thickness)(outline_polygon - cutout_polygon) if include_ledge: ledge_shape = offset_shape(outline_shape, 1.2) ledge_polygon = polygon(ledge_shape) - outline_polygon # Cut the ledge in half by taking the bounding box of the outline, cutting it in half # and removing the resulting shape from the ledge shape # We always leave the longer side of the ledge intact so we don't end up with a tiny ledge. cutter = bounding_box(ledge_shape) height = abs(cutter[1][1] - cutter[0][1]) width = abs(cutter[0][0] - cutter[3][0]) if width > height: cutter[1][1] -= height / 2 cutter[2][1] -= height / 2 else: cutter[2][0] -= width / 2 cutter[3][0] -= width / 2 ledge_polygon = ledge_polygon - polygon(cutter) ledge = utils.down(ledge_height - stencil_thickness)( linear_extrude(height=ledge_height)(ledge_polygon)) stencil = ledge + stencil # Rotate the stencil to make it printable stencil = rotate(a=180, v=[1, 0, 0])(stencil) return scad_render(stencil)
def create_cutouts(solder_paste, increase_hole_size_by=0.0): solder_paste.to_metric() cutout_shapes = [] cutout_lines = [] apertures = {} current_aperture = None current_x = None current_y = None for statement in solder_paste.statements: if statement.type == 'PARAM' and statement.param == 'AD': # define aperture apertures[statement.d] = { 'shape': statement.shape, 'modifiers': statement.modifiers } elif statement.type == 'APERTURE': current_aperture = statement.d elif statement.type == 'COORD' and statement.op == 'D3': # flash object coordinates if not current_aperture: raise Exception("No aperture set on flash object coordinates!") aperture = apertures[current_aperture] current_x = statement.x if statement.x is not None else current_x current_y = statement.y if statement.y is not None else current_y if aperture['shape'] == 'C': # circle cutout_shapes.append( primitive_to_shape( primitives.Circle(diameter=aperture['modifiers'][0][0], position=[current_x, current_y]))) elif aperture['shape'] == 'R': # rectangle width, height = aperture['modifiers'][0] cutout_shapes.append( primitive_to_shape( primitives.Rectangle( position=[current_x, current_y], width=width, height=height, ))) else: raise NotImplementedError( "Only circular and rectangular flash objects are supported!" ) for p in solder_paste.primitives: shape = primitive_to_shape(p) if len(shape) > 2: cutout_shapes.append(shape) else: cutout_lines.append(shape) # If the cutouts contain lines we try to first join them together into shapes cutout_shapes += lines_to_shapes(cutout_lines) polygons = [] for shape in cutout_shapes: if increase_hole_size_by and len(shape) > 2: shape = offset_shape(shape, increase_hole_size_by) polygons.append(polygon([[x, y] for x, y in shape])) return union()(*polygons)
def triangle90(a, b, height=1, axis="z", center=True): """A quick 90 degree triangle with sidelengths a,b, extruded to height""" p = solid.polygon([ [ 0, 0, ], [a, 0], [a, b], ]).e(height) if center: c = ((0 + a + a) / 3, (0 + 0 + b) / 3) if center is True or "z" in center: p = p.down(height / 2) if center is True or "x" in center: p = p.left(c[0]) if center is True or "y" in center: p = p.back(c[1]) if axis == "x": p = p.rotate(90, 0, 0) elif axis == "y": p = p.rotate( 0, 90, 0, ) elif axis == "z": pass else: raise ValueError("invalid axis") return p
def dovetail(width, length, depth, ratio): return linear_extrude(depth)(polygon(( (0, 0), (width, 0), (width - length * ratio, length), (length * ratio, length), )))
def rounded_rectangle(size: XY, corner_radius, segments): if isinstance(size, (int, float)): size = XY(size, size) if isinstance(corner_radius, (int, float)): corner_radius = (corner_radius, corner_radius, corner_radius, corner_radius) corner_points = [ XY(size.x - corner_radius[0], size.y - corner_radius[0]), XY(corner_radius[1], size.y - corner_radius[1]), XY(corner_radius[2], corner_radius[2]), XY(size.x - corner_radius[3], corner_radius[3]), ] theta = 0 dtheta = 90 theta_step = dtheta / (segments / 4) points = [] # Go counter clockwise just in case we need to calculate a normal for cp, r in zip(corner_points, corner_radius): t = theta while t < theta + dtheta + theta_step: points.append( XY(cp.x + r * cos(radians(t)), cp.y + r * sin(radians(t)))) t += theta_step theta += dtheta points.append(points[0]) # Close the polygon return polygon(points)
def write_to_scad(self, filename = 'test.scad'): """Uses solidpython to export the animation (shapes and their associated transformations) to an OpenSCAD file, ready to be rendered as an STL. Inputs: filename: The filename to export to. """ if type(self.final_shapes) == NoneType: self.render_shapes() shapes_to_export = [] for shape in self.final_shapes: solid_shapes = [] for i in range(len(shape)): #For each shape (which is stored as a list of points)... solid_shape = shape[i].T.tolist() #Represent it as a polygon... solid_shapes.append(sp.polygon(solid_shape)) #Extrude that polygon up .21mm... solid_shapes[i] = sp.linear_extrude(.21)(solid_shapes[i]) #Then translate that extrusion up .2mm... solid_shapes[i] = up(i*.2)(solid_shapes[i]) shapes_to_export.append(solid_shapes) #Then union ALL of the extrudes of EVERY shape final_export = union()(shapes_to_export) scad_render_to_file(final_export,filename)
def path_2d_polygon(points:Sequence[Point23], width:float=1, closed:bool=False) -> polygon: ''' Return an OpenSCAD `polygon()` in an area `width` units wide around `points` ''' path_points = path_2d(points, width, closed) paths = [list(range(len(path_points)))] if closed: paths = [list(range(len(points))), list(range(len(points), len(path_points)))] return polygon(path_points, paths=paths)
def conic_section(theta): line = solid.polygon(points = [[0,0],[50,50],[49.9,50],[0,.1]]) cone = solid.rotate_extrude( convexity = 20)(line) plane = solid.translate([0,0,5])(solid.cube([50,50,.1],center = True)) plane = solid.rotate([0,theta,0])(plane) section = solid.rotate([0,-1*theta, 0])(solid.intersection()(cone, plane)) return section
def process(outline_file, solderpaste_file, stencil_thickness=0.2, include_ledge=True, ledge_height=1.2, ledge_gap=0.0, increase_hole_size_by=0.0): outline_shape = create_outline_shape(outline_file) cutout_polygon = create_cutouts(solderpaste_file, increase_hole_size_by=increase_hole_size_by) if ledge_gap: # Add a gap between the ledge and the stencil outline_shape = offset_shape(outline_shape, ledge_gap) outline_polygon = polygon(outline_shape) stencil = linear_extrude(height=stencil_thickness)(outline_polygon - cutout_polygon) if include_ledge: ledge_shape = offset_shape(outline_shape, 1.2) ledge_polygon = polygon(ledge_shape) - outline_polygon # Cut the ledge in half by taking the bounding box of the outline, cutting it in half # and removing the resulting shape from the ledge shape # We always leave the longer side of the ledge intact so we don't end up with a tiny ledge. cutter = bounding_box(ledge_shape) height = abs(cutter[1][1] - cutter[0][1]) width = abs(cutter[0][0] - cutter[3][0]) if width > height: cutter[1][1] -= height/2 cutter[2][1] -= height/2 else: cutter[2][0] -= width/2 cutter[3][0] -= width/2 ledge_polygon = ledge_polygon - polygon(cutter) ledge = utils.down( ledge_height - stencil_thickness )( linear_extrude(height=ledge_height)(ledge_polygon) ) stencil = ledge + stencil # Rotate the stencil to make it printable stencil = rotate(a=180, v=[1, 0, 0])(stencil) return scad_render(stencil)
def panel_shape(panel_tri): edge_midpoint = 0.5 * (panel_tri[0] + panel_tri[1]) rotation_vector = panel_tri[1] - panel_tri[0] transform = lambda x: solid.translate( edge_midpoint)(solid.rotate(a=panel_angle, v=rotation_vector) (solid.translate(-edge_midpoint)(x))) return transform( solid.linear_extrude(0.01)(solid.polygon(panel_tri * 0.9)))
def make_stick_figures(shell, stick_figures, radius, breadth): """Adds stick figure inscription onto the shell.""" # Create inscribed shell i_shell = solid.difference() i_shell.add(shell) # Add individual polygons to the inscribed shell for cname in sorted(stick_figures.keys()): for a1, e1, a2, e2 in stick_figures[cname]: i_shell.add( solid.linear_extrude(height=radius)(solid.polygon( make_sticks(a1, e1, a2, e2, radius, breadth)))) return i_shell
def printedStencil(outlineDxf, holesDxf, extraHoles, thickness, frameHeight, frameWidth, frameClearance, enlargeHoles, front): zScale = -1 if front else 1 xRotate = 180 if front else 0 substrate = solid.scale([1, 1, zScale])(printedStencilSubstrate(outlineDxf, thickness, frameHeight, frameWidth, frameClearance)) holesOffset = solid.utils.up(0) if enlargeHoles == 0 else solid.offset(delta=enlargeHoles) holes = solid.linear_extrude(height=4*thickness, center=True)( holesOffset(solid.import_dxf(holesDxf))) substrate -= holes for h in extraHoles: substrate -= solid.scale([toMm(1), -toMm(1), 1])( solid.linear_extrude(height=4*thickness, center=True)( solid.polygon(h.exterior.coords))) return solid.rotate(a=xRotate, v=[1, 0, 0])(substrate)
def bezier_polygon(controls: FourPoints, subdivisions: int = DEFAULT_SUBDIVISIONS, extrude_height: float = DEFAULT_EXTRUDE_HEIGHT, show_controls: bool = False, center: bool = True) -> OpenSCADObject: points = bezier_points(controls, subdivisions) shape = polygon(points) if extrude_height != 0: shape = linear_extrude(extrude_height, center=center)(shape) if show_controls: control_objs = control_points(controls, extrude_height=extrude_height, center=center) shape += control_objs return shape
def create_cutouts(solder_paste, increase_hole_size_by=0.0): solder_paste.to_metric() cutout_faces = [] for p in solder_paste.primitives: cutout_faces += primitive_to_faces(p) cutout_shapes = combine_faces_into_shapes(cutout_faces) polygons = [] for shape in cutout_shapes: if increase_hole_size_by and len(shape) > 2: shape = offset_shape(shape, increase_hole_size_by) polygons.append(polygon([[x, y] for x, y in shape])) return union()(*polygons)
def hold_down(od, wing_thickness, alpha, height, width, angle): half_thickness=to_mm(wing_thickness/2); r=to_mm(od/2); depth = r * sqrt(1-alpha*alpha); h=to_mm(height); w=to_mm(width); hprime = h*w/(w-(1-alpha)*r); basic_wedge = linear_extrude(h, center=True)(polygon([[0,half_thickness], [0,depth], [w, half_thickness]])) basic_cylinder = rotate([angle, 0,0])(cylinder(h=h*1.5, r=r,center=True)) cone_envelope = translate([alpha*r, half_thickness,-h*0.5])( rotate([0,90,0])( scale([hprime/(depth-half_thickness),1,1])( cylinder(r1=depth-half_thickness, r2=0, h=w)))) clipping_cube = cube([10000,r*1.2, 10000],center=True); holder = (translate([alpha*r, 0, 0])(basic_wedge)- basic_cylinder)* cone_envelope *clipping_cube return holder
def test_with_solidpython(self): import solid sys = System() a = sys.add_param(10) b = sys.add_param(3) c = sys.add_param(17) d = sys.add_param(23) #NOTE We should use Point2d, but I don't want to # create a workplane just for that. p1 = Point3d(Param(7), Param(2), Param(0), sys) poly = solid.polygon([[a,b],[c,d], [0,0], p1]) self.assertEqual( solid.scad_render(poly), "\n\npolygon(paths = [[0, 1, 2, 3]], points = [[10.0000000000, 3.0000000000], [17.0000000000, 23.0000000000], [0, 0], [7.0000000000, 2.0000000000, 0.0000000000]]);")
def sector(radius=20, angles=(45, 135), segments=24): r = radius / math.cos(180 * degree_to_radians / segments) step = int(-360 / segments) points = [[0, 0]] for a in range(int(angles[0]), int(angles[1] - 360), step): points.append([ r * math.cos(a * degree_to_radians), r * math.sin(a * degree_to_radians) ]) for a in range(int(angles[0]), int(angles[1] - 360), step): points.append([ r * math.cos(angles[1] * degree_to_radians), r * math.sin(angles[1] * degree_to_radians), ]) return solid.difference()( solid.circle(radius, segments=segments), solid.polygon(points), )
def create_cutouts(solder_paste, increase_hole_size_by=0.0): solder_paste.to_metric() cutout_shapes = [] cutout_lines = [] for p in solder_paste.primitives: shape = primitive_to_shape(p) if len(shape) > 2: cutout_shapes.append(shape) else: cutout_lines.append(shape) # If the cutouts contain lines we try to first join them together into shapes cutout_shapes += lines_to_shapes(cutout_lines) polygons = [] for shape in cutout_shapes: if increase_hole_size_by and len(shape) > 2: shape = offset_shape(shape, increase_hole_size_by) polygons.append(polygon([[x, y] for x, y in shape])) return union()(*polygons)
def __init__(self, length, thickness=None, inner=None, outer=None, points=5, **kwargs): """ :param length: :param thickness: :param inner: :param outer: :param points: :param kwargs: Hollow doesn't work quite right yet. """ super(MorphedNoseCone, self).__init__(length, thickness, **kwargs) outer = to_mm(outer, self.outer_diameter / 4.) inner = to_mm(inner, self.outer_diameter / 8.) out = [] for i in range(0, points): alpha = i * 2. * pi / points p = polygon([[0, 0], [inner * cos(alpha - pi / points), inner * sin(alpha - pi / points)], [outer * cos(alpha), outer * sin(alpha)], [inner * cos(alpha + pi / points), inner * sin(alpha + pi / points)]]) out.append( MorphedNoseCone(length, p, inner_diameter=self.inner_diameter, outer_diameter=self.outer_diameter, thickness=thickness).cone) self.cone = union()(*out)
def catmull_rom_polygon(points: Sequence[Point23], subdivisions: int = DEFAULT_SUBDIVISIONS, extrude_height: float = DEFAULT_EXTRUDE_HEIGHT, show_controls: bool = False, center: bool = True) -> OpenSCADObject: """ Return a closed OpenSCAD polygon object through all of `points`, extruded to `extrude_height`. If `show_controls` is True, return red cylinders at each of the specified control points; this makes it easier to move determine which points should move to get a desired shape. NOTE: if `extrude_height` is 0, this function returns a 2D `polygon()` object, which OpenSCAD can only combine with other 2D objects (e.g. `square`, `circle`, but not `cube` or `cylinder`). If `extrude_height` is nonzero, the object returned will be 3D and only combine with 3D objects. """ catmull_points = catmull_rom_points(points, subdivisions, close_loop=True) shape = polygon(catmull_points) if extrude_height > 0: shape = linear_extrude(height=extrude_height, center=center)(shape) if show_controls: shape += control_points(points, extrude_height, center) return shape
How much to offset from the origin (set to the wall thickness) ''' xpts = np.linspace(0, length, nsteps) term1 = (1 - xpts / length)**(2 / 3.) profile = 0.9 * length * term1 * np.sqrt(1 - term1) # For the profile to "stand up" swap the axes coords = list(zip(profile, xpts + offset)) # Add origin point as the last point to close the profile coords.append((0., offset)) return coords # Read the outer profile for the shell #shell = sol.polygon(read_csv(PROF_OUTER)) # Generate the outer profile for the shell shell = sol.polygon(egg(LENGTH)) # Rotational extrusion to make the solid. # rotate_extrude takes the profile on the XY plane and uses it as the # profile in the XZ plane for revolution about the Z-axis shell = sol.rotate_extrude(convexity=10)(shell) # Variables... # The following variables need to be defined in the HEADER string # for OpenSCAD to have access to them in the generated program: # coupler_shoulder_len, coupler_shoulder_diam, coupler_fitting_len # coupler_fitting_diam, coupler_extra, delta, wall_th OUTFILE = 'Customizable_hollow_egg_carrier.scad' coupler_fitting_diam = 'coupler_fitting_diam' coupler_shoulder_h = 'coupler_shoulder_len + coupler_extra' coupler_shoulder_diam = 'coupler_shoulder_diam' coupler_shoulder_shift = '-coupler_shoulder_len'
def triangle_prism(a, b, c, thickness): return linear_extrude(thickness)(polygon((a, b, c, a)))
def spurGear(nT=12, gmodule=3, holeDiam=6.35, gthick=4, pressAngle=28): '''Return CSG of a cylindrical spur gear having center-hole diameter=holeDiam, thickness=gthick, module=gmodule, pressure angle=pressAngle, #teeth=nT. In more detail: module: A metric gear's module (mm) is its reference diameter (its pitch diameter) divided by its tooth count. For example, a module-3 gear with 30 teeth is 90 mm across. "Circular pitch", equal to pi*module, is tooth-to-tooth circumferential distance at pitch diameter. "Diametral pitch" is the number of tooth intervals per inch at pitch diameter. Pressure angle (degrees) is profile angle at pitch diameter; also equals angle between normal to tooth surface and angle of force when gear contact point is at the pitch diameter. Coefficient of profile shift: [Not in this version; may add in future] Use 0 if #teeth is large; as #teeth gets smaller, use a larger shift to avoid tooth undercutting. See Table 4, p. T-40 in SDP-SI 8050T034.pdf ''' cyl = cylinder(h=gthick*1.1, d=holeDiam, center=True) nTeeth = float(nT) gmodule = float(gmodule) pitchDiam = gmodule * nTeeth baseDiam = pitchDiam * cos(rad(pressAngle)) rootDiam = pitchDiam - 2.5 * gmodule tipDiam = pitchDiam + 2 * gmodule #addendum, dedendum = gmodule, gmodule*1.25 rp, rb, rr, rt = pitchDiam/2., baseDiam/2., rootDiam/2., tipDiam/2. rm = max(rb, rr) #print ('rp {:8.2f} rb {:8.2f} rr {:8.2f} rt {:8.2f}'.format(rp, rb, rr, rt)) def round3(v): return int(1000*v+0.5)/1000.0 def rotated(pl, ra): '''Return point list pl, rotated by ra radians, rounded to a few decimal places for compactness. ''' s, c = sin(ra), cos(ra) return [[round3(x*c-y*s), round3(x*s+y*c)] for x,y in pl] def invo(r): # Compute involute at radius r, return its (x,y) ia = sqrt((r/rb)**2 - 1) # Radians for involute to reach radius r ix = rb*(cos(ia) + ia*sin(ia)) iy = rb*(sin(ia) - ia*cos(ia)) # x,y coords of point on involute return (ix,-iy) # Make set of points for outline of one tooth along +y axis # Compute point at pitch radius, for use as alignment angle x, y = invo(rp) # (x,y) when involute crosses pitch circle alan = atan2(-y, x) # angle from origin to x,y tang = pi/nTeeth # angle subtended by one tooth or one gap at rp htan = tang/2 # half-tooth angle: 1/4 of pitch angle pang = 2*tang # angle subtended by one tooth + one gap nradii = 6 rstep = (rt-rm)/float(nradii) points = [invo(rm + j*rstep) for j in range(nradii)] # Rotate half-tooth for proper tooth thickness at pitch circle points = rotated(points, alan+htan) #print ('Half-tooth points after adjust: ',points) # Curve-rounding in gap. Note, pang = adel+2*aeps = 2*tang x, y = points[0]; aeps = atan2(y,x); adel = pang-2*aeps g = rr*adel; u = g/20 # adel = angle in gap, g = gap width at rr if g>0: # Add 3 points like on an arc curve = [[rr,0],[rr+u,-u*6],[rr+u*3,-u*8]] # Skip first point if it's behind the curve points = rotated(curve,tang) + points[0 if x>rr+u*3 else 1:] #print ('Half-tooth points after curver: ',points) # Mirror tooth top side to bottom (except for center pt of gap) bepo = points + [[x,-y] for x,y in reversed(points[1:])] repo = list(reversed(bepo)) # draw up not down polyli = [] for i in range(nT): polyli += rotated(repo, i*pang) return linear_extrude(gthick, True)(polygon(polyli)) - cyl
def process_gerber( outline_file, solderpaste_file, stencil_thickness=0.2, include_ledge=True, ledge_height=1.2, ledge_gap=0.0, increase_hole_size_by=0.0, simplify_regions=False, flip_stencil=False, ): """Convert gerber outline and solderpaste files to an scad file.""" outline_shape = create_outline_shape(outline_file) cutout_polygon = create_cutouts( solderpaste_file, increase_hole_size_by=increase_hole_size_by, simplify_regions=simplify_regions, ) # debugging! # return scad_render(linear_extrude(height=stencil_thickness)(polygon(outline_shape))) if ledge_gap: # Add a gap between the ledge and the stencil outline_shape = offset_shape(outline_shape, ledge_gap) outline_polygon = polygon([v.as_tuple() for v in outline_shape]) if flip_stencil: mirror_normal = (-1, 0, 0) outline_bounds = geometry.bounding_box(outline_shape) outline_polygon = translate((outline_bounds[2][0], 0, 0))( mirror(mirror_normal)(outline_polygon) ) cutout_polygon = translate((outline_bounds[2][0], 0, 0))( mirror(mirror_normal)(cutout_polygon) ) stencil = linear_extrude(height=stencil_thickness)(outline_polygon - cutout_polygon) if include_ledge: ledge_shape = offset_shape(outline_shape, 1.2) ledge_polygon = polygon([v.as_tuple() for v in ledge_shape]) - outline_polygon # Cut the ledge in half by taking the bounding box of the outline, cutting it in half # and removing the resulting shape from the ledge shape # We always leave the longer side of the ledge intact so we don't end up with a tiny ledge. cutter = geometry.bounding_box(ledge_shape) height = abs(cutter[1][1] - cutter[0][1]) width = abs(cutter[0][0] - cutter[3][0]) if width > height: cutter[1].y -= height / 2 cutter[2].y -= height / 2 else: cutter[2].x -= width / 2 cutter[3].x -= width / 2 ledge_polygon = ledge_polygon - polygon([v.as_tuple() for v in cutter]) ledge = utils.down(ledge_height - stencil_thickness)( linear_extrude(height=ledge_height)(ledge_polygon) ) stencil = ledge + stencil # Rotate the stencil to make it printable stencil = rotate(a=180, v=(1, 0, 0))(stencil) # for debugging, output just the cutout polygon (extruded) # return scad_render(linear_extrude(height=stencil_thickness)(cutout_polygon)) return scad_render(stencil)
def create_cutouts(solder_paste, increase_hole_size_by=0.0, simplify_regions=False): solder_paste.to_metric() cutout_shapes: List[List[V]] = [] cutout_lines: List[List[V]] = [] apertures = {} # Aperture macros are saved as a list of shapes aperture_macros = {} current_aperture = None current_x = 0 current_y = 0 for statement in solder_paste.statements: if statement.type == "PARAM": if statement.param == "AD": # define aperture apertures[statement.d] = { "shape": statement.shape, "modifiers": statement.modifiers, } elif statement.param == "AM": # Aperture macro aperture_macros[statement.name] = [] for primitive in statement.primitives: aperture_macros[statement.name].append( primitive_to_shape(primitive) ) elif statement.type == "APERTURE": current_aperture = statement.d elif statement.type == "COORD" and statement.op in ["D02", "D2"]: # Move coordinates if statement.x is not None: current_x = statement.x if statement.y is not None: current_y = statement.y elif statement.type == "COORD" and statement.op in [ "D03", "D3", ]: # flash object coordinates if not current_aperture: raise Exception("No aperture set on flash object coordinates!") aperture = apertures[current_aperture] current_x = statement.x if statement.x is not None else current_x current_y = statement.y if statement.y is not None else current_y if aperture["shape"] == "C": # circle cutout_shapes.append( primitive_to_shape( primitives.Circle( diameter=aperture["modifiers"][0][0], position=[current_x, current_y], ) ) ) elif aperture["shape"] == "R": # rectangle width, height = aperture["modifiers"][0] print(f"Rect X: {current_x} Y: {current_y}") cutout_shapes.append( primitive_to_shape( primitives.Rectangle( position=[current_x, current_y], width=width, height=height, ) ) ) elif aperture["shape"] == "O": # obround width, height = aperture["modifiers"][0] print(f"Obround at {current_x},{current_y}") obround = primitives.Obround((0, 0), width, height) shape = primitive_to_shape(obround) positioned_shape = [ V(p[0] + current_x, p[1] + current_y) for p in shape ] cutout_shapes.append(positioned_shape) elif aperture["shape"] in aperture_macros: # Aperture macro shape for macro_shape in aperture_macros[aperture["shape"]]: # Offset all points in the macro and add the resulting shape shape = [V(p[0] + current_x, p[1] + current_y) for p in macro_shape] cutout_shapes.append(shape) else: raise NotImplementedError( f"Unsupported flash aperture {aperture['shape']}" ) else: pass for p in solder_paste.primitives: if type(p) == primitives.AMGroup: print(f"Ignoring AMGroup {p}") continue shape = primitive_to_shape(p, simplify_regions=simplify_regions) if len(shape) > 2: cutout_shapes.append(shape) else: cutout_lines.append(shape) # If the cutouts contain lines we try to first join them together into shapes cutout_shapes += lines_to_shapes(cutout_lines) polygons = [] for shape in cutout_shapes: if increase_hole_size_by and len(shape) > 2: shape = offset_shape(shape, increase_hole_size_by) polygons.append(polygon([(x, y) for x, y in shape])) return union()(*polygons)
def path_render3D(self, pconfig, border=False): global _delta, PRECISION, SCALEUP self.rotations_to_3D() config={} config=self.overwrite(config,pconfig) inherited = self.get_config() # if('transformations' in config): config=self.overwrite(config, inherited) if border==False and 'zoffset' in pconfig: zoffset= pconfig['zoffset'] elif 'zoffset' in config and config['zoffset']: zoffset= config['zoffset'] else: zoffset = 0 if 'thickness' not in config: config['thickness']=pconfig['thickness'] if config['z0'] is None or config['z0'] is False: z0=0 else: z0=config['z0'] if border==False: z0 += 1 if (config['z1'] is False or config['z1'] is None) and config['z0'] is not None and config['thickness'] is not None: if border==False: z1 = - config['thickness']- 20 else: z1 = - config['thickness'] else: z1= config['z1'] z0 *=config['zdir'] z1*=config['zdir'] # z0 = - config['thickness'] - z0 # z1 = - config['thickness'] - z1 # try to avoid faces and points touching by offsetting them slightly z0+=_delta z1-=_delta _delta+=0.00001 outline = [] points = self.polygonise(RESOLUTION) # extrude_path = [ Point3(0,0,zoffset + float(z0)), Point3(0,0, zoffset + float(z1)) ] for p in points: outline.append( [round(p[0],PRECISION)*SCALEUP, round(p[1],PRECISION)*SCALEUP ]) outline.append([round(points[0][0],PRECISION)*SCALEUP, round(points[0][1],PRECISION)*SCALEUP]) # outline.append( Point3(p[0], p[1], p[2] )) # outline.append( Point3(points[0][0], points[0][1], points[0][2] )) h = round(abs(z1-z0),PRECISION)*SCALEUP bottom = round((min(z1,z0)+zoffset),PRECISION) *SCALEUP # extruded = extrude_along_path(shape_pts=outline, path_pts=extrude_path) if self.extrude_scale is not None: scale = self.extrude_scale if self.extrude_centre is None: self.extrude_centre = V(0,0) centre = (PSharp(V(0,0)).point_transform(config['transformations']).pos+self.extrude_centre) centre = [centre[0], centre[1]] uncentre = [-centre[0], -centre[1]] extruded = solid.translate([0,0,bottom])( solid.translate(centre)( solid.linear_extrude(height=h, center=False, scale = scale)( solid.translate(uncentre)(solid.polygon(points=outline))))) else: scale = 1 extruded = solid.translate([0,0,bottom])(solid.linear_extrude(height=h, center=False)(solid.polygon(points=outline))) #extruded = translate([0,0,bottom])(linear_extrude(height=h, center=False)(solid.polygon(points=outline))) # if 'isback' in config and config['isback'] and border==False: # extruded = mirror([1,0,0])(extruded ) if 'colour' in config and config['colour']: extruded = solid.color(self.scad_colour(config['colour']))(extruded) if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'matrix3D' in self.transform: if type(self.transform['matrix3D'][0]) is list: extruded=solid.translate([-self.transform['rotate3D'][1][0], - self.transform['rotate3D'][1][1]])(extruded) extruded=solid.multmatrix(m=self.transform['matrix3D'][0])(extruded) extruded=solid.translate([self.transform['rotate3D'][1][0], self.transform['rotate3D'][1][1]])(extruded) else: extruded=solid.multmatrix(m=self.transform['matrix3D'])(extruded) if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'rotate3D' in self.transform: if type(self.transform['rotate3D'][0]) is list: print [-self.transform['rotate3D'][1][0], - self.transform['rotate3D'][1][1], - self.transform['rotate3D'][1][2]- zoffset] extruded=solid.translate([-self.transform['rotate3D'][1][0], - self.transform['rotate3D'][1][1], - self.transform['rotate3D'][1][2]- zoffset])(extruded) extruded=solid.rotate([self.transform['rotate3D'][0][0], self.transform['rotate3D'][0][1],self.transform['rotate3D'][0][2] ])(extruded) extruded=solid.translate([self.transform['rotate3D'][1][0], self.transform['rotate3D'][1][1], self.transform['rotate3D'][1][1] + zoffset])(extruded) else: extruded=solid.rotate([self.transform['rotate3D'][0], self.transform['rotate3D'][1],self.transform['rotate3D'][2] ])(extruded) if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'translate3D' in self.transform: extruded=solid.translate([self.transform['translate3D'][0], self.transform['translate3D'][1],self.transform['translate3D'][2] ])(extruded) return [extruded]
def path_render3D(self, pconfig, border=False): global _delta, PRECISION, SCALEUP self.rotations_to_3D() config={} config=self.overwrite(config,pconfig) inherited = self.get_config() # if('transformations' in config): config=self.overwrite(config, inherited) if border==False and 'zoffset' in pconfig: zoffset= pconfig['zoffset'] elif 'zoffset' in config and config['zoffset']: zoffset= config['zoffset'] else: zoffset = 0 if 'thickness' not in config: config['thickness']=pconfig['thickness'] if config['z0'] is None or config['z0'] is False: z0=0 else: z0=config['z0'] if border==False: z0 += config['thickness'] +1 if (config['z1'] is False or config['z1'] is None) and config['z0'] is not None and config['thickness'] is not None: if border==False: z1 = - config['thickness']- 20 else: z1 = - config['thickness'] else: z1= config['z1'] z0 *=config['zdir'] z1*=config['zdir'] # z0 = - config['thickness'] - z0 # z1 = - config['thickness'] - z1 # try to avoid faces and points touching by offsetting them slightly z0+=_delta z1-=_delta _delta+=0.00001 outline = [] self.reset_points() points = self.polygonise(RESOLUTION) # extrude_path = [ Point3(0,0,zoffset + float(z0)), Point3(0,0, zoffset + float(z1)) ] for p in points: outline.append( [round(p[0],PRECISION)*SCALEUP, round(p[1],PRECISION)*SCALEUP ]) outline.append([round(points[0][0],PRECISION)*SCALEUP, round(points[0][1],PRECISION)*SCALEUP]) # outline.append( Point3(p[0], p[1], p[2] )) # outline.append( Point3(points[0][0], points[0][1], points[0][2] )) h = round(abs(z1-z0),PRECISION)*SCALEUP bottom = round((min(z1,z0)+zoffset),PRECISION) *SCALEUP # dodgy but working atm if not border and 'isback' in config and config['isback']: pass h = 2*h # extruded = extrude_along_path(shape_pts=outline, path_pts=extrude_path) if self.extrude_scale is not None: scale = self.extrude_scale if self.extrude_centre is None: self.extrude_centre = V(0,0) centre = (PSharp(V(0,0)).point_transform(config['transformations']).pos+self.extrude_centre) centre = [centre[0], centre[1]] uncentre = [-centre[0], -centre[1]] extruded = solid.translate([0,0,bottom])( solid.translate(centre)( solid.linear_extrude(height=h, center=False, scale = scale)( solid.translate(uncentre)(solid.polygon(points=outline))))) else: scale = 1 extruded = solid.translate([0,0,bottom])(solid.linear_extrude(height=h, center=False)(solid.polygon(points=outline))) #extruded = translate([0,0,bottom])(linear_extrude(height=h, center=False)(solid.polygon(points=outline))) # if not border and 'isback' in config and config['isback'] and border==False: # extruded = solid.mirror([1,0,0])(extruded ) if 'colour' in config and config['colour']: extruded = solid.color(self.scad_colour(config['colour']))(extruded) p=self p.rotations_to_3D() while(p and type(p) is not Plane and not ( hasattr(p,'renderable') and p.renderable())):# and (c==0 or not p.renderable() )): if hasattr(p, 'transform') and p.transform is not None and p.transform is not False and 'matrix3D' in p.transform: if type(p.transform['matrix3D'][0]) is list or type(p.transform['matrix3D'][0]) is Vec: extruded=solid.translate([-p.transform['matrix3D'][0][0], -p.transform['matrix3D'][0][1],-p.transform['matrix3D'][0][2]])(extruded) extruded=solid.multmatrix(m=p.transform['matrix3D'][1])(extruded) extruded=solid.translate([p.transform['matrix3D'][0][0], p.transform['matrix3D'][0][1],p.transform['matrix3D'][0][2]])(extruded) else: extruded=solid.multmatrix(m=p.transform['matrix3D'])(extruded) if hasattr(p, 'transform') and p.transform is not None and p.transform is not False and 'rotate3D' in p.transform: if type(p.transform['rotate3D'][0]) is list or type(p.transform['rotate3D'][0]) is Vec: if p.transform['rotate3D'][0]!=[0,0,0]: extruded=solid.translate([-p.transform['rotate3D'][0][0], -p.transform['rotate3D'][0][1],-p.transform['rotate3D'][0][2]])(extruded) extruded=solid.rotate([p.transform['rotate3D'][1][0], p.transform['rotate3D'][1][1],p.transform['rotate3D'][1][2] ])(extruded) if p.transform['rotate3D'][0]!=[0,0,0]: extruded=solid.translate([p.transform['rotate3D'][0][0], p.transform['rotate3D'][0][1],p.transform['rotate3D'][0][2]])(extruded) else: extruded=solid.rotate([p.transform['rotate3D'][0], p.transform['rotate3D'][1],p.transform['rotate3D'][2] ])(extruded) if hasattr(p, 'transform') and p.transform is not None and p.transform is not False and 'translate3D' in p.transform: extruded=solid.translate([p.transform['translate3D'][0], p.transform['translate3D'][1],p.transform['translate3D'][2] ])(extruded) p=p.parent # if (hasattr(p,'renderable') and p.renderable and p.obType == "Part"): # if(p.border is not False and p.border is not None): # if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'matrix3D' in self.transform: # if type(self.transform['matrix3D'][0]) is list: # extruded=solid.translate([-self.transform['rotate3D'][0][0], - self.transform['rotate3D'][0][1]])(extruded) # extruded=solid.multmatrix(m=self.transform['matrix3D'][0])(extruded) # extruded=solid.translate([self.transform['rotate3D'][0][0], self.transform['rotate3D'][0][1]])(extruded) # else: # extruded=solid.multmatrix(m=self.transform['matrix3D'])(extruded) # if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'rotate3D' in self.transform: # if type(self.transform['rotate3D'][0]) is list or type(self.transform['rotate3D'][0]) is Vec: # extruded=solid.translate([-self.transform['rotate3D'][1][0], - self.transform['rotate3D'][1][1], - self.transform['rotate3D'][1][2]- zoffset])(extruded) # extruded=solid.rotate([self.transform['rotate3D'][0][0], self.transform['rotate3D'][0][1],self.transform['rotate3D'][0][2] ])(extruded) # extruded=solid.translate([self.transform['rotate3D'][1][0], self.transform['rotate3D'][1][1], self.transform['rotate3D'][1][1] + zoffset])(extruded) # else: # extruded=solid.rotate([self.transform['rotate3D'][0], self.transform['rotate3D'][1],self.transform['rotate3D'][2] ])(extruded) # if hasattr(self, 'transform') and self.transform is not None and self.transform is not False and 'translate3D' in self.transform: # extruded=solid.translate([self.transform['translate3D'][0], self.transform['translate3D'][1],self.transform['translate3D'][2] ])(extruded) return [extruded]