示例#1
0
 def tabs(self):
     c = math.cos(TAB_ANGLE)
     s = math.sin(TAB_ANGLE)
     alpha = 1.0 / (math.sqrt(4.0 - c * c) / (2 * s))
     post = self.inner_cylinder()
     ytabs = scale([2, alpha, 1])(post)
     xtabs = scale([alpha, 2, 1])(post)
     return self.tab_cylinder() * (xtabs + ytabs)
示例#2
0
    def ell_main():
        return S.translate([0, 0, pot_w])(
            S.difference()(
                S.scale([1, pot_lw_ratio, 1])(S.sphere(pot_w)),

                # cut out inside
                S.scale([s2, pot_lw_ratio * s2, s2])(S.sphere(pot_w)),
                S.translate([-rectl / 2, -rectl / 2,
                             -pot_w + 50])(S.cube([rectl, rectl, rectl])),
            ))
示例#3
0
def scaffold(length, color=[0, 0, 1, 1]):
    h = solid.translate([diameter / 2.0, 0, length / 2.0])(
        solid.scale([diameter, diameter, length])(
            solid.cube(center=True)
            )
    ) + solid.translate([0, 0, length / 2.0])(
        solid.scale([center_notch + tool_radius * 2,
                     center_notch, length])(
            solid.cube(center=True)
            )
    )
    # scale & move in Z to ensure overlap
    h = solid.translate([0, 0, -(length * .1)/2.0])(solid.scale([1, 1, 1.1])(h))
    return solid.color(color)(h)
示例#4
0
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)
示例#5
0
文件: pegs.py 项目: orwonthe/print3d
def slot_peg_with_catch(
        diameter=DEFAULT_PEG_DIAMETER,
        thickness=DEFAULT_HOLDER_THICKNESS,
        clearance=DEFAULT_CLEARANCE,
        overreach=None,
        slot_width=None,
        slot_clearance=DEFAULT_CONTAINER_CLEARANCE
):
    if overreach is None:
        overreach = diameter
    peg = solid_peg(diameter=diameter, thickness=thickness, clearance=clearance, overreach=overreach)
    if slot_width is None:
        slot_width = 0.35 * min(diameter, overreach)
    hole_height = clearance + overreach
    hole_displacement = slot_clearance + thickness + 0.5 * hole_height
    round_hole_bottom = back(0.5 * hole_height)(
        cylinder(r=0.5 * slot_width, h=2 * diameter, center=True, segments=16))
    hole_cutout = cube([slot_width, hole_height, 2 * diameter], center=True)
    hole = up(hole_displacement)(
        rotate([90.0, 0.0, 0.0])(
            hole_cutout + round_hole_bottom))
    catch_height = clearance + thickness + 0.5 * diameter
    catch_offset = 0.5 * diameter
    unscaled_catch = up(catch_height)(
        rotate([90, 0, 0])(
            cylinder(r=0.5 * diameter, h=0.5 * slot_width, center=True, segments=4)))
    catch = scale([0.4, 1.0, 1.0])(unscaled_catch)
    return peg \
           + left(catch_offset)(catch) \
           + right(catch_offset)(catch) \
           - hole
示例#6
0
def key_handle():
    radius = HANDLE_DIAMETER / 2
    base = scale([1.5, 1.0, 1.0])(cylinder(r=radius,
                                           h=KEY_HEIGHT,
                                           center=True,
                                           segments=32))
    return back(radius)(base)
示例#7
0
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 scaledipad(x, y, height):
    """Returns an instance of the iPad scaled to have x, y mm extra size, at height"""
    xscale = (IPAD_W + x) / IPAD_W
    yscale = (IPAD_H + y) / IPAD_H
    print(xscale, yscale)
    o = linear_extrude(height)(polygon(IPAD_POINTS))
    return scale([xscale, yscale, 1])(o)
示例#9
0
 def test_same_transformations_nonequivalent_types(self) -> None:
     self.assertFalse(
         affinables.Affinable._same_transformations(
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.scale([31.0, 32.0, 33.0]),
             ],
             [
                 solid.translate([11.0, 12.0, 13.0]),
                 solid.scale([21.0, 22.0, 23.0]),
                 solid.rotate([31.0, 32.0, 33.0]),
             ],
         ),
         msg="Similar transformations with different types are not the same",
     )
