gumyr / cq_warehouse

A cadquery parametric part collection
Apache License 2.0
107 stars 23 forks source link

custom threads / nuts #79

Closed MarcWeber closed 1 year ago

MarcWeber commented 1 year ago

I had a use case pencil like thing with protection cap I wanted to do with screws This is what I came up with with gumyr's help. And it works :-) The question is whether / how to integrate such features into core library only requiring threads no head no nuts etc.

The idea here is to have the workplane with a location and using that (and it's orientation) to build the screw with some 'gaps' cutting from original cylinder so that it just works. The external/internal ThreadCuttings thus build a cylinder to be cutted to then add the threads. However cutting from an object which has a thread added doesn't work well. Thus the cylinders to be cutted can be applied first, then all threads get added.

So this example illustrates how to move a thread using fastener.locate and building the cylinder to be cutted from workplane. Probably there are better ways but it works.

import cadquery as cq
from copy import copy
from cq_warehouse.fastener import IsoThread, SocketHeadCapScrew

def externalThreadCutting(fastener, clearance = None, workplane=None, simple = False, **kwargs):
    """
    cyl = (cq.Workplane("XY").circle(12/2).extrude(20))
    thread = IsoThread( major_diameter = 12, pitch = 1, length = 10)
    cyl = externalThreadCutting(fastener=thread, clearance = 12)
    """
    if workplane == None:
        workplane = cq.Workplane("XY")

    root_radius = fastener.root_radius
    length = fastener.length
    if simple:
        fastener = (workplane.circle(fastener.apex_radius).circle(root_radius).extrude(length))
    else:
        fastener = fastener.locate(workplane.plane.location)
    if clearance == None:
        clearance = 13
# face = thing.faces(faces),
    tube = workplane.circle(clearance).circle(root_radius).extrude(length)
# return thing.faces(faces).workplane().cut(tube).add(fastener)
    return {"cut": tube, "fastener": fastener}

def internalThreadCutting(fastener, simple = False, workplane = None, **kwargs):
    if workplane == None:
        workplane = cq.Workplane("XY")
    apex_radius = fastener.apex_radius
    root_radius = fastener.root_radius
    length = fastener.length
    if simple:
        fastener = (workplane.circle(apex_radius).circle(root_radius).extrude(length))
    else:
        fastener = fastener.locate(workplane.plane.location)
    tube = workplane.circle(apex_radius).extrude(length)
    return {"cut": tube, "fastener": fastener}

def _applyFasteners(self, *list):
    cut_fasteners = []
    for l in list:
        x = copy(l)
        ff = x.pop("fastenerFunction")
        if "workplane" in x:
            workplane = x.pop("workplane")
        fastener = ff(**x)
        l["fastener"] = fastener
        cut_fasteners.append(externalThreadCutting(**l) if l["fastener"].external else internalThreadCutting(**l))

    thing = self
    for x in cut_fasteners:
        thing = thing - x["cut"]
    for x in cut_fasteners:
        thing = thing.add(x["fastener"])
    return thing

cq.Workplane.applyFasteners = _applyFasteners

height = 50

D = 12
cap_D = 8
cyl = (cq.Workplane("XY").circle(D/2+10).extrude(height))
thread_fit = 0.1 # eg for resin printers

wp_bottom = cq.Workplane("XY")
wp_top     = cq.Workplane("XY", origin = (0,0, height) ).transformed(rotate=cq.Vector(180,0,0))

fasteners = [
    {"major_diameter" : D, "pitch" : 1, "external" : True, "length" : 5, "simple" : False, "workplane": wp_bottom, "fastenerFunction": IsoThread},
    {"major_diameter" : D, "pitch" : 1, "external" : True, "length" : 5, "simple" : False, "workplane": wp_top, "fastenerFunction": IsoThread},
    {"major_diameter" : cap_D + thread_fit, "pitch" : 1, "external" : False, "length" : 5, "simple" : False, "workplane": wp_top, "fastenerFunction": IsoThread}
]

cyl = cyl.applyFasteners(*fasteners)
gumyr commented 1 year ago

Here is your objected as I intended cq_warehouse.threads to be used:

import cadquery as cq
from cq_warehouse.fastener import IsoThread
import cq_warehouse.extensions

height, D, cap_D, recess_width = 50, 12, 8, 7
ext = IsoThread(major_diameter=D, pitch=1, length=5)
int = IsoThread(major_diameter=cap_D, pitch=1, length=5, external=False)
outer_radius = ext.min_radius + 10

profile = (
    cq.Sketch()
    .push([(outer_radius / 2, height / 2)])
    .rect(outer_radius, height)
    .push(
        [
            (ext.min_radius + recess_width / 2, ext.length / 2),
            (ext.min_radius + recess_width / 2, height - ext.length / 2),
        ]
    )
    .rect(recess_width, ext.length, mode="s")
    .push([(int.min_radius / 2, height - int.length / 2)])
    .rect(int.min_radius, int.length, mode="s")
    .clean()
)
cyl = (
    cq.Workplane("XZ")
    .placeSketch(profile)
    .revolve()
    .add(
        [
            ext.cq_object,
            ext.cq_object.moved(cq.Location(cq.Vector(0, 0, height - ext.length))),
            int.cq_object.moved(cq.Location(cq.Vector(0, 0, height - int.length))),
        ]
    )
)

show_object(cyl, "cyl")
show_object(profile, "profile")

image

gumyr commented 1 year ago

.. shouldn't have used int as a variable name..