CadQuery / cadquery

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

Best way to create a T-slot profile #670

Closed skanga closed 1 year ago

skanga commented 3 years ago

Any suggestions on how to model this part in CadQuery?

Profile

adam-urbanczyk commented 3 years ago

Use the DXF import functionality and extrude?

jmwright commented 3 years ago

@skanga There's an example in a pending PR here: https://github.com/CadQuery/cadquery/pull/668/files#diff-cb972e06b41c528b92f42d1f94c6c205ff15c6e35613bfb32333d9c13f3496acR56

skanga commented 3 years ago

@adam-urbanczyk This works great. You're a rock star - wow! @jmwright Thanks for the reference. That was a great tip.

I found this repo of t-slot profiles - https://github.com/dcowden/dxf/tree/master/8020_shapes

Here is some sample working code using files from that repo

import cadquery as cq

def extrudeObject(dxfFile, extrudeLength):
    result = (
        cq.importers.importDXF(dxfFile)
        .wires()
        .toPending()
        .extrude(extrudeLength)
        )
    return result

#result = extrudeObject('dxf/8020_shapes/8020-10Series/2040.dxf', 20)
#result = extrudeObject('dxf/8020_shapes/8020-20Series/20-4040.dxf', 100)
#result = extrudeObject('dxf/8020_shapes/8020-20Series/20-4040.dxf', 100)
result = extrudeObject('dxf/8020_shapes/8020-40Series/40-4040.dxf', 100)
#result = extrudeObject('dxf/8020_shapes/8020-40Series/40-8080.dxf', 100)
#result = extrudeObject('dxf/8020_shapes/8020-45Series/45-9090.dxf', 100)
#result = extrudeObject('dxf/8020_shapes/QF_Series/9035.dxf', 10)
#result = extrudeObject('dxf/8020_shapes/RT_Series/9725.dxf', 10)
#result = extrudeObject('dxf/8020_shapes/RT_Series/9820.dxf', 10)

show_object(result)

Now - 1 more question. Is it possible to cut the ends of the extrusion at say 60 or 45 degree angles?

Say - I want to make a square frame with one of these profiles, using 4 of these sticks with 45 degree corners? Any nice tricks to do that?

adam-urbanczyk commented 3 years ago

You can use this kind of apporach:

res = (
    cq.Workplane()
    .rect(1,1)
    .extrude(5)
    .faces('>Z').edges('>X')
    .workplane(centerOption='CenterOfMass')
    .transformed((0,-45,0)).split(False,True)
    )

obraz

adam-urbanczyk commented 3 years ago

BTW: @skanga if you have something sharable you could open a PR for cadquery-contrib. I'm sure more CQ users will be interested in modeling examples with alu profiles.

skanga commented 3 years ago

OK - I'm happy to contribute. I'm thinking of this in terms of a DXF parts API which is backed up by dcowden's repo. If you want to use a part you can get it by using the Series and Part parameters. If the dxf isn't already available locally then we can fetch it from that repo.

The only complexity here is to create a consistent part naming scheme from the inconsistent conventions used in that repo. This seems to work for all the parts that I tried. It's a first attempt. Any thoughts?

import cadquery as cq
import os
import urllib.request
import urllib

# Store/cache downloaded files in this folder
localDir = "dxf/"

baseUrl = "https://raw.githubusercontent.com/dcowden/dxf/master/8020_shapes/"
#seriesUrl = "https://github.com/dcowden/dxf/tree/master/8020_shapes"
dxfPath = 'dxf/8020_shapes/{}Series/{}.dxf'

"""
Download dxf file from dcowden's github repo, while accounting for 
the naming inconsistencies as shown below:
8020-10Series/1001.dxf
8020-15Series/1501.dxf
8020-20Series/20-2020.dxf
8020-25Series/25-2501.dxf
8020-30Series/30-3030.dxf
8020-40Series/40-4001.dxf
8020-45Series/45-4545.dxf
QF_Series/9000.dxf
RT_Series/9700.dxf
"""

def downloadDxfFile(dxfFile):
    dxfSeries = dxfFile.split('-')[0]
    if (dxfFile[0].isdigit()):
        dxfFolder = "8020-" + dxfSeries + "Series/"
    else:
        dxfFolder = dxfSeries + "_Series/"
    dxfFile = dxfFile + ".dxf"
    # Reuse cached copy
    if (os.path.exists(localDir + dxfFile)):
        return
    if (dxfSeries.startswith ("1")) or (not dxfSeries[0].isdigit()):
        downloadUrl = baseUrl + dxfFolder + dxfFile.split('-', 1)[1]
    else:
        downloadUrl = baseUrl + dxfFolder + dxfFile
    # Download the file and save it locally
    urllib.request.urlretrieve(downloadUrl, localDir + dxfFile)
    return localDir + dxfFile