示例#10
0
 def test_same_transformations_equivalent(self) -> None:
     self.assertTrue(
         affinables.Affinable._same_transformations(
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.scale([31.0, 32.0, 33.0]),
             ],
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.scale([31.0, 32.0, 33.0]),
             ],
         ),
         msg="Identical transformations should be considered the same",
     )
示例#11
0
 def test_same_transformations_different_order(self) -> None:
     self.assertFalse(
         affinables.Affinable._same_transformations(
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.scale([31.0, 32.0, 33.0]),
             ],
             [
                 solid.scale([31.0, 32.0, 33.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.rotate([11.0, 12.0, 13.0]),
             ],
         ),
         msg=
         "Identical transformations in different orders should not be considered the same",
     )
示例#12
0
 def create_cutter(addr, left_or_right_dir):
     pos = switches.get_switch_position(addr)
     pos = Point3(*pos, 0)
     angle = switches.get_switch_angle(addr)
     cutter = switches.create_switch(addr, size=1)
     cutter = sc.translate(pos)(sc.scale(
         (4, 4, 0))(sc.translate(pos * -1)(cutter)))
     offset = utils.unit_point2(angle) * 2.5 * left_or_right_dir
     cutter = sc.translate((*offset, 0))(cutter)
     return cutter
示例#13
0
 def __call__(self, scaling=nscale_inches):
     table = self.picnic_table_top() + self.picnic_seats() + self.picnic_frame()
     if self.add_support:
         table += self.support()
     return scale(1 * scaling)(
         up(self.table_length/2) (
             rotate([90, 0, 0]) (
                 table
             )
         )
     )
示例#14
0
    def test_transform_dispatch_scaling(self) -> None:
        holonomic_transformable = MockHolonomicTransformable()

        scaling = solid.scale([31.0, 32.0, 33.0])
        holonomic_transformable.transform(scaling)

        self.assertEqual(
            holonomic_transformable._scalings,
            [scaling],
            msg="Scaling should be correctly dispatched",
        )
示例#15
0
    def test_transform_equivalent(self) -> None:
        original_connector = connector.Connector(
            point=vector.Vector.from_raw([1, 1, 1]),
            axis=vector.Vector.from_raw([0, 0, 1]),
            normal=vector.Vector.from_raw([1, 0, 0]),
        )

        self.assertEqual(
            original_connector.rotate(solid.rotate(a=[90, 90, 90])).translate(
                solid.translate([1, 1, 1])).scale(solid.scale([2, 2, 2])),
            original_connector.transform([
                solid.rotate(a=90, v=[1, 0, 0]),
                solid.rotate(a=90, v=[0, 1, 0]),
                solid.rotate(a=90, v=[0, 0, 1]),
                solid.translate([1, 1, 1]),
                solid.scale([2, 2, 2]),
            ]),
            msg=
            "Transform should accept & correctly dispatch multiple transformations",
        )
示例#16
0
文件: scad.py 项目: NVSL/CaseMaker
def rect2scad(rect, height, z_start = 0.0, mirrored = False):
    """
    Convert a Rectangle into an openscad cube by giving it a height and Z start
    """
    scad_cube = sc.translate([rect.left(), rect.bot(), z_start])(
        sc.cube([rect.width, rect.height, height])
    )
    if mirrored:
        return sc.scale([1,1,-1])(scad_cube)
    else:
        return scad_cube
示例#17
0
def key_lettering():
    messages = ["LeRoy", "Dental"]
    layout = union()([
        forward((index - 0.5) * -LETTER_HEIGHT)(text(message,
                                                     halign='center',
                                                     valign='center'))
        for index, message in enumerate(messages)
    ])
    lettering = up(THICKNESS / 2 - LETTERING_RISE)(
        linear_extrude(height=2 * LETTERING_RISE)(scale([0.8, 0.8,
                                                         1])(layout)))
    return back(HANDLE_DIAMETER / 2)(lettering)
示例#18
0
    def test_eq_different_transformations(self) -> None:
        transformable_1 = MockHistoricalTransformable()
        transformable_1.transform(solid.rotate([11.0, 12.0, 13.0]))

        transformable_2 = MockHistoricalTransformable()
        transformable_2.transform(solid.scale([31.0, 32.0, 33.0]))

        self.assertNotEqual(
            transformable_1,
            transformable_2,
            msg=
            "Two historical transformables with different transformations are not equal",
        )
示例#19
0
def shell(obj: OpenSCADObject, wall: float, center_x: float, center_y: float,
          center_z: float) -> OpenSCADObject:
    '''
    create a shell of a given object centered at [center_x, center_y, center_z] scaled by wall as a percentage.
    PARAMETERS:
        center_x, center_y, center_z: center coordinates of the hollow shell
        wall: thickness of the shell given as a percentage of the given object
    RETURNS:
        shell of given OpenSCADObject
    '''
    solution = obj - hole()(translate([
        center_x / 2, center_y / 2, center_z / 2
    ])(scale([wall, wall, wall])(obj)))
    return solution
示例#20
0
 def test_same_transformations_different_lengths(self) -> None:
     self.assertFalse(
         affinables.Affinable._same_transformations(
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0]),
                 solid.scale([31.0, 32.0, 33.0]),
             ],
             [
                 solid.rotate([11.0, 12.0, 13.0]),
                 solid.translate([21.0, 22.0, 23.0])
             ],
         ),
         msg="Transformation lists of different lengths are not the same",
     )
