A work in progress showcase: dovetail function

35 views
Skip to first unread message

W W

unread,
Jun 7, 2025, 3:25:51 AMJun 7
to PythonSCAD

dovetail.png

I used to get away with Prusa slicer's dovetail, but it does not do a "stable" dovetail with a base.

I have an early prototype of a dovetail function in python. I need it for a project I need to finish this weekend :)

Here's a rough sketch:

```py

from openscad import *

nimport('https://raw.githubusercontent.com/wiw-pub/ztools/refs/heads/main/src/ztools.py')

def y_dovetail_cut(solid, dovetail_big_width, dovetail_small_width, dovetail_height, base_offset, epsilon=0.1):
    '''
    Perform dovetail cut across the y-axis. Inspired by traditional Japanese joinery.
   
    Precondition: solid is already positioned on the z axis where top part will have dovetail, and the bottom will not.
   
    When the solid saddles across +z and -z, the -z part of the solid is taken as a base where the dovetail does not apply.
   
    Instead, the -z part of the solid can have an extension applied for the "female" part of the dovetail joint. This is typically done for stability.
   
    Parameters:
        solid: input solid to be dovetail cut.
        dovetail_big_width: the width of the larger dovetail trapezoid.
        dovetail_small_width: the width of the smaller dovetail trapezoid.
        dovetail_height: Perpendicular span of the dovetail trapezoid.
        base_offset: Positive offset extending base for the "female" part of the dovetail (negative not yet supported).
        epsilon: Optional param to combat z-fighting.
    '''
   
   
    def dovetail(h):
        '''
        Helper method to create dovetail base shape.
        '''
        # trapezoid
        pts = [
            [-dovetail_big_width/2, 0],
            [dovetail_big_width/2, 0],
            [dovetail_small_width/2, dovetail_height],
            [-dovetail_small_width/2, dovetail_height],
            [-dovetail_big_width/2, 0]
        ]
        trap = polygon(pts).rotz(90).linear_extrude(h)
        trap = center(trap, axis=[1, 1, 0])[0]
        return trap
       
    mn, mx = bounding_box(solid)
    _, _, dovetail_h = mx
   
    # Finishing detail: Shape the dovetail to be flushed with the input solid.
    dovetail = dovetail(dovetail_h) & solid
   
    # Preprocessing to hollow out where dovetail would sit.
    hollow = solid - dovetail
   
    # Slice the bottom for base offset.
    top, bottom = z_bisect(hollow)
       
    # Cut left and right.
    left, right = y_bisect(hollow)
   
    # Override right with the larger base_offset.
    _, b_right = y_bisect(bottom.right(base_offset))
    b_right = b_right.left(base_offset)
   
    # Attach dovetail to and mask the base offsets.
    # Epsilon for z-fighting.
    male = (left | dovetail) - b_right.scale([1 + epsilon, 1 + epsilon, 1])
    female = right | b_right
   
    return [male, female]
    
# Tested with a "tilted" cube.
kw = {
    'solid': cube([50, 20, 5], center=True).rotz(10).roty(10),
    'dovetail_big_width': 10,
    'dovetail_small_width': 5,
    'dovetail_height': 10,
    'base_offset': 5
}
l, r = y_dovetail_cut(**kw)
show([l.left(20), r.right(20)])

```

The parametric inputs are reasonably simple. The input solid to be dovetail cut, the dimensions of the dovetail, and the optional base_offset for stability.

Love to hear any feedbacks. Once I am done, I will stick in ztools.py somewhere (my own melting pot of higher order abstractions since I am terrible at mathematical expressions).

Raymond West

unread,
Jun 7, 2025, 6:17:15 AMJun 7
to W W, PythonSCAD

Hi,

The dovetail was invented years ago, being a simple method of making a strong, interlocking joint in wood, using hand tools. In that situation, you are usually starting with two separate items to be joined. I expect, in 3d printing, you have one large item, that you need to split apart, so the parts fit into the printer. It is a different situation, using different materials, etc. There are better ways of doing that, and  simpler methods of connecting plastic 3d printed parts.

However, I was not able to run your script, but I would suggest you add an adjustable  tolerance/clearance factor if you want other users to be able to use it on other printers/materials. When you have the male part of the dovetail, use offset, to get a larger shape, and use that to generate the female part. 

hth,

Best wishes,

Ray

--
You received this message because you are subscribed to the Google Groups "PythonSCAD" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pythonscad+...@googlegroups.com.
To view this discussion, visit https://groups.google.com/d/msgid/pythonscad/70aed65c-8db7-403f-b5d7-fef4e93540dan%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

W W

unread,
Jun 7, 2025, 11:17:06 AMJun 7
to Raymond West, PythonSCAD
Hey Ray,


>  There are better ways of doing that, and  simpler methods of connecting plastic 3d printed parts.

Can you tell me more? Love to explore alternatives. Dovetailing was the best I was able to find in my research.

>  but I would suggest you add an adjustable  tolerance/clearance factor if you want other users to be able to use it on other printers/materials.

I plan to do a test print later today.

Tolerances can be tricky since it may be printer/filament/nozzle size dependent.

I see in woodworking, a technique called "Killing the wood" adds chamfer to the dovetail for a snug fit. I may explore this route if I run into issues.

Thanks!
-W

Raymond West

unread,
Jun 7, 2025, 2:04:57 PMJun 7
to PythonSCAD

You use tolerances as a parametric value. There are test prints, or simple enough to generate, for you to try with your printer. Then you use that as a default, but be prepared to vary it. No matter what you use, wood ground steel, titanium, concrete,you can never fit a rod into exactly the same diameter hole. In openscad, where that is tried, you sometimes get 'z-fighting'.

