CadQuery / cadquery

A python parametric CAD scripting framework based on OCCT
https://cadquery.readthedocs.io
Other
2.93k stars 276 forks source link

Mating two gears with teeth #1607

Open lenianiva opened 1 week ago

lenianiva commented 1 week ago

I have two gears that have teeth on them:

image

How would I make tagged objects and constraints so that the gears will mesh? Ideally I want to supply two values for this assembly:

  1. The offset, which specifies how far apart the gears should be
  2. The angle, which specifies the relative angles between the two gears along their axes Is there a way to do this? I tried to tag a circle wire that is forConstruction but wires cannot be used as axis or plane constraints.
import cadquery as Cq

base_height = 60
radius = 60;
radius_centre = 30
radius_inner = 40
n_tooth = 10
def create():
    teeth = (
        Cq.Workplane("XY")
        .polarArray(radius=radius_inner, startAngle=0, angle=360, count=n_tooth)
        .eachpoint(lambda loc: Cq.Solid.makeBox(radius - radius_inner, 2, 2).located(loc))
        .union()
    )
    base = (
        Cq.Workplane('XY')
        .cylinder(
            height=base_height,
            radius=radius,
            centered=(True, True, False))
        .cylinder(
            height=base_height,
            radius=radius_centre,
            combine='s',
            centered=(True, True, True))
        .union(teeth.val().move(0, 0, base_height))
    )
    base.faces(">Z").tag("mate")
    return base

obj1 = create()
obj2 = create()
result = (
    Cq.Assembly()
    .add(obj1, name="obj1", color=Cq.Color(0.8,0.8,0.5,0.3))
    .add(obj2, name="obj2", color=Cq.Color(0.5,0.5,0.5,0.3), loc=Cq.Location(0,0,40))
    .constrain("obj1?mate", "obj2?mate", "Plane")
    .solve()
)
show_object(result)
lorenzncode commented 1 week ago

First, here is an example without using constraints (also an example using Sketch).

import cadquery as cq

base_height = 60
radius = 60
radius_centre = 30
radius_inner = 40
n_tooth = 10

offset = 2 * radius - 20
angle = 3

def create():
    teeth_sk = (
        cq.Sketch()
        .parray(radius_inner + (radius - radius_inner) / 2, 0, 360, n_tooth)
        .rect(radius - radius_inner, 2)
    )
    teeth = (
        cq.Workplane(origin=(0, 0, base_height))
        .placeSketch(teeth_sk)
        .extrude(2)
        .tag("teeth")
    )

    base = (
        cq.Workplane()
        .sketch()
        .circle(radius)
        .circle(radius_centre, "s")
        .finalize()
        .extrude(base_height)
        .tag("base")
    )

    gear = base.union(teeth)

    return gear

obj1 = create()
obj2 = create()

result = (
    cq.Assembly()
    .add(obj1, name="obj1", color=cq.Color(0.8, 0.8, 0.5, 0.3))
    .add(
        obj2,
        name="obj2",
        color=cq.Color(0.5, 0.5, 0.5, 0.3),
        loc=cq.Location((0, 0, 2 * base_height + 2))
        * cq.Location((offset, 0, 0), (180, 0, angle)),
    )
)

Here is a try with constraints. I'm not a gear expert and you may find other/better ways to do it.

Change contact, and angle values.

import cadquery as cq
from cadquery.occ_impl.shapes import vertex 

base_height = 60
radius = 60
radius_centre = 30
radius_inner = 40
n_tooth = 10

contact = 0.8  # tooth contact point range [0, 1]
angle = -10

def create():
    teeth_sk = (
        cq.Sketch()
        .parray(radius_inner + (radius - radius_inner) / 2, 0, 360, n_tooth)
        .rect(radius - radius_inner, 2)
    )
    teeth = (
        cq.Workplane(origin=(0, 0, base_height))
        .placeSketch(teeth_sk)
        .extrude(2)
        .tag("teeth")
    )

    base = (
        cq.Workplane()
        .sketch()
        .circle(radius)
        .circle(radius_centre, "s")
        .finalize()
        .extrude(base_height)
        .tag("base")
    )

    gear = base.union(teeth)

    gear.solids(">X", tag="teeth").faces(">Z").edges("<Y").tag("edge1")  # gear1 edge
    gear.solids("<X", tag="teeth").faces("<Z").edges("<Y").tag("edge2")  # gear2 vertex
    gear.solids("<X", tag="teeth").faces("<Z").vertices("<XY").tag(
        "vertex2"
    )  # gear2 vertex

    return gear

obj1 = create()
obj2 = create()

p1 = obj1.edges(tag="edge1").val().positionAt(1 - contact)
vertex1 = vertex(p1)
vertex2 = obj2.vertices(tag="vertex2").val()