示例#21
0
def create_model(data, rows, columns):
    hexes = to_base(int.from_bytes(data, 'big'), 720)
    assert len(hexes) == rows * columns

    sts = []
    for row in range(rows):
        for col in range(columns):
            order = permute.permute([1, 2, 3, 4, 5, 6], hexes[row * 9 + col])

            heights = [1 + 1.5 * i / len(order) for i in order]
            st = stump(heights)

            xdiff = 1.5 * col
            xdiff *= 1.2
            ydiff = (-3**0.5) * row - (3**0.5 / 2) * col
            ydiff *= 1.2
            st = solid.translate([xdiff, ydiff, 0])(st)

            sts.append(st)

    base = solid.translate([-3.4, -3.2, 0])(solid.rotate([0, 0, -30])(
        solid.scale([22, 6.5, 0.3])(solid.cube(1))))

    x, y = 0.5, (3**0.5 / 2) / 2
    marker = solid.translate([-1.5, -2.5, 0])(
        solid.polyhedron(
            points=[
                # bottom
                (-x, -y, 0),
                (x, -y, 0),
                (0, y, 0),
                # top
                (-x, -y, 1),
                (x, -y, 1),
                (0, y, 1)
            ],
            faces=[
                # bottom
                (0, 1, 2),
                # top
                (5, 4, 3),
                # sides
                (0, 3, 4, 1),
                (1, 4, 5, 2),
                (2, 5, 3, 0)
            ]))

    return solid.union()([base, solid.union()(sts), marker])
示例#22
0
def combine_shapes(shapes):
  """
  Transform and combine shapes as in all_shapes below using OpenSCAD
  generators and functions.

  Args:
    shapes = (open_pl, squarcle_pl, star_pl)

  Retruns:
    A single PolyLine with transformed and combined geometry
  """
  open_pl, squarcle_pl, star_pl = shapes
  small_open_pl = solid.scale(0.5)( open_pl.get_generator() )
  trans_squarcle_pl = solid.translate([0,50])( squarcle_pl.get_generator() )
  trans_rot_star_pl = solid.translate([50,175])( solid.rotate(numpy.pi/2)( star_pl.get_generator() ) )
  combined = (small_open_pl + trans_squarcle_pl + trans_rot_star_pl)
  return PolyLine(generator=combined)
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
示例#24
0
    def test_transformed_object(self) -> None:
        transformations = [
            solid.rotate([11.0, 12.0, 13.0]),
            solid.translate([21.0, 22.0, 23.0]),
            solid.scale([31.0, 32.0, 33.0]),
        ]

        transformable = MockHistoricalTransformable()
        transformable.transform(transformations)

        transformed_nonaffinable = transformable.cube

        self.assertTrue(
            affinables.Affinable._same_transformations(
                transformations,
                # Just extract the transformations to compare
                utils.flatten_openscad_children(transformed_nonaffinable)[1:],
            ),
            msg=
            "The property should be correctly transformed, identically to the parent Affinable",
        )
示例#25
0
    def test_transformed_solid_transformation(self) -> None:
        transformations = [
            solid.rotate([11.0, 12.0, 13.0]),
            solid.translate([21.0, 22.0, 23.0]),
            solid.scale([31.0, 32.0, 33.0]),
        ]

        transformable = MockHistoricalTransformable()
        transformable.transform(transformations)

        cube = solid.cube(1)
        transformed_nonaffinable = transformable.transformed(cube)

        self.assertTrue(
            affinables.Affinable._same_transformations(
                transformations,
                # Just extract the transformations to compare
                utils.flatten_openscad_children(transformed_nonaffinable)[1:],
            ),
            msg=
            "Transformation application order should be unchanged, and application shouldn't alter transformations",
        )