A stronger joint would be a mortice and tenon, with solvent adhesive or a peg through it. But, it depends on the shapes you are trying to join, and if it needs to be dismantled.

Here is a simple part solution, Parametric. The cylinders could be made into cones. Having multiple joints, instead of one large one, increases the surface area for adhesive, takes less length for the same interfacing length, etc. of course, instead of the cylinders, you could use dovetail shapes, but avoid sharp corners. (at a larger scale I'd fillet the sharp corners where the circles meet the slab).

############################################


from openscad import *
# circular lugs join

edge=50  #length of edge to be joined
leng= 30 # distance of overlap
num=5    # number of lugs
diam= edge/(num*2) #diameter of lugs
tol=0.2 # tolerance for fitting
th=10 # thickness of joint



dist= leng+(diam/4)
base= square([leng,edge])

fn=200

def lugs():
   j = diam
   all=[]
   while j < edge:
      all.append(
            circle(d=diam).translate([leng + diam/3, j, 0])
        )
      j += diam * 2
   return union(*all)  
    
    
#lugs().show()    

ma=union(base,lugs())
male=ma.linear_extrude(th)
male.show()

fe= ma.offset(tol)
#fe.show()
fe2= fe.linear_extrude(th+20)

female= difference(base.right(leng).linear_extrude(th),fe2.down(10))
female.show()


##########################################################

Guenther Sohler

unread,
Jun 7, 2025, 2:55:43 PMJun 7
to PythonSCAD
I just tested your dovetail cut and each time I learn something new

**kw can apparently convert a dict into a tuple perfect for function invocation.
I am sure, that i will find scenarios where thats applicable.

But I also found something else:


it appears there is a small gap, when putting the things together. is that intentional ?

W W

unread,
Jun 7, 2025, 4:21:56 PMJun 7
to PythonSCAD
@guenther


> it appears there is a small gap, when putting the things together. is that intentional ?

Hehe, it was not intentional. The epsilon I had for z-fighting artifacts from CSG difference was too big (0.1). 

Changing epsilon to 0.001, the gap is no longer noticeable (and successfully deal with z-fighting).

@ray

>  You use tolerances as a parametric value ... No matter what you use, wood ground steel, titanium, concrete,you can never fit a rod into exactly the same diameter hole.

Parameterizing the tolerance isn't the complex part I was thinking about. The complexity is abstracting that out, e.g., user specify nozzle size and the function can auto-calculate the tolerance on your behalf by default. 

It can still expose tolerance as an explicit parameter, but it's be optional. 

The idea is to have the function parameters be intent-based rather than implementation-based, if possible. At least that's what I would aim for.

>  In openscad, where that is tried, you sometimes get 'z-fighting'

The z-fighting I have been dealing with is built-in constraints with CSG difference operations in openscad. Nothing to do with tolerance (as in, I would still have to do z-fighting whenever I perform difference() even if I am not trying to do a dovetail connection).

>  A stronger joint would be a mortice and tenon, with solvent adhesive or a peg through it. But, it depends on the shapes you are trying to join, and if it needs to be dismantled.

Nice. Thanks for the intro on mortice and tenon. In your experience, does it work well without adhesive?

For the pet project I am doing, I prefer not to use adhesive if possible. It may need to be occasionally dismantled. But not the end of the world...I can always reprint the parts.

>  instead of the cylinders, you could use dovetail shapes, but avoid sharp corners.

Good call. I did not consider using cylindrical shape before. I like this idea a lot. Functionally, I don't think it's any weaker than dovetail.

-W

W W

unread,
Jun 7, 2025, 8:35:24 PMJun 7
to PythonSCAD
Screenshot 2025-06-07 173156.png

Thanks Ray for your suggestion. Doing lug instead of dovetail was a great idea. It is also more printing friendly (curves are smoother to print than sharp corners).

I did a test print with dovetail. To my pleasant surprise, tolerance was not needed.

It does buckle and detach easy if bent. I will be trying another test print with lugs on both sides.

-W

W W

unread,
Jun 7, 2025, 9:57:36 PMJun 7
to PythonSCAD
Happy to report: experiment was a success.

My goal was to "quarter" an I-beam into four pieces that can be printed flat on a FDM printer, and be able to join them after the print with a dovetail/lug locking mechanism.

Figure 1: PythonSCAD render of the "quartered" I-beam rail.

Screenshot 2025-06-07 185107.png

It looks like a mess in pieces, and there's some z-fighting I was not able to overcome. 

However, it doesn't matter when printing, because the z-fighting artifacts are so thin due to small epsilon. The slicer simply removes them.

Figure 2: PrusaSlicer's sliced version of the 4 pieces, scaled down to 20% for test print.
Screenshot 2025-06-07 185253.png

Figure 3, 4, 5: Test print assembled. No tolerance needed. Without any adhesive, the assembly is reasonably snug.

Screenshot 2025-06-07 185443.pngScreenshot 2025-06-07 185455.pngScreenshot 2025-06-07 185505.png

I may rethink the abstraction a bit. But otherwise, I am happy with this prototype outcome. It meets my needs for my actual pet project.

-W

William Adams

unread,
Jun 9, 2025, 6:56:22 PMJun 9
to PythonSCAD
FWIW, I did dovetails (for woodworking) in:


see the modules for cut_pins and cut_tails

William

W W

unread,
Jun 10, 2025, 11:03:37 AMJun 10
to PythonSCAD
Nice. When I revisit dovetail again, I will cross reference your work. It's always a treat analyzing your work on various abstractions building on pythonscad :)
Reply all
Reply to author
Forward
0 new messages