def extrudeObject(dxfFile, extrudeLength):
    downloadDxfFile(dxfFile)
    dxfSeries = dxfFile.split('-')[0]
    fullDxf = dxfPath.format (dxfSeries, dxfFile)
    result = (
        cq.importers.importDXF(fullDxf)
        .wires()
        .toPending()
        .extrude(extrudeLength)
        )
    return result

#result = extrudeObject('10-2040', 20)
#result = extrudeObject('15-3030', 20)
#result = extrudeObject('15-1530-ULS', 20)
#result = extrudeObject('20-4040', 100)
#result = extrudeObject('20-4040', 100)
result = extrudeObject('40-4040', 100)
#result = extrudeObject('40-4084-LITE', 100)
#result = extrudeObject('40-8080', 100)
#result = extrudeObject('45-9090', 100)
#result = extrudeObject('QF-9035', 10)
#result = extrudeObject('RT-9725', 10)
#result = extrudeObject('RT-9820', 10)

show_object(result)
skanga commented 3 years ago

Imagine the power of expanding something like this into an online repository of pre-built parts for CQ that anyone can contribute to. Not just for dxf but for anything!

With one simple API call we can import from a potentially large set of parts - just by knowing its name.

Also, having a "parts browser" panel in CQ-editor would be pretty cool - IMHO. There you could navigate to and view a part before you decide to use it.

dcowden commented 3 years ago

@skanga nice sleuthing to find those dxfs!

adam-urbanczyk commented 3 years ago

@skanga I get a FileNotFoundError - probably need to create some directory structure. For your own sanity you might want to consider using requests and path.py instead of os and urllib.

skanga commented 3 years ago

Strange! I don't think you need to create any dir structure. Indeed, the whole reason for doing the download was to avoid that! Anyway - let me review using requestsandpath.pyinstead of osand urllib. Being a python noob - I just tried whatever seemed to work on my machine (Windows 10 with Python 3.9)

skanga commented 3 years ago

I also created a "discovery" API to show what series are available and the parts in each series. Is the python Beautiful Soup library also one to avoid? This code works standalone but in CQ-editor it fails to import BeautifulSoupat all!


import urllib.request
import urllib
from bs4 import BeautifulSoup

baseUrl = "https://github.com/dcowden/dxf/tree/master/8020_shapes"

def fetchAllSeries():
    with urllib.request.urlopen(baseUrl) as response:
        html = response.read()
    soup = BeautifulSoup(html, 'html.parser')

    links = soup.find_all('a', class_="js-navigation-open Link--primary")
    results = []
    for link in links:
        #print(link.text.replace("Series", "").replace("8020-", "").replace("_", ""))
        results.append (link.text.replace("Series", "").replace("8020-", "").replace("_", ""))
    return results

def fetchSeriesParts(seriesName):
    with urllib.request.urlopen(baseUrl) as response:
        html = response.read()
    soup = BeautifulSoup(html, 'html.parser')

    links = soup.find_all('a', class_="js-navigation-open Link--primary")
    for link in links:
        if (seriesName in link.text.replace("Series", "").replace("8020-", "").replace("_", "")):
            break
    #print (link.text)
    with urllib.request.urlopen(baseUrl + "/" + link.text) as response:
        html = response.read()
    soup = BeautifulSoup(html, 'html.parser')
    links = soup.find_all('a', class_="js-navigation-open Link--primary")
    results = []
    for link in links:
        #print(link.text.replace(".dxf", "")
        if (seriesName.startswith ("1")) or (not seriesName[0].isdigit()):
            results.append (seriesName + "-" + link.text.replace(".dxf", ""))       
        else:
            results.append (link.text.replace(".dxf", ""))
    return results

def fetchAllParts():
    allSeries = fetchAllSeries()
    results = []
    for currSeries in allSeries:
        #print (currSeries, fetchSeriesParts(currSeries))
        results.append (fetchSeriesParts(currSeries))
    return results

print("All Parts:", fetchAllParts())
#print("All Series:", fetchAllSeries())
#print("20 Series:", fetchSeriesParts("20"))
#print("QF Series:", fetchSeriesParts("QF"))
Jojain commented 3 years ago

BeautifulSoup isn't from the standard library to avoid having to download and import external library you can maybe look at html.parser module from the standard library (never used it but should do the same work)