示例#26
0
    def __post_init__(self):
        """
        create the dish

        calculates
            chord_length: y offest
        """
        if self.top_dims is None:
            self.top_dims = [12.16, 14.16]

        self.key_x, self.key_y = self.top_dims

        if self.dish_type == "cylinder":
            c_length = chord_length(self.key_x, self.depth)
            radius = rad_chord(self.key_x, self.depth)

            self.dish = s.cylinder(h=100,
                                   r=radius,
                                   center=True,
                                   segments=self.segments)

            if self.inverted:
                self.dish = s.translate([0, -c_length, 0])(self.dish)
            else:
                self.dish = s.translate([0, c_length, 0])(self.dish)

            self.dish = s.rotate([90, 0, 0])(self.dish)

        elif self.dish_type == "sphere":
            self.chord = pow((pow(self.key_x, 2) + pow(self.key_y, 2)), .5)

            c_length = chord_length(self.chord, self.depth)

            self.radius = rad_chord(self.chord, self.depth)
            self.dish = s.scale([
                self.chord / 2 / self.depth, self.chord / 2 / self.depth
            ])(s.sphere(r=self.depth, segments=self.segments))

        self.dish = s.translate([0, 0, self.key_z])(self.dish)
示例#27
0
    def test_scaling(self) -> None:
        original_connector = connector.Connector(
            point=vector.Vector.from_raw([2, 2, 2]))
        translated_connector = original_connector.scale(solid.scale([1, 2, 3]))

        self.assertEqual(
            translated_connector.point,
            vector.Vector.from_raw([2, 4, 6]),
            msg="The connector point should be scaled as expected",
        )

        self.assertEqual(
            translated_connector.axis,
            original_connector.axis,
            msg="The primary alignment axis should be unchanged under scaling",
        )

        self.assertEqual(
            translated_connector.normal,
            original_connector.normal,
            msg=
            "The secondary alignment axis should be unchanged under scaling",
        )
示例#28
0
def grabbers():
    gripper_shape = [CLAMP_GRIP_WIDTH, CLAMP_LENGTH, CLAMP_GRIP_HEIGHT]
    gripper = grounded_cube(gripper_shape)
    gripper_offset = (CLAMP_BASE_WIDTH - CLAMP_GRIP_WIDTH) / 2
    grippers = left_right_symmetric(gripper_offset, gripper)

    bumper_shape = [CLAMP_BUMPER_WIDTH, CLAMP_LENGTH, CLAMP_BUMPER_THICKNESS]
    bumper_elevation = CLAMP_GRIP_HEIGHT - CLAMP_BUMPER_THICKNESS
    bumper_offset = (CLAMP_BASE_WIDTH - CLAMP_BUMPER_WIDTH) / 2

    round_bumper_radius = CLAMP_BUMPER_WIDTH / 2
    shrinkage = CLAMP_BUMPER_THICKNESS / round_bumper_radius
    round_bumper = right(0)(
        rotate([90, 0, 0])(
            scale([1.0, shrinkage, 1.0])(
                cylinder(r=round_bumper_radius, h=CLAMP_LENGTH, segments=4, center=True))))

    bumper = up(bumper_elevation)(round_bumper)
    # grounded_cube(bumper_shape) + )
    bumpers = left_right_symmetric(bumper_offset, bumper)

    clipper_shape = [CLAMP_BASE_WIDTH, CLAMP_LENGTH, CLAMP_TOP_CLIP]
    clipper = grounded_cube(clipper_shape)
    return (grippers + bumpers) * clipper
SLIP = 1  # Space around the edges to allow the iPad to slide in
WALL_PADDING = 0.4

BUTTON_TO_EDGE = 3.7
SCREEN_BORDER = 3
BUTTON_D = 14  # Oversize to also cover the camera and light sensor

START_X = -100.000162
START_Y = -67.209085

POWER_CONNECTOR_LEN = 7
POWER_UNDERHANG = 20
LOCK_BUTTON_LEN = 12
LOCK_BUTTON_TO_EDGE = 9