result = (
    cq.Assembly()
    .add(obj1, name="obj1", color=cq.Color(0.8, 0.8, 0.5, 0.3))
    .add(
        obj2,
        name="obj2",
        color=cq.Color(0.5, 0.5, 0.5, 0.3),
    )
    .constrain("obj1", "Fixed")
    .constrain("obj2", "FixedRotation", (180, 0, angle))
    .constrain("obj1", vertex1, "obj2", vertex2, "Point")
    .solve()
)

gear1 = result.objects.get("obj1")
gear2 = result.objects.get("obj2")
gear1 = gear1.obj.val().moved(gear1.loc)
gear2 = gear2.obj.val().moved(gear2.loc)

# check for intersection
intersect = gear1.intersect(gear2)
if intersect.Volume() > 1e-6:
    raise ValueError("Gears intersect!")

If you are not on the lastest master branch, remove the vertex import and change: vertex1 = vertex(p1) to vertex1 = cq.Vertex.makeVertex(*p1.toTuple())

lenianiva commented 5 days ago

First, here is an example without using constraints (also an example using Sketch).

import cadquery as cq

base_height = 60
radius = 60
radius_centre = 30
radius_inner = 40
n_tooth = 10

offset = 2 * radius - 20
angle = 3

def create():
    teeth_sk = (
        cq.Sketch()
        .parray(radius_inner + (radius - radius_inner) / 2, 0, 360, n_tooth)
        .rect(radius - radius_inner, 2)
    )
    teeth = (
        cq.Workplane(origin=(0, 0, base_height))
        .placeSketch(teeth_sk)
        .extrude(2)
        .tag("teeth")
    )

    base = (
        cq.Workplane()
        .sketch()
        .circle(radius)
        .circle(radius_centre, "s")
        .finalize()
        .extrude(base_height)
        .tag("base")
    )

    gear = base.union(teeth)

    return gear

obj1 = create()
obj2 = create()

result = (
    cq.Assembly()
    .add(obj1, name="obj1", color=cq.Color(0.8, 0.8, 0.5, 0.3))
    .add(
        obj2,
        name="obj2",
        color=cq.Color(0.5, 0.5, 0.5, 0.3),
        loc=cq.Location((0, 0, 2 * base_height + 2))
        * cq.Location((offset, 0, 0), (180, 0, angle)),
    )
)

Here is a try with constraints. I'm not a gear expert and you may find other/better ways to do it.

Change contact, and angle values.

import cadquery as cq
from cadquery.occ_impl.shapes import vertex 

base_height = 60
radius = 60
radius_centre = 30
radius_inner = 40
n_tooth = 10

contact = 0.8  # tooth contact point range [0, 1]
angle = -10

def create():
    teeth_sk = (
        cq.Sketch()
        .parray(radius_inner + (radius - radius_inner) / 2, 0, 360, n_tooth)
        .rect(radius - radius_inner, 2)
    )
    teeth = (
        cq.Workplane(origin=(0, 0, base_height))
        .placeSketch(teeth_sk)
        .extrude(2)
        .tag("teeth")
    )

    base = (
        cq.Workplane()
        .sketch()
        .circle(radius)
        .circle(radius_centre, "s")
        .finalize()
        .extrude(base_height)
        .tag("base")
    )

    gear = base.union(teeth)

    gear.solids(">X", tag="teeth").faces(">Z").edges("<Y").tag("edge1")  # gear1 edge
    gear.solids("<X", tag="teeth").faces("<Z").edges("<Y").tag("edge2")  # gear2 vertex
    gear.solids("<X", tag="teeth").faces("<Z").vertices("<XY").tag(
        "vertex2"
    )  # gear2 vertex

    return gear

obj1 = create()
obj2 = create()

p1 = obj1.edges(tag="edge1").val().positionAt(1 - contact)
vertex1 = vertex(p1)
vertex2 = obj2.vertices(tag="vertex2").val()

result = (
    cq.Assembly()
    .add(obj1, name="obj1", color=cq.Color(0.8, 0.8, 0.5, 0.3))
    .add(
        obj2,
        name="obj2",
        color=cq.Color(0.5, 0.5, 0.5, 0.3),
    )
    .constrain("obj1", "Fixed")
    .constrain("obj2", "FixedRotation", (180, 0, angle))
    .constrain("obj1", vertex1, "obj2", vertex2, "Point")
    .solve()
)

gear1 = result.objects.get("obj1")
gear2 = result.objects.get("obj2")
gear1 = gear1.obj.val().moved(gear1.loc)
gear2 = gear2.obj.val().moved(gear2.loc)

# check for intersection
intersect = gear1.intersect(gear2)
if intersect.Volume() > 1e-6:
    raise ValueError("Gears intersect!")

If you are not on the lastest master branch, remove the vertex import and change: vertex1 = vertex(p1) to vertex1 = cq.Vertex.makeVertex(*p1.toTuple())

Are the teeth of the gear automatically fused with the base during .union? If I were to 3d print them I want them to be fused as one

adam-urbanczyk commented 4 days ago

You can check yourself: len(gear1.Solids()). They are fused into one solid (this is how union works). BTW: if you don't want them to be fused use compound(...)