BASE_IPAD = translate([0, IPAD_H, 0])(rotate([0, 0, -90])(scale(
    [25.4, 25.4, 25.4])(import_stl("iPadMini.STL")))).set_modifier("#")
SCREEN_BOTTOM_EDGE = (IPAD_H - SCREEN_H) / 2
SCREEN_SIDE_EDGE = (IPAD_W - SCREEN_W) / 2
BASE_IPAD -= translate([SCREEN_SIDE_EDGE, SCREEN_BOTTOM_EDGE,
                        IPAD_D - 0.1])(cube([SCREEN_W, SCREEN_H,
                                             0.2]).set_modifier("%"))
IPAD_STL = translate([IPAD_W, IPAD_H, 0])(rotate([0, 0, 180])(BASE_IPAD))


def screwthread():
    """Returns a screw thread"""
    if DEV_MODE:
        return cylinder(d=6, h=15).set_modifier("#")
    else:
        return metric_thread(diameter=6, length=15)
示例#30
0
  # Lets create circles/holes for the joints and position them #
  c_circle= PolyLine(generator= solid.circle(3.5)).simplified()
  c_circle_lst=[]

  for i in range(len(joints_pose_tr)):
    d = c_circle.clone()
    trl_mtx = translation_matrix(joints_pose_tr[i])
    d *= trl_mtx
    c_circle_lst.append(d)

  p_bBox= p.bounding_box().points
  
  p_w = numpy.linalg.norm(pts_to_vec(p_bBox[0],p_bBox[3]))
  p_h = numpy.linalg.norm(pts_to_vec(p_bBox[0],p_bBox[1]))

  scale_factor_x = ternary_w/p_w
  scale_factor_y = ternary_h/p_h

  p_scl = PolyLine(generator= solid.scale([scale_factor_x,scale_factor_y,1])(p.get_generator())).simplified()

  for i in range(len(a_circle_lst)):
    p_scl = p_scl | a_circle_lst[i]
   
  # Lets create layers #
  p_scl_layer= Layer(p_scl, color= 'red')
  circle_layer= Layer(c_circle_lst, color='red')

  Final_block = Block([p_scl_layer, circle_layer])

  a_layout= Layout(blocks= Final_block).save('opt_foot.dxf')
示例#31
0
def battery_cup():
    height = 39 * mm
    diameter = 36 * mm
    squish = 31 * mm / diameter
    thickness = 2 * mm
    return scale([1.0, squish, 1.0])(cup(diameter, height, thickness))
示例#32
0
    ddiam1, ddiam2 = cdiam1+0.07, cdiam2+0.07
    #      ...flange.....    ....center....     ...outer tube...
    dii = [cdiam1, cdiam1,   cdiam1, cdiam2,    diam2,    diam2]
    doo = [1.70,   1.70,     ddiam1, ddiam2,    diam1,    diam1]
    hss = [0.00, baseThik,   baseThik,  cHi,   baseThik,  thredHi]
    topAsm = cylinderAsm(dii, doo, hss) # Make assembly-of-cylinders

    # Bottom piece: Specify inner and outer diameters, plus heights
    thredSlop = 0.05            # Oversize to avoid thread binding
    # The bottom's ID is top's OD plus some slop for clearance
    diam1, diam2 = thredOD+thredSlop+0.20, thredOD+thredSlop
    moveTop = (doo[0]+diam1+.1)/2 # Left-shift for top & bottom parts
    ringHi = 0.35
    turns = ringHi/pitch
    dii = [diam2, diam2]
    doo = [diam1, diam1-0.10]
    hss = [0, ringHi]
    botAsm = cylinderAsm(dii, doo, hss) # Make assembly-of-cylinders
    botThred = threadAsm(0, diam2, thredThik, pitch, 3, turns, False)
        
    # Assemble items and apply scale factor
    asm, bot, top = None,  botAsm + botThred,  topThred+topAsm
    if makeConn: asm = left(moveTop)(top)
    if makeRing: asm = asm + bot if asm else bot
    asm = scale((sf, sf, sf))(asm)
    cylSegments, version = 60, 3
    cylSet_fn = '$fn = {};'.format(cylSegments)
    asmFile = 'flanged-tube{}.scad'.format(version)
    scad_render_to_file(asm, asmFile, file_header=cylSet_fn, include_orig_code=False)
    print ('Wrote scad code to {}'.format(asmFile))