├── .coverage ├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS.md ├── Dockerfile ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── build-docs.sh ├── build_docker.sh ├── cadquery.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── not-zip-safe └── top_level.txt ├── cadquery ├── README.txt ├── __init__.py ├── contrib │ └── __init__.py ├── cq.py ├── cq_directive.py ├── cqgi.py ├── freecad_impl │ ├── README.txt │ ├── __init__.py │ ├── console_logging.py │ ├── exporters.py │ ├── geom.py │ ├── importers.py │ └── shapes.py ├── plugins │ └── __init__.py └── selectors.py ├── changes.md ├── conda-py3-freecad.yml ├── cq_cmd.py ├── cq_cmd.sh ├── doc ├── README ├── _static │ ├── KiCad_Capacitors_SMD.jpg │ ├── KiCad_Capacitors_SMD_thumb.jpg │ ├── ParametricPulley.PNG │ ├── block.png │ ├── box_export.step │ ├── cadquery_cheatsheet.html │ ├── cqlogo.png │ ├── extruder_support.png │ ├── hyOzd-cablefix.png │ ├── hyOzd-finished.jpg │ ├── hyOzd-finished_thumb.jpg │ ├── jupyter_showcase_thumb.png │ ├── logo │ │ ├── README.md │ │ ├── Roboto_Font │ │ │ ├── LICENSE.txt │ │ │ └── Roboto-Bold.ttf │ │ ├── cadquery_logo_dark.svg │ │ ├── cadquery_logo_dark_inkscape.svg │ │ ├── cadquery_logo_light.svg │ │ └── cadquery_logo_light_inkscape.svg │ ├── march30_landing_page.png │ ├── new_badge.png │ ├── parametric-cup-screencap.PNG │ ├── parametric-pillowblock-screencap.png │ ├── pillowblock.png │ ├── quickstart-1.png │ ├── quickstart-2.png │ ├── quickstart-3.png │ ├── quickstart-4.png │ ├── quickstart-5.png │ ├── quickstart.png │ ├── quickstart │ │ ├── 000.png │ │ ├── 001.png │ │ ├── 002.png │ │ ├── 003.png │ │ ├── 004.png │ │ └── 005.png │ └── simpleblock.png ├── apireference.rst ├── classreference.rst ├── conf.py ├── cqgi.rst ├── designprinciples.rst ├── examples.rst ├── extending.rst ├── fileformat.rst ├── index.rst ├── installation.rst ├── intro.rst ├── primer.rst ├── quickstart.rst ├── roadmap.rst └── selectors.rst ├── examples └── FreeCAD │ ├── Ex001_Simple_Block.py │ ├── Ex002_Block_With_Bored_Center_Hole.py │ ├── Ex003_Pillow_Block_With_Counterbored_Holes.py │ ├── Ex004_Extruded_Cylindrical_Plate.py │ ├── Ex005_Extruded_Lines_and_Arcs.py │ ├── Ex006_Moving_the_Current_Working_Point.py │ ├── Ex007_Using_Point_Lists.py │ ├── Ex008_Polygon_Creation.py │ ├── Ex009_Polylines.py │ ├── Ex010_Defining_an_Edge_with_a_Spline.py │ ├── Ex011_Mirroring_Symmetric_Geometry.py │ ├── Ex012_Creating_Workplanes_on_Faces.py │ ├── Ex013_Locating_a_Workplane_on_a_Vertex.py │ ├── Ex014_Offset_Workplanes.py │ ├── Ex015_Rotated_Workplanes.py │ ├── Ex016_Using_Construction_Geometry.py │ ├── Ex017_Shelling_to_Create_Thin_Features.py │ ├── Ex018_Making_Lofts.py │ ├── Ex019_Counter_Sunk_Holes.py │ ├── Ex020_Rounding_Corners_with_Fillets.py │ ├── Ex021_Splitting_an_Object.py │ ├── Ex022_Classic_OCC_Bottle.py │ ├── Ex023_Parametric_Enclosure.py │ ├── Ex024_Using_FreeCAD_Solids_as_CQ_Objects.py │ ├── Ex025_Revolution.py │ ├── Ex026_Lego_Brick.py │ ├── Ex027_Remote_Enclosure.py │ ├── Ex028_Numpy.py │ ├── Ex029_Braille.py │ ├── Ex030_Panel_with_Various_Holes_for_Connector_Installation.py │ ├── Ex031_Sweep.py │ ├── Ex032_3D_Printer_Extruder_Support.py │ ├── Ex033_Shelled_Cube_Inside_Chamfer_With_Logical_Selector_Operators.py │ ├── Ex034_Sweep_Along_List_Of_Wires.py │ └── Ex035_Reinforce_Junction_UsingFillet.py ├── registering.txt ├── requirements-dev.txt ├── requirements.txt ├── runtests.py ├── setup.cfg ├── setup.py └── tests ├── README.txt ├── TestCQGI.py ├── TestCQSelectors.py ├── TestCadObjects.py ├── TestCadQuery.py ├── TestExporters.py ├── TestImporters.py ├── TestLogging.py ├── TestWorkplanes.py └── __init__.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = cadquery/* 3 | omit = cadquery/cq_directive.py 4 | 5 | 6 | [xml] 7 | output = coverage.xml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pyc 3 | doc/_build/* 4 | dist/* 5 | .idea/* 6 | cadquery.egg-info 7 | target/* 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - python: 2.7 6 | 7 | before_install: 8 | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then 9 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 10 | bash miniconda.sh -b -p $HOME/miniconda; 11 | export PATH="$HOME/miniconda/bin:$PATH"; 12 | hash -r; 13 | conda config --set always_yes yes --set changeps1 no; 14 | conda update -q conda; 15 | conda info -a; 16 | conda create -y -q -n freecad_cq3 -c cadquery -c conda-forge freecad=0.17 python=3.6 pyparsing conda mock coverage codecov; 17 | source ~/miniconda/bin/activate freecad_cq3; 18 | else 19 | sudo apt-get update -qq; 20 | sudo apt-get install -y freecad; 21 | pip install -r requirements-dev.txt; 22 | pip install travis-sphinx; 23 | pip install sphinx_rtd_theme; 24 | pip install readme_renderer; 25 | fi 26 | 27 | install: 28 | - python setup.py install; 29 | 30 | script: 31 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 32 | python setup.py check -r -s; 33 | fi 34 | - coverage run runtests.py 35 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 36 | travis-sphinx build --nowarn --source=doc; 37 | fi 38 | 39 | after_success: 40 | - coverage xml 41 | - codecov -X gcov --file coverage.xml 42 | 43 | after_script: 44 | - coveralls 45 | - travis-sphinx deploy 46 | 47 | branches: 48 | except: 49 | - pythonocc 50 | - 2_0_branch 51 | 52 | deploy: 53 | provider: pypi 54 | user: dcowden 55 | password: 56 | secure: aP02wBbry1j3hYG/w++siF1lk26teuRQlPAx1c+ec8fxUw+bECa2HbPQHcIvSXB5N6nc6P3L9LjHt9ktm+Dn6FLJu3qWYNGAZx9PTn24ug0iAmB+JyNrsET3nK6WUKR1XpBqvjKgdpukd1Hknh2FSzYoyUvFWH9/CovITCFN3jo= 57 | on: 58 | tags: true 59 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Core CQ Developers 2 | 3 | * [Dave Cowden](https://github.com/dcowden), Creator 4 | * [Adam Urbanczyk](https://github.com/adam-urbanczyk) 5 | * [Jeremy Wright](https://github.com/jmwright) 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | MAINTAINER 4 | 5 | ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 6 | 7 | ENV CQ_HOME=/opt/cadquery 8 | 9 | #from continuum: https://hub.docker.com/r/continuumio/anaconda/~/dockerfile/ 10 | RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates \ 11 | libglib2.0-0 libxext6 libsm6 libxrender1 \ 12 | git mercurial subversion 13 | 14 | RUN echo 'export PATH=/opt/conda/bin:$PATH' > /etc/profile.d/conda.sh && \ 15 | wget --quiet https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ 16 | /bin/bash ~/miniconda.sh -b -p /opt/conda && \ 17 | rm ~/miniconda.sh 18 | 19 | RUN apt-get install -y curl grep sed dpkg && \ 20 | TINI_VERSION=`curl https://github.com/krallin/tini/releases/latest | grep -o "/v.*\"" | sed 's:^..\(.*\).$:\1:'` && \ 21 | curl -L "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini_${TINI_VERSION}.deb" > tini.deb && \ 22 | dpkg -i tini.deb && \ 23 | rm tini.deb && \ 24 | apt-get clean 25 | 26 | ENV PATH /opt/conda/bin:$PATH 27 | 28 | RUN apt-get install -y software-properties-common 29 | 30 | RUN add-apt-repository -y ppa:freecad-maintainers/freecad-stable && \ 31 | apt-get update && apt-get install -y freecad 32 | 33 | RUN mkdir -p $CQ_HOME 34 | RUN mkdir -p $CQ_HOME/build_data 35 | VOLUME $CQ_HOME/build_data 36 | 37 | COPY requirements-dev.txt runtests.py cq_cmd.py cq_cmd.sh setup.py README.rst MANIFEST setup.cfg $CQ_HOME/ 38 | COPY cadquery $CQ_HOME/cadquery 39 | COPY examples $CQ_HOME/examples 40 | COPY tests $CQ_HOME/tests 41 | 42 | 43 | RUN pip install -r /opt/cadquery/requirements-dev.txt 44 | RUN cd $CQ_HOME && python ./setup.py install 45 | RUN pip install cqparts 46 | RUN pip install cqparts-bearings 47 | RUN pip install cqparts-fasteners 48 | RUN pip install cqparts-misc 49 | RUN chmod +x $CQ_HOME/cq_cmd.sh 50 | RUN useradd -ms /bin/bash cq 51 | USER cq 52 | WORKDIR /home/cq 53 | 54 | ENTRYPOINT [ "/opt/cadquery/cq_cmd.sh" ] 55 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | README.rst 2 | setup.cfg 3 | setup.py 4 | cadquery\cq.py 5 | cadquery\__init__.py 6 | cadquery\cq_directive.py 7 | cadquery\selectors.py 8 | cadquery\cqgi.py 9 | cadquery\contrib\__init__.py 10 | cadquery\freecad_impl\__init__.py 11 | cadquery\freecad_impl\exporters.py 12 | cadquery\freecad_impl\importers.py 13 | cadquery\freecad_impl\geom.py 14 | cadquery\freecad_impl\shapes.py 15 | cadquery\plugins\__init__.py 16 | tests\TestCQSelectors.py 17 | tests\TestCadObjects.py 18 | tests\TestCadQuery.py 19 | tests\TestExporters.py 20 | tests\TestImporters.py 21 | tests\TestWorkplanes.py 22 | tests\TestCQGI.py 23 | tests\__init__.py 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | shallow_clone: true 2 | 3 | platform: 4 | - x64 5 | 6 | environment: 7 | matrix: 8 | - PYTHON_VERSION: 3.6 9 | MINICONDA_DIRNAME: C:\Miniconda36-x64 10 | 11 | install: 12 | - set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%" 13 | - conda config --set always_yes yes 14 | - conda update -q conda 15 | - conda create --quiet --name cqtest -c cadquery -c conda-forge python=%PYTHON_VERSION% freecad=0.17 python=3.6 pyparsing mock coverage codecov 16 | - activate cqtest 17 | - python setup.py install 18 | 19 | build: false 20 | 21 | test_script: 22 | - coverage run runtests.py 23 | 24 | on_success: 25 | - coverage xml 26 | - codecov -X gcov --file coverage.xml 27 | -------------------------------------------------------------------------------- /build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sphinx-build -b html doc target/docs -------------------------------------------------------------------------------- /build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | #builds and tests the docker image 5 | docker build -t dcowden/cadquery . 6 | 7 | # set up tests 8 | CQ_TEST_DIR=/tmp/cq_docker-test 9 | mkdir -p $CQ_TEST_DIR 10 | rm -rf $CQ_TEST_DIR/*.* 11 | cp examples/FreeCAD/Ex001_Simple_Block.py $CQ_TEST_DIR 12 | 13 | 14 | fail_test( ){ 15 | echo "Test Failed." 16 | } 17 | 18 | echo "Running Tests..." 19 | echo "No arguments prints documentation..." 20 | docker run dcowden/cadquery | grep "CadQuery Docker Image" || fail_test 21 | echo "OK" 22 | 23 | echo "Std in and stdout..." 24 | cat $CQ_TEST_DIR/Ex001_Simple_Block.py | docker run -i dcowden/cadquery build --in_spec stdin --out_spec stdout | grep "ISO-10303-21" || fail_test 25 | echo "OK" 26 | 27 | echo "Mount a directory and produce output..." 28 | docker run -i -v $CQ_TEST_DIR:/home/cq dcowden/cadquery build --in_spec Ex001_Simple_Block.py --format STEP 29 | ls $CQ_TEST_DIR | grep "cqobject-1.STEP" || fail_test 30 | echo "OK" 31 | 32 | echo "Future Server EntryPoint" 33 | docker run -i dcowden/cadquery runserver | grep "Future CadQuery Server" || fail_test 34 | echo "OK" 35 | -------------------------------------------------------------------------------- /cadquery.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | MANIFEST.in 3 | README.rst 4 | setup.cfg 5 | setup.py 6 | cadquery/__init__.py 7 | cadquery/cq.py 8 | cadquery/cq_directive.py 9 | cadquery/cqgi.py 10 | cadquery/selectors.py 11 | cadquery.egg-info/PKG-INFO 12 | cadquery.egg-info/SOURCES.txt 13 | cadquery.egg-info/dependency_links.txt 14 | cadquery.egg-info/not-zip-safe 15 | cadquery.egg-info/requires.txt 16 | cadquery.egg-info/top_level.txt 17 | cadquery/contrib/__init__.py 18 | cadquery/freecad_impl/__init__.py 19 | cadquery/freecad_impl/console_logging.py 20 | cadquery/freecad_impl/exporters.py 21 | cadquery/freecad_impl/geom.py 22 | cadquery/freecad_impl/importers.py 23 | cadquery/freecad_impl/shapes.py 24 | cadquery/plugins/__init__.py 25 | tests/TestCQGI.py 26 | tests/TestCQSelectors.py 27 | tests/TestCadObjects.py 28 | tests/TestCadQuery.py 29 | tests/TestExporters.py 30 | tests/TestImporters.py 31 | tests/TestLogging.py 32 | tests/TestWorkplanes.py 33 | tests/__init__.py -------------------------------------------------------------------------------- /cadquery.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cadquery.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cadquery.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | cadquery 2 | tests 3 | -------------------------------------------------------------------------------- /cadquery/README.txt: -------------------------------------------------------------------------------- 1 | *** 2 | Core CadQuery implementation. 3 | 4 | No files should depend on or import FreeCAD , pythonOCC, or other CAD Kernel libraries!!! 5 | Dependencies should be on the classes provided by implementation packages, which in turn 6 | can depend on CAD libraries. 7 | 8 | *** -------------------------------------------------------------------------------- /cadquery/__init__.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | print("WARNING: CadQuery 1.x is no longer supported. Please upgrade to CadQuery 2.x https://github.com/CadQuery/cadquery") 3 | warnings.warn("CadQuery 1.x is no longer supported. Please upgrade to CadQuery 2.x https://github.com/CadQuery/cadquery", DeprecationWarning) 4 | 5 | #these items point to the freecad implementation 6 | from .freecad_impl.geom import Plane,BoundBox,Vector,Matrix,sortWiresByBuildOrder 7 | from .freecad_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound 8 | from .freecad_impl import exporters 9 | from .freecad_impl import importers 10 | 11 | #these items are the common implementation 12 | 13 | #the order of these matter 14 | from .selectors import * 15 | from .cq import * 16 | 17 | 18 | __all__ = [ 19 | 'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder', 20 | 'Shape','Vertex','Edge','Wire','Face','Solid','Shell','Compound','exporters', 'importers', 21 | 'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector', 22 | 'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins', 23 | ] 24 | 25 | __version__ = "1.2.5" 26 | -------------------------------------------------------------------------------- /cadquery/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC 3 | 4 | This file is part of CadQuery. 5 | 6 | CadQuery is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | CadQuery is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; If not, see 18 | """ 19 | -------------------------------------------------------------------------------- /cadquery/cq_directive.py: -------------------------------------------------------------------------------- 1 | """ 2 | A special directive for including a cq object. 3 | 4 | """ 5 | 6 | import traceback 7 | from cadquery import * 8 | from cadquery import cqgi 9 | import io 10 | from docutils.parsers.rst import directives 11 | 12 | template = """ 13 | 14 | .. raw:: html 15 | 16 |
17 | %(out_svg)s 18 |
19 |
20 |
21 | 22 | """ 23 | template_content_indent = ' ' 24 | 25 | 26 | def cq_directive(name, arguments, options, content, lineno, 27 | content_offset, block_text, state, state_machine): 28 | # only consider inline snippets 29 | plot_code = '\n'.join(content) 30 | 31 | # Since we don't have a filename, use a hash based on the content 32 | # the script must define a variable called 'out', which is expected to 33 | # be a CQ object 34 | out_svg = "Your Script Did not assign call build_output() function!" 35 | 36 | try: 37 | _s = io.StringIO() 38 | result = cqgi.parse(plot_code).build() 39 | 40 | if result.success: 41 | exporters.exportShape(result.first_result.shape, "SVG", _s) 42 | out_svg = _s.getvalue() 43 | else: 44 | raise result.exception 45 | 46 | except Exception: 47 | traceback.print_exc() 48 | out_svg = traceback.format_exc() 49 | 50 | # now out 51 | # Now start generating the lines of output 52 | lines = [] 53 | 54 | # get rid of new lines 55 | out_svg = out_svg.replace('\n', '') 56 | 57 | txt_align = "left" 58 | if "align" in options: 59 | txt_align = options['align'] 60 | 61 | lines.extend((template % locals()).split('\n')) 62 | 63 | lines.extend(['::', '']) 64 | lines.extend([' %s' % row.rstrip() 65 | for row in plot_code.split('\n')]) 66 | lines.append('') 67 | 68 | if len(lines): 69 | state_machine.insert_input( 70 | lines, state_machine.input_lines.source(0)) 71 | 72 | return [] 73 | 74 | 75 | def setup(app): 76 | setup.app = app 77 | setup.config = app.config 78 | setup.confdir = app.confdir 79 | 80 | options = {'height': directives.length_or_unitless, 81 | 'width': directives.length_or_percentage_or_unitless, 82 | 'align': directives.unchanged 83 | } 84 | 85 | app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options) 86 | -------------------------------------------------------------------------------- /cadquery/freecad_impl/README.txt: -------------------------------------------------------------------------------- 1 | It is ok for files in this directory to import FreeCAD, FreeCAD.Base, and FreeCAD.Part. 2 | 3 | Other modules should _not_ depend on FreeCAD -------------------------------------------------------------------------------- /cadquery/freecad_impl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC 3 | 4 | This file is part of CadQuery. 5 | 6 | CadQuery is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | CadQuery is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; If not, see 18 | """ 19 | import os 20 | import sys 21 | import glob 22 | 23 | 24 | #FreeCAD has crudified the stdout stream with a bunch of STEP output 25 | #garbage 26 | #this cleans it up 27 | #so it is possible to use stdout for object output 28 | class suppress_stdout_stderr(object): 29 | ''' 30 | A context manager for doing a "deep suppression" of stdout and stderr in 31 | Python, i.e. will suppress all print, even if the print originates in a 32 | compiled C/Fortran sub-function. 33 | This will not suppress raised exceptions, since exceptions are printed 34 | to stderr just before a script exits, and after the context manager has 35 | exited (at least, I think that is why it lets exceptions through). 36 | 37 | ''' 38 | def __init__(self): 39 | # Open a pair of null files 40 | self.null_fds = [os.open(os.devnull,os.O_RDWR) for x in range(2)] 41 | # Save the actual stdout (1) and stderr (2) file descriptors. 42 | self.save_fds = [os.dup(1), os.dup(2)] 43 | 44 | def __enter__(self): 45 | # Assign the null pointers to stdout and stderr. 46 | os.dup2(self.null_fds[0],1) 47 | os.dup2(self.null_fds[1],2) 48 | 49 | def __exit__(self, *_): 50 | # Re-assign the real stdout/stderr back to (1) and (2) 51 | os.dup2(self.save_fds[0],1) 52 | os.dup2(self.save_fds[1],2) 53 | # Close all file descriptors 54 | for fd in self.null_fds + self.save_fds: 55 | os.close(fd) 56 | 57 | def _fc_path(): 58 | """Find FreeCAD""" 59 | # Look for FREECAD_LIB env variable 60 | _PATH = os.environ.get('FREECAD_LIB', '') 61 | if _PATH and os.path.exists(_PATH): 62 | return _PATH 63 | 64 | # Try to guess if using Anaconda 65 | if 'env' in sys.prefix: 66 | if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): 67 | _PATH = os.path.join(sys.prefix,'lib') 68 | # return PATH if FreeCAD.[so,pyd] is present 69 | if len(glob.glob(os.path.join(_PATH,'FreeCAD.so'))) > 0: 70 | return _PATH 71 | elif sys.platform.startswith('win'): 72 | _PATH = os.path.join(sys.prefix,'Library','bin') 73 | # return PATH if FreeCAD.[so,pyd] is present 74 | if len(glob.glob(os.path.join(_PATH,'FreeCAD.pyd'))) > 0: 75 | return _PATH 76 | 77 | if sys.platform.startswith('linux'): 78 | # Make some dangerous assumptions... 79 | for _PATH in [ 80 | os.path.join(os.path.expanduser("~"), "lib/freecad/lib"), 81 | "/usr/local/lib/freecad/lib", 82 | "/usr/lib/freecad/lib", 83 | "/opt/freecad/lib/", 84 | "/usr/bin/freecad/lib", 85 | "/usr/lib/freecad-daily/lib", 86 | "/usr/lib/freecad", 87 | "/usr/lib64/freecad/lib", 88 | ]: 89 | if os.path.exists(_PATH): 90 | return _PATH 91 | 92 | elif sys.platform.startswith('win'): 93 | # Try all the usual suspects 94 | for _PATH in [ 95 | "c:/Program Files/FreeCAD0.12/bin", 96 | "c:/Program Files/FreeCAD0.13/bin", 97 | "c:/Program Files/FreeCAD0.14/bin", 98 | "c:/Program Files/FreeCAD0.15/bin", 99 | "c:/Program Files/FreeCAD0.16/bin", 100 | "c:/Program Files/FreeCAD0.17/bin", 101 | "c:/Program Files (x86)/FreeCAD0.12/bin", 102 | "c:/Program Files (x86)/FreeCAD0.13/bin", 103 | "c:/Program Files (x86)/FreeCAD0.14/bin", 104 | "c:/Program Files (x86)/FreeCAD0.15/bin", 105 | "c:/Program Files (x86)/FreeCAD0.16/bin", 106 | "c:/Program Files (x86)/FreeCAD0.17/bin", 107 | "c:/apps/FreeCAD0.12/bin", 108 | "c:/apps/FreeCAD0.13/bin", 109 | "c:/apps/FreeCAD0.14/bin", 110 | "c:/apps/FreeCAD0.15/bin", 111 | "c:/apps/FreeCAD0.16/bin", 112 | "c:/apps/FreeCAD0.17/bin", 113 | "c:/Program Files/FreeCAD 0.12/bin", 114 | "c:/Program Files/FreeCAD 0.13/bin", 115 | "c:/Program Files/FreeCAD 0.14/bin", 116 | "c:/Program Files/FreeCAD 0.15/bin", 117 | "c:/Program Files/FreeCAD 0.16/bin", 118 | "c:/Program Files/FreeCAD 0.17/bin", 119 | "c:/Program Files (x86)/FreeCAD 0.12/bin", 120 | "c:/Program Files (x86)/FreeCAD 0.13/bin", 121 | "c:/Program Files (x86)/FreeCAD 0.14/bin", 122 | "c:/Program Files (x86)/FreeCAD 0.15/bin", 123 | "c:/Program Files (x86)/FreeCAD 0.16/bin", 124 | "c:/Program Files (x86)/FreeCAD 0.17/bin", 125 | "c:/apps/FreeCAD 0.12/bin", 126 | "c:/apps/FreeCAD 0.13/bin", 127 | "c:/apps/FreeCAD 0.14/bin", 128 | "c:/apps/FreeCAD 0.15/bin", 129 | "c:/apps/FreeCAD 0.16/bin", 130 | "c:/apps/FreeCAD 0.17/bin", 131 | ]: 132 | if os.path.exists(_PATH): 133 | return _PATH 134 | 135 | elif sys.platform.startswith('darwin'): 136 | # Assume we're dealing with a Mac 137 | for _PATH in [ 138 | "/Applications/FreeCAD.app/Contents/lib", 139 | "/Applications/FreeCAD.app/Contents/Resources/lib", 140 | os.path.join(os.path.expanduser("~"), 141 | "Library/Application Support/FreeCAD/lib"), 142 | ]: 143 | if os.path.exists(_PATH): 144 | return _PATH 145 | 146 | raise ImportError('cadquery was unable to determine freecad library path') 147 | 148 | 149 | # Make sure that the correct FreeCAD path shows up in Python's system path 150 | with suppress_stdout_stderr(): 151 | try: 152 | import FreeCAD 153 | except ImportError: 154 | path = _fc_path() 155 | sys.path.insert(0, path) 156 | import FreeCAD 157 | 158 | # logging 159 | from . import console_logging 160 | -------------------------------------------------------------------------------- /cadquery/freecad_impl/console_logging.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | 4 | import FreeCAD 5 | 6 | # Logging Handler 7 | # Retain a reference to the logging handler so it may be removed on requeset. 8 | # Also to prevent 2 handlers being added 9 | _logging_handler = None 10 | 11 | # FreeCAD Logging Handler 12 | class FreeCADConsoleHandler(logging.Handler): 13 | """logging.Handler class to output to FreeCAD's console""" 14 | 15 | def __init__(self, *args, **kwargs): 16 | super(FreeCADConsoleHandler, self).__init__(*args, **kwargs) 17 | 18 | # Test for expected print functions 19 | # (just check they exist, if they don't an exception will be raised) 20 | FreeCAD.Console.PrintMessage 21 | FreeCAD.Console.PrintWarning 22 | FreeCAD.Console.PrintError 23 | 24 | def emit(self, record): 25 | log_text = self.format(record) + "\n" 26 | if record.levelno >= logging.ERROR: 27 | FreeCAD.Console.PrintError(log_text) 28 | elif record.levelno >= logging.WARNING: 29 | FreeCAD.Console.PrintWarning(log_text) 30 | else: 31 | FreeCAD.Console.PrintMessage(log_text) 32 | 33 | 34 | def enable(level=None, format="%(message)s"): 35 | """ 36 | Enable python builtin logging, and output it somewhere you can see. 37 | - FreeCAD Console, or 38 | - STDOUT (if output to console fails, for whatever reason) 39 | 40 | Any script can log to FreeCAD console with: 41 | 42 | >>> import cadquery 43 | >>> cadquery.freecad_impl.console_logging.enable() 44 | >>> import logging 45 | >>> log = logging.getLogger(__name__) 46 | >>> log.debug("detailed info, not normally displayed") 47 | >>> log.info("some information") 48 | some information 49 | >>> log.warning("some warning text") # orange text 50 | some warning text 51 | >>> log.error("an error message") # red text 52 | an error message 53 | 54 | logging only needs to be enabled once, somewhere in your codebase. 55 | debug logging level can be set with: 56 | 57 | >>> import cadquery 58 | >>> import logging 59 | >>> cadquery.freecad_impl.console_logging.enable(logging.DEBUG) 60 | >>> log = logging.getLogger(__name__) 61 | >>> log.debug("debug logs will now be displayed") 62 | debug logs will now be displayed 63 | 64 | :param level: logging level to display, one of logging.(DEBUG|INFO|WARNING|ERROR) 65 | :param format: logging format to display (search for "python logging format" for details) 66 | :return: the logging Handler instance in effect 67 | """ 68 | global _logging_handler 69 | 70 | # Set overall logging level (done even if handler has already been assigned) 71 | root_logger = logging.getLogger() 72 | if level is not None: 73 | root_logger.setLevel(level) 74 | elif _logging_handler is None: 75 | # level is not specified, and ho handler has been added yet. 76 | # assumption: user is enabling logging for the first time with no parameters. 77 | # let's make it simple for them and default the level to logging.INFO 78 | # (logging default level is logging.WARNING) 79 | root_logger.setLevel(logging.INFO) 80 | 81 | if _logging_handler is None: 82 | # Determine which Handler class to use 83 | try: 84 | _logging_handler = FreeCADConsoleHandler() 85 | except Exception as e: 86 | raise 87 | # Fall back to STDOUT output (better than nothing) 88 | _logging_handler = logging.StreamHandler(sys.stdout) 89 | 90 | # Configure and assign handler to root logger 91 | _logging_handler.setLevel(logging.DEBUG) 92 | root_logger.addHandler(_logging_handler) 93 | 94 | # Set formatting (can be used to re-define logging format) 95 | formatter = logging.Formatter(format) 96 | _logging_handler.setFormatter(formatter) 97 | 98 | return _logging_handler 99 | 100 | 101 | def disable(): 102 | """ 103 | Disables logging to FreeCAD console (or STDOUT). 104 | Note, logging may be enabled by another imported module, so this isn't a 105 | guarantee; this function undoes logging_enable(), nothing more. 106 | """ 107 | global _logging_handler 108 | if _logging_handler: 109 | root_logger = logging.getLogger() 110 | root_logger.handlers.remove(_logging_handler) 111 | _logging_handler = None 112 | -------------------------------------------------------------------------------- /cadquery/freecad_impl/importers.py: -------------------------------------------------------------------------------- 1 | 2 | import cadquery 3 | from .shapes import Shape 4 | 5 | import FreeCAD 6 | import Part 7 | import sys 8 | import os 9 | import urllib as urlreader 10 | import tempfile 11 | 12 | class ImportTypes: 13 | STEP = "STEP" 14 | 15 | class UNITS: 16 | MM = "mm" 17 | IN = "in" 18 | 19 | 20 | def importShape(importType, fileName): 21 | """ 22 | Imports a file based on the type (STEP, STL, etc) 23 | :param importType: The type of file that we're importing 24 | :param fileName: THe name of the file that we're importing 25 | """ 26 | 27 | #Check to see what type of file we're working with 28 | if importType == ImportTypes.STEP: 29 | return importStep(fileName) 30 | 31 | 32 | #Loads a STEP file into a CQ.Workplane object 33 | def importStep(fileName): 34 | """ 35 | Accepts a file name and loads the STEP file into a cadquery shape 36 | :param fileName: The path and name of the STEP file to be imported 37 | """ 38 | #Now read and return the shape 39 | try: 40 | rshape = Part.read(fileName) 41 | 42 | # Extract all solids and surfaces 43 | geometry = [] 44 | for solid in rshape.Solids: 45 | geometry.append(Shape.cast(solid)) 46 | 47 | for shell in rshape.Shells: 48 | geometry.append(Shape.cast(shell)) 49 | 50 | return cadquery.Workplane("XY").newObject(geometry) 51 | 52 | except: 53 | raise ValueError("STEP File Could not be loaded") 54 | 55 | #Loads a STEP file from an URL into a CQ.Workplane object 56 | def importStepFromURL(url): 57 | #Now read and return the shape 58 | try: 59 | # Account for differences in urllib between Python 2 and 3 60 | if hasattr(urlreader, "urlopen"): 61 | webFile = urlreader.urlopen(url) 62 | else: 63 | import urllib.request 64 | webFile = urllib.request.urlopen(url) 65 | 66 | tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False) 67 | tempFile.write(webFile.read()) 68 | webFile.close() 69 | tempFile.close() 70 | 71 | # Read saved file and return CQ Workplane object 72 | return importStep(tempFile.name) 73 | except: 74 | raise ValueError("STEP File from the URL: " + url + " Could not be loaded") 75 | -------------------------------------------------------------------------------- /cadquery/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CadQuery 3 | Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | """ 19 | -------------------------------------------------------------------------------- /changes.md: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | v1.2.5 5 | ------ 6 | * This is a deprecation release to clean things up so that the repository can be archived 7 | 8 | 9 | v1.2.0 10 | ------ 11 | * Multiple Anaconda CI fixes by @adam-urbanczyk 12 | * @adam-urbanczyk added AppVeyor with Windows test integration to the CI pipeline 13 | * @adam-urbanczyk added codecov in Travis CI configuration 14 | * @jpmlt updated sweep operation to work with a list of wires #249 15 | * @gntech added ability to draw an arc from the current point to endPoint with an arc defined by the sag (sagitta) #253 16 | * @gntech added ability to draw an arc from the current point to endPoint with an arc defined by the radius #254 17 | * @gntech updated the close function to be smarter when the start point and end points are the same #257 18 | * @gntech removed some waste from the CI test environment by avoiding the installation of the FreeCAD docs #256 19 | 20 | 21 | v1.1.0 22 | ------ 23 | * Fixes and addition of graphical examples for selectors (thanks @adam-urbanczyk) #181 24 | * Added intersect operation (thanks @fragmuffin) #189 25 | * CQGI updates (changed `build_object` function name to `show_object`) #194 26 | * Added `lefthand` and `heightstyle` options to makeHelix function (thanks @fragmuffin) #197 27 | * Test suite cleanup (thanks @fragmuffin) #198 28 | * Added formal logging mechanism (thanks @fragmuffin) #200 29 | * Merged examples into this library (they were spread out before) #201 30 | * Fixed vector equality error (thanks @fragmuffin) #202 31 | * Expanded CQGI to handle float variables in addition to int #204 32 | * Dockerfile added by @dcowden for Docker build and command line interface #205 33 | * Initial Python 3 support for the FreeCAD backend was added by @adam-urbanczyk #207 34 | * Fixed Line versus LineSegment incompatibility between FreeCAD 0.16 and 0.17 #209 35 | * @RustyVermeer fixed a long-standing bug with the extrusion of invalid faces #210 36 | * @adam-urbanczyk fixed a Python 2 versus Python 3 (unicode) issue with SVG export #215 37 | * Python 3 support was completed by the community with @adam-urbanczyk leading the way 38 | * SVG export improvements including orientation control from @RustyVermeer #232 39 | * Improved test coverage 40 | * @galou fixed braille example #229 41 | 42 | 43 | v1.0.0 44 | ------ 45 | * Added an option to do symmetric extrusion about the workplane (thanks @adam-urbanczyk) 46 | * Extended selector syntax to include Nth selector and re-implemented selectors using pyparsing (thanks @adam-urbanczyk) 47 | * Added logical operations to string selectors (thanks @adam-urbanczyk) 48 | * Cleanup of README.md and changes.md (thanks @baoboa) 49 | * Fixed bugs with toVector and Face 'Not Defined' errors (thanks @huskier) 50 | * Refactor of the initialization code for PEP8 compliance and Python 3 compatibility (thanks @Peque) 51 | * Making sure that the new pyparsing library dependency is handled properly (thanks @Peque) 52 | 53 | v0.5.2 54 | ------ 55 | * Added the sweep operation #33 56 | 57 | v0.5.1 58 | ------ 59 | * Mirroring fixes (thanks @huskier) 60 | * Added a mirroring example (thanks @huskier) 61 | 62 | v0.5.0-stable 63 | ------ 64 | * Configuring Travis to push to PyPI on version releases. 65 | 66 | v0.4.1 67 | ------ 68 | * Minor CQGI updates 69 | 70 | v0.4.0 71 | ------ 72 | * Added Documentation, which is available on dcowden.github.io/cadquery 73 | * Added CQGI, an adapter API that standardizes use of cadquery from within structured execution environments 74 | * Added ability to import STEP files from a web URL (thanks @huskier ) #128 75 | 76 | v0.3.0 77 | ----- 78 | * Fixed a bug where clean() could not be called on appropriate objects other than solids (thanks @hyOzd) #108 79 | * Implemented new selectors that allow existing selectors to be combined with arithmetic/boolean operations (thanks @hyOzd) #110 80 | * Fixed a bug where only 1 random edge was returned with multiple min/max selector matches (thanks @hyOzd) #111 81 | * Implemented the creation of a workplane from multiple co-planar faces (thanks @hyOzd) #113 82 | * Fixed the operation of Center() when called on a compound with multiple solids 83 | * Add the named planes ZX YX ZY to define different normals (thanks @galou) #115 84 | * Code cleanup in accordance with PEP 8 (thanks @galou) 85 | * Fixed a bug with the close function not resetting the first point of the context correctly (thanks @huskier) 86 | * Fixed the findSolid function so that it handles compounds #107 87 | * Changed the polyline function so that it adds edges to the stack instead of a wire #102 88 | * Add the ability to find the center of the bounding box, rather than the center of mass (thanks @huskier) #122 89 | * Changed normalize function to normalized to match OCC/PythonOCC nomenclature #124 90 | * Added a label attribute to all freecad_impl.shapes so that they can have IDs attached to them #124 91 | 92 | v0.2.0 93 | ----- 94 | * Fixed versioning to match the semantic versioning scheme 95 | * Added license badge in changes.md 96 | * Fixed Solid.makeSphere implementation 97 | * Added CQ.sphere operation that mirrors CQ.box 98 | * Updated copyright dates 99 | * Cleaned up spelling and misc errors in docstrings 100 | * Fixed FreeCAD import error on Arch Linux (thanks @moeb) 101 | * Made FreeCAD import report import error instead of silently failing (thanks @moeb) 102 | * Added ruled option for the loft operation (thanks @hyOzd) 103 | * Fixed close() not working in planes other than XY (thanks @hyOzd) 104 | * Added box selector with bounding box option (thanks @hyOzd) 105 | * CQ.translate and CQ.rotate documentation fixes (thanks @hyOzd) 106 | * Fixed centering of a sphere 107 | * Increased test coverage 108 | * Added a clean function to keep some operations from failing on solids that need simplified (thanks @hyOzd) 109 | * Added a mention of the new Google Group to the readme 110 | 111 | v0.1.8 112 | ----- 113 | * Added toFreecad() function as a convenience for val().wrapped 114 | * Converted all examples to use toFreecad() 115 | * Updated all version numbers that were missed before 116 | * Fixed import issues in Windows caused by fc_import 117 | * Added/fixed Mac OS support 118 | * Improved STEP import 119 | * Fixed bug in rotateAboutCenter that negated its effect on solids 120 | * Added Travis config (thanks @krasin) 121 | * Removed redundant workplane.py file left over from the PParts.com migration 122 | * Fixed toWorldCoordinates bug in moveTo (thanks @xix-xeaon) 123 | * Added new tests for 2D drawing functions 124 | * Integrated Coveralls.io, with a badge in README.md 125 | * Integrated version badge in README.md 126 | 127 | v0.1.7 128 | ----- 129 | * Added revolve operation and supporting tests 130 | * Fixed minor documentation errors 131 | 132 | v0.1.6 133 | ----- 134 | * Added STEP import and supporting tests 135 | 136 | v0.1 137 | ----- 138 | * Initial Version 139 | -------------------------------------------------------------------------------- /conda-py3-freecad.yml: -------------------------------------------------------------------------------- 1 | name: cq-freecad 2 | channels: !!python/tuple 3 | - !!python/unicode 4 | 'defaults' 5 | dependencies: 6 | - certifi=2016.2.28=py36_0 7 | - conda-forge::boost=1.64.0=py36_4 8 | - conda-forge::boost-cpp=1.64.0=1 9 | - conda-forge::bzip2=1.0.6=1 10 | - conda-forge::curl=7.54.1=0 11 | - conda-forge::cycler=0.10.0=py36_0 12 | - conda-forge::fontconfig=2.12.1=4 13 | - conda-forge::freeimage=3.17.0=0 14 | - conda-forge::freetype=2.7=1 15 | - conda-forge::future=0.16.0=py36_0 16 | - conda-forge::git=2.14.1=1 17 | - conda-forge::hdf5=1.8.18=0 18 | - conda-forge::icu=58.1=1 19 | - conda-forge::jsoncpp=0.10.6=1 20 | - conda-forge::krb5=1.14.2=0 21 | - conda-forge::libssh2=1.8.0=1 22 | - conda-forge::libtiff=4.0.6=7 23 | - conda-forge::libxslt=1.1.29=5 24 | - conda-forge::matplotlib=1.5.3=np113py36_8 25 | - conda-forge::occt=7.1.0=occt7.1.0_1 26 | - conda-forge::pyparsing=2.2.0=py36_0 27 | - conda-forge::pyqt=4.11.4=py36_2 28 | - conda-forge::pyside=1.2.4=py36_8 29 | - conda-forge::python-dateutil=2.6.1=py36_0 30 | - conda-forge::pytz=2017.2=py36_0 31 | - conda-forge::sip=4.18=py36_1 32 | - conda-forge::six=1.11.0=py36_1 33 | - conda-forge::tbb=2018_20170919=0 34 | - conda-forge::tornado=4.5.2=py36_0 35 | - conda-forge::vtk=7.1.1=py36_202 36 | - conda-forge::xerces-c=3.1.4=3 37 | - dbus=1.10.20=0 38 | - expat=2.1.0=0 39 | - freecad::coin3d=4.0.0=5 40 | - freecad::freecad=0.17=py36_occt7.1.0_5 41 | - freecad::libmed=3.1.0=2 42 | - freecad::netgen=6.2=py36_occt7.1.0_3 43 | - freecad::pivy=0.6.2=py36_dev_4 44 | - freecad::simage=1.7.0=0 45 | - freecad::soqt=1.5.0=0 46 | - glib=2.50.2=1 47 | - gst-plugins-base=1.8.0=0 48 | - gstreamer=1.8.0=0 49 | - jpeg=9b=0 50 | - libffi=3.2.1=1 51 | - libgcc=5.2.0=0 52 | - libgfortran=3.0.0=1 53 | - libiconv=1.14=0 54 | - libpng=1.6.30=1 55 | - libxcb=1.12=1 56 | - libxml2=2.9.4=0 57 | - mkl=2017.0.3=0 58 | - mock=2.0.0=py36_0 59 | - numpy=1.13.1=py36_0 60 | - openssl=1.0.2l=0 61 | - pbr=1.10.0=py36_0 62 | - pcre=8.39=1 63 | - pip=9.0.1=py36_1 64 | - python=3.6.2=0 65 | - qt=4.8.7=3 66 | - readline=6.2=2 67 | - setuptools=36.4.0=py36_1 68 | - sqlite=3.13.0=0 69 | - tk=8.5.18=0 70 | - wheel=0.29.0=py36_0 71 | - xz=5.2.3=0 72 | - zlib=1.2.11=0 73 | -------------------------------------------------------------------------------- /cq_cmd.py: -------------------------------------------------------------------------------- 1 | # 2 | # cadquery command line interface 3 | # usage: cq_cmd [-h] [--param-file inputfile ] [--format STEP|STL ] [--output filename] filename 4 | # if input file contains multiple inputs, multiple outputs are created 5 | # if no input filename is provided, stdin is read instead 6 | # if no output filename is provided, output goes to stdout 7 | # default output format is STEP 8 | # 9 | from __future__ import print_function 10 | import sys,os 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | from cadquery import cqgi,exporters 19 | import argparse 20 | import os.path 21 | import json 22 | import tempfile 23 | 24 | 25 | class FilepathShapeWriter(object): 26 | #a shape writer that writes a new file in a directory for each object 27 | def __init__(self,file_pattern, shape_format): 28 | self.shape_format=shape_format 29 | self.file_pattern=file_pattern 30 | self.counter = 1 31 | 32 | def _compute_file_name(self): 33 | return self.file_pattern % ({ "counter": self.counter,"format": self.shape_format } ) 34 | 35 | def write_shapes(self,shape_list): 36 | for result in shape_list: 37 | shape = result.shape 38 | file_name = self._compute_file_name() 39 | info("Writing %s Output to '%s'" % (self.shape_format, file_name)) 40 | s = open(file_name,'w') 41 | exporters.exportShape(shape,self.shape_format,s) 42 | s.flush() 43 | s.close() 44 | 45 | class StdoutShapeWriter(object): 46 | #has extra code to prevent freecad crap from junking up stdout 47 | def __init__(self,shape_format ): 48 | self.shape_format = shape_format 49 | 50 | def write_shapes(self,shape_list): 51 | #f = open('/tmp/cqtemp','w') 52 | #with suppress_stdout_stderr(): 53 | exporters.exportShape(shape_list[0].shape,self.shape_format,sys.stdout) 54 | #f.flush() 55 | #f.close() 56 | #f = open('/tmp/cqtemp') 57 | #sys.stdout.write(f.read()) 58 | 59 | 60 | def create_shape_writer(out_spec,shape_format): 61 | if out_spec == 'stdout': 62 | return StdoutShapeWriter(shape_format) 63 | else: 64 | return FilepathShapeWriter(out_spec,shape_format) 65 | 66 | class ErrorCodes(object): 67 | SCRIPT_ERROR=2 68 | UNKNOWN_ERROR=3 69 | INVALID_OUTPUT_DESTINATION=4 70 | INVALID_OUTPUT_FORMAT=5 71 | INVALID_INPUT=6 72 | MULTIPLE_RESULTS=7 73 | 74 | def eprint(*args, **kwargs): 75 | print(*args, file=sys.stderr, **kwargs) 76 | 77 | def error(msg,exit_code=1): 78 | eprint("ERROR %d: %s" %( exit_code, msg)) 79 | sys.exit(exit_code) 80 | 81 | def warning(msg): 82 | eprint("WARNING: %s" % msg) 83 | 84 | def info(msg): 85 | eprint("INFO: %s" % msg) 86 | 87 | def read_file_as_string(file_name): 88 | if os.path.isfile(file_name): 89 | f = open(file_name) 90 | s = f.read() 91 | f.close() 92 | return s 93 | else: 94 | return None 95 | 96 | class ParameterHandler(object): 97 | def __init__(self): 98 | self.params = {} 99 | 100 | def apply_file(self,file_spec): 101 | if file_spec is not None: 102 | p = read_file_as_string(file_spec) 103 | if p is not None: 104 | d = json.loads(p) 105 | self.params.update(d) 106 | 107 | def apply_string(self,param_string): 108 | if param_string is not None: 109 | r = json.loads(param_string) 110 | self.params.update(r) 111 | 112 | def get(self): 113 | return self.params 114 | 115 | def read_input_script(file_name): 116 | if file_name != "stdin": 117 | s = read_file_as_string(file_name) 118 | if s is None: 119 | error("%s does not appear to be a readable file." % file_name,ErrorCodes.INVALID_INPUT) 120 | else: 121 | return s 122 | else: 123 | s = sys.stdin.read() 124 | return s 125 | 126 | def check_input_format(input_format): 127 | valid_types = [exporters.ExportTypes.TJS,exporters.ExportTypes.AMF, 128 | exporters.ExportTypes.STEP,exporters.ExportTypes.STL,exporters.ExportTypes.TJS ] 129 | if input_format not in valid_types: 130 | error("Invalid Input format '%s'. Valid values: %s" % ( input_format, str(valid_types)) , 131 | ErrorCodes.INVALID_OUTPUT_FORMAT) 132 | 133 | 134 | def describe_parameters(user_params, script_params): 135 | if len(script_params)> 0: 136 | parameter_names = ",".join(list(script_params.keys())) 137 | info("This script provides parameters %s, which can be customized at build time." % parameter_names) 138 | else: 139 | info("This script provides no customizable build parameters.") 140 | if len(user_params) > 0: 141 | info("User Supplied Parameter Values ( Override Model Defaults):") 142 | for k,v in user_params.items(): 143 | info("\tParameter: %s=%s" % (k,v)) 144 | else: 145 | info("The script will run with default variable values") 146 | info("use --param_file to provide a json file that contains values to override the defaults") 147 | 148 | def run(args): 149 | 150 | info("Reading from file '%s'" % args.in_spec) 151 | input_script = read_input_script(args.in_spec) 152 | script_name = 'stdin' if args.in_spec is None else args.in_spec 153 | cq_model = cqgi.parse(input_script) 154 | info("Parsed Script '%s'." % script_name) 155 | 156 | param_handler = ParameterHandler() 157 | param_handler.apply_file(args.param_file) 158 | param_handler.apply_string(args.params) 159 | user_params = param_handler.get() 160 | describe_parameters(user_params,cq_model.metadata.parameters) 161 | 162 | check_input_format(args.format) 163 | 164 | build_result = cq_model.build(build_parameters=user_params) 165 | 166 | info("Output Format is '%s'. Use --output-format to change it." % args.format) 167 | info("Output Path is '%s'. Use --out_spec to change it." % args.out_spec) 168 | 169 | if build_result.success: 170 | result_list = build_result.results 171 | info("Script Generated %d result Objects" % len(result_list)) 172 | shape_writer = create_shape_writer(args.out_spec,args.format) 173 | shape_writer.write_shapes(result_list) 174 | else: 175 | error("Script Error: '%s'" % build_result.exception,ErrorCodes.SCRIPT_ERROR) 176 | 177 | if __name__=='__main__': 178 | 179 | desc=""" 180 | CQ CMD. Runs a cadquery python file, and produces a 3d object. 181 | A script can be provided as a file or as standard input. 182 | Each object created by the script is written the supplied output directory. 183 | """ 184 | filename_pattern_help=""" 185 | Filename pattern to use when creating output files. 186 | The sequential file number and the format are available. 187 | Default: cqobject-%%(counter)d.%%(format)s 188 | Use stdout to write to stdout ( can't be used for multiple results though) 189 | """ 190 | parser = argparse.ArgumentParser(description=desc) 191 | parser.add_argument("--format", action="store",default="STEP",help="Output Object format (TJS|STEP|STL|SVG)") 192 | parser.add_argument("--param_file", action="store",help="Parameter Values File, in JSON format") 193 | parser.add_argument("--params",action="store", help="JSON encoded parameter values. They override values provided in param_file") 194 | parser.add_argument("--in_spec", action="store", required=True, help="Input File path. Use stdin to read standard in") 195 | parser.add_argument("--out_spec", action="store",default="./cqobject-%(counter)d.%(format)s",help=filename_pattern_help) 196 | args = parser.parse_args() 197 | run(args) 198 | -------------------------------------------------------------------------------- /cq_cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # NOTE 3 | # the -u (unbuffered flag) in the below is very important 4 | # without it, the FreeCAD libraries somehow manage to get some stdout 5 | # junking up output when stdout is used. 6 | # this is the script we use 7 | # to select between running a build server 8 | # and a command line job runner 9 | if [ -z "$1" ]; then 10 | echo "************************" 11 | echo "CadQuery Docker Image" 12 | echo "************************" 13 | echo "Usage: docker run cadquery build [options]" 14 | echo "Examples:" 15 | echo " Read a model from stdin, write output to stdout" 16 | echo "" 17 | echo " cat cadquery_script.py | sudo docker run -i dcowden/cadquery:latest --in_spec stdin --out_spec stdout > my_object.STEP" 18 | echo " " 19 | echo " Mount a directory, and write results into the local directory" 20 | echo "" 21 | echo " sudo docker run -i dcowden/cadquery:latest --in_spec my_script.py" 22 | echo "" 23 | exec python -u /opt/cadquery/cq_cmd.py -h 24 | exit 1 25 | fi; 26 | if [ "$1" == "build" ]; then 27 | exec python -u /opt/cadquery/cq_cmd.py "${@:2}" 28 | fi; 29 | if [ "$1" == "runserver" ]; then 30 | echo "Future CadQuery Server" 31 | exit 1 32 | #exec python -u /opt/cadquery/cq_server.py "${@:2}" 33 | fi; 34 | -------------------------------------------------------------------------------- /doc/README: -------------------------------------------------------------------------------- 1 | This documentation should be generated with sphinxdoc. 2 | see ../build-docs.sh 3 | -------------------------------------------------------------------------------- /doc/_static/KiCad_Capacitors_SMD.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/KiCad_Capacitors_SMD.jpg -------------------------------------------------------------------------------- /doc/_static/KiCad_Capacitors_SMD_thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/KiCad_Capacitors_SMD_thumb.jpg -------------------------------------------------------------------------------- /doc/_static/ParametricPulley.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/ParametricPulley.PNG -------------------------------------------------------------------------------- /doc/_static/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/block.png -------------------------------------------------------------------------------- /doc/_static/cqlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/cqlogo.png -------------------------------------------------------------------------------- /doc/_static/extruder_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/extruder_support.png -------------------------------------------------------------------------------- /doc/_static/hyOzd-cablefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/hyOzd-cablefix.png -------------------------------------------------------------------------------- /doc/_static/hyOzd-finished.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/hyOzd-finished.jpg -------------------------------------------------------------------------------- /doc/_static/hyOzd-finished_thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/hyOzd-finished_thumb.jpg -------------------------------------------------------------------------------- /doc/_static/jupyter_showcase_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/jupyter_showcase_thumb.png -------------------------------------------------------------------------------- /doc/_static/logo/README.md: -------------------------------------------------------------------------------- 1 | ## CadQuery Logo 2 | 3 | This logo uses the Roboto bold font (included), the Inkscape font size is 56, and the color is #2980b9 4 | -------------------------------------------------------------------------------- /doc/_static/logo/Roboto_Font/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/logo/Roboto_Font/Roboto-Bold.ttf -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 68 | 72 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_dark_inkscape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | cq 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 68 | 72 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/logo/cadquery_logo_light_inkscape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | cq 77 | 78 | 79 | -------------------------------------------------------------------------------- /doc/_static/march30_landing_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/march30_landing_page.png -------------------------------------------------------------------------------- /doc/_static/new_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/new_badge.png -------------------------------------------------------------------------------- /doc/_static/parametric-cup-screencap.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/parametric-cup-screencap.PNG -------------------------------------------------------------------------------- /doc/_static/parametric-pillowblock-screencap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/parametric-pillowblock-screencap.png -------------------------------------------------------------------------------- /doc/_static/pillowblock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/pillowblock.png -------------------------------------------------------------------------------- /doc/_static/quickstart-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart-1.png -------------------------------------------------------------------------------- /doc/_static/quickstart-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart-2.png -------------------------------------------------------------------------------- /doc/_static/quickstart-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart-3.png -------------------------------------------------------------------------------- /doc/_static/quickstart-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart-4.png -------------------------------------------------------------------------------- /doc/_static/quickstart-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart-5.png -------------------------------------------------------------------------------- /doc/_static/quickstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart.png -------------------------------------------------------------------------------- /doc/_static/quickstart/000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/000.png -------------------------------------------------------------------------------- /doc/_static/quickstart/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/001.png -------------------------------------------------------------------------------- /doc/_static/quickstart/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/002.png -------------------------------------------------------------------------------- /doc/_static/quickstart/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/003.png -------------------------------------------------------------------------------- /doc/_static/quickstart/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/004.png -------------------------------------------------------------------------------- /doc/_static/quickstart/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/quickstart/005.png -------------------------------------------------------------------------------- /doc/_static/simpleblock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcowden/cadquery/90b609dcc14676334211dfec9389660af8d116d0/doc/_static/simpleblock.png -------------------------------------------------------------------------------- /doc/apireference.rst: -------------------------------------------------------------------------------- 1 | .. _apireference: 2 | 3 | *********************** 4 | CadQuery API Reference 5 | *********************** 6 | 7 | The CadQuery API is made up of 3 main objects: 8 | 9 | * **CQ** - An object that wraps a topological entity. 10 | * **Workplane** -- A subclass of CQ, that applies in a 2-D modelling context. 11 | * **Selector** -- Filter and select things 12 | 13 | This page lists methods of these objects grouped by **functional area** 14 | 15 | .. seealso:: 16 | This page lists api methods grouped by functional area. 17 | Use :ref:`classreference` to see methods alphabetically by class. 18 | 19 | 20 | Initialization 21 | ---------------- 22 | 23 | .. currentmodule:: cadquery 24 | 25 | Creating new workplanes and object chains 26 | 27 | .. autosummary:: 28 | CQ 29 | Workplane 30 | 31 | 32 | .. _2dOperations: 33 | 34 | 2-d Operations 35 | ----------------- 36 | 37 | Creating 2-d constructs that can be used to create 3 d features. 38 | 39 | All 2-d operations require a **Workplane** object to be created. 40 | 41 | .. currentmodule:: cadquery 42 | 43 | .. autosummary:: 44 | Workplane.center 45 | Workplane.lineTo 46 | Workplane.line 47 | Workplane.vLine 48 | Workplane.vLineTo 49 | Workplane.hLine 50 | Workplane.moveTo 51 | Workplane.move 52 | Workplane.spline 53 | Workplane.threePointArc 54 | Workplane.rotateAndCopy 55 | Workplane.mirrorY 56 | Workplane.mirrorX 57 | Workplane.wire 58 | Workplane.rect 59 | Workplane.circle 60 | Workplane.polyline 61 | Workplane.close 62 | Workplane.rarray 63 | 64 | .. _3doperations: 65 | 66 | 3-d Operations 67 | ----------------- 68 | 69 | Some 3-d operations also require an active 2-d workplane, but some do not. 70 | 71 | 3-d operations that require a 2-d workplane to be active: 72 | 73 | .. autosummary:: 74 | Workplane.cboreHole 75 | Workplane.cskHole 76 | Workplane.hole 77 | Workplane.extrude 78 | Workplane.cut 79 | Workplane.cutBlind 80 | Workplane.cutThruAll 81 | Workplane.box 82 | Workplane.union 83 | Workplane.combine 84 | 85 | 3-d operations that do NOT require a 2-d workplane to be active: 86 | 87 | .. autosummary:: 88 | CQ.shell 89 | CQ.fillet 90 | CQ.split 91 | CQ.rotate 92 | CQ.rotateAboutCenter 93 | CQ.translate 94 | 95 | File Management and Export 96 | --------------------------------- 97 | 98 | .. autosummary:: 99 | CQ.toSvg 100 | CQ.exportSvg 101 | 102 | 103 | .. autosummary:: 104 | importers.importStep 105 | exporters.exportShape 106 | 107 | 108 | Iteration Methods 109 | ------------------ 110 | 111 | Methods that allow iteration over the stack or objects 112 | 113 | .. autosummary:: 114 | Workplane.each 115 | Workplane.eachpoint 116 | 117 | 118 | .. _stackMethods: 119 | 120 | Stack and Selector Methods 121 | ------------------------------ 122 | 123 | CadQuery methods that operate on the stack 124 | 125 | .. autosummary:: 126 | CQ.all 127 | CQ.size 128 | CQ.vals 129 | CQ.add 130 | CQ.val 131 | CQ.first 132 | CQ.item 133 | CQ.last 134 | CQ.end 135 | CQ.vertices 136 | CQ.faces 137 | CQ.edges 138 | CQ.wires 139 | CQ.solids 140 | CQ.shells 141 | CQ.compounds 142 | 143 | .. _selectors: 144 | 145 | Selectors 146 | ------------------------ 147 | 148 | Objects that filter and select CAD objects. Selectors are used to select existing geometry 149 | as a basis for further operations. 150 | 151 | .. currentmodule:: cadquery 152 | 153 | .. autosummary:: 154 | 155 | NearestToPointSelector 156 | BoxSelector 157 | BaseDirSelector 158 | ParallelDirSelector 159 | DirectionSelector 160 | DirectionNthSelector 161 | PerpendicularDirSelector 162 | TypeSelector 163 | DirectionMinMaxSelector 164 | BinarySelector 165 | AndSelector 166 | SumSelector 167 | SubtractSelector 168 | InverseSelector 169 | StringSyntaxSelector 170 | -------------------------------------------------------------------------------- /doc/classreference.rst: -------------------------------------------------------------------------------- 1 | .. _classreference: 2 | 3 | ************************* 4 | CadQuery Class Summary 5 | ************************* 6 | 7 | This page documents all of the methods and functions of the CadQuery classes, organized alphabatically. 8 | 9 | .. seealso:: 10 | 11 | For a listing organized by functional area, see the :ref:`apireference` 12 | 13 | .. currentmodule:: cadquery 14 | 15 | Core Classes 16 | --------------------- 17 | 18 | .. autosummary:: 19 | CQ 20 | Workplane 21 | 22 | Topological Classes 23 | ---------------------- 24 | 25 | .. autosummary:: 26 | Shape 27 | Vertex 28 | Edge 29 | Wire 30 | Face 31 | Shell 32 | Solid 33 | Compound 34 | 35 | Geometry Classes 36 | ------------------ 37 | 38 | .. autosummary:: 39 | Vector 40 | Matrix 41 | Plane 42 | 43 | Selector Classes 44 | --------------------- 45 | 46 | 47 | .. autosummary:: 48 | 49 | Selector 50 | NearestToPointSelector 51 | BoxSelector 52 | BaseDirSelector 53 | ParallelDirSelector 54 | DirectionSelector 55 | DirectionNthSelector 56 | PerpendicularDirSelector 57 | TypeSelector 58 | DirectionMinMaxSelector 59 | BinarySelector 60 | AndSelector 61 | SumSelector 62 | SubtractSelector 63 | InverseSelector 64 | StringSyntaxSelector 65 | 66 | 67 | Class Details 68 | --------------- 69 | 70 | .. automodule:: cadquery 71 | :members: 72 | -------------------------------------------------------------------------------- /doc/cqgi.rst: -------------------------------------------------------------------------------- 1 | .. _cqgi: 2 | 3 | The CadQuery Gateway Interface 4 | ==================================== 5 | 6 | CadQuery is first and foremost designed as a library, which can be used as a part of any project. 7 | In this context, there is no need for a standard script format or gateway api. 8 | 9 | Though the embedded use case is the most common, several tools have been created which run 10 | cadquery scripts on behalf of the user, and then render the result of the script visually. 11 | 12 | These execution environments (EE) generally accept a script and user input values for 13 | script parameters, and then display the resulting objects visually to the user. 14 | 15 | Today, three execution environments exist: 16 | 17 | * `The CadQuery Freecad Module `_, which runs scripts 18 | inside of the FreeCAD IDE, and displays objects in the display window 19 | * the cq-directive, which is used to execute scripts inside of sphinx-doc, 20 | producing documented examples that include both a script and an SVG representation of the object that results 21 | * `Jupyter Notebooks `_, which provide a web-based way to build, view, and export scripts. 22 | 23 | The CQGI is distributed with cadquery, and standardizes the interface between execution environments and cadquery scripts. 24 | 25 | The Script Side 26 | ----------------- 27 | 28 | CQGI compliant containers provide an execution environment for scripts. The environment includes: 29 | 30 | * the cadquery library is automatically imported as 'cq'. 31 | * the :py:meth:`cadquery.cqgi.ScriptCallback.show_object()` method is defined that should be used to export a shape to the execution environment 32 | * the :py:meth:`cadquery.cqgi.ScriptCallBack.debug()` method is defined, which can be used by scripts to debug model output during execution. 33 | 34 | Scripts must call build_output at least once. Invoking show_object more than once will send multiple objects to 35 | the container. An error will occur if the script does not return an object using the show_object() method. 36 | 37 | An optional options dictionary can be provided to the show_object method. If provided, it is passed onto the executing environment, and is used to render the object. Typically, this will be colors, transparency, and other visual affects. 38 | 39 | 40 | This CQGI compliant script produces a cube with a circle on top, and displays a workplane as well as an intermediate circle as debug output:: 41 | 42 | base_cube = cq.Workplane('XY').rect(1.0,1.0).extrude(1.0) 43 | top_of_cube_plane = base_cube.faces(">Z").workplane() 44 | debug(top_of_cube_plane, { 'color': 'yellow', } ) 45 | debug(top_of_cube_plane.center, { 'color' : 'blue' } ) 46 | 47 | circle=top_of_cube_plane.circle(0.5) 48 | debug(circle, { 'color': 'red' } ) 49 | 50 | show_object( circle.extrude(1.0),{"color": "#aaaaaa" ) 51 | 52 | Note that importing cadquery is not required. 53 | At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle 54 | 55 | Future enhancements will include several other methods, used to provide more metadata for the execution environment: 56 | * :py:meth:`cadquery.cqgi.ScriptCallback.add_error()`, indicates an error with an input parameter 57 | * :py:meth:`cadquery.cqgi.ScriptCallback.describe_parameter()`, provides extra information about a parameter in the script, 58 | 59 | 60 | The execution environment side 61 | ------------------------------- 62 | 63 | CQGI makes it easy to run cadquery scripts in a standard way. To run a script from an execution environment, 64 | run code like this:: 65 | 66 | from cadquery import cqgi 67 | 68 | user_script = ... 69 | build_result = cqgi.parse(user_script).build() 70 | 71 | The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object. 72 | 73 | The `metadata`p property of the object contains a `cadquery.cqgi.ScriptMetaData` object, which can be used to discover the 74 | user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the 75 | model parameters. Typically, after collecting new values, the environment will supply them in the build() method. 76 | 77 | This code will return a dictionary of parameter values in the model text SCRIPT:: 78 | 79 | parameters = cqgi.parse(SCRIPT).metadata.parameters 80 | 81 | The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object, 82 | which has a name, type, and default value. 83 | 84 | The type is an object which extends ParameterType-- you can use this to determine what kind of widget to render ( checkbox for boolean, for example ). 85 | 86 | The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the 87 | describe_parameter() method. 88 | 89 | 90 | 91 | Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object, 92 | ,which includes the script execution time, and a success flag. 93 | 94 | If the script was successful, the results property will include a list of results returned by the script, 95 | as well as any debug the script produced 96 | 97 | If the script failed, the exception property contains the exception object. 98 | 99 | If you have a way to get inputs from a user, you can override any of the constants defined in the user script 100 | with new values:: 101 | 102 | from cadquery import cqgi 103 | 104 | user_script = ... 105 | build_result = cqgi.parse(user_script).build(build_parameters={ 'param': 2 }, build_options={} ) 106 | 107 | If a parameter called 'param' is defined in the model, it will be assigned the value 2 before the script runs. 108 | An error will occur if a value is provided that is not defined in the model, or if the value provided cannot 109 | be assigned to a variable with the given name. 110 | 111 | build_options is used to set server-side settings like timeouts, tessellation tolerances, and other details about 112 | how the model should be built. 113 | 114 | 115 | More about script variables 116 | ----------------------------- 117 | 118 | CQGI uses the following rules to find input variables for a script: 119 | 120 | * only top-level statements are considered 121 | * only assignments of constant values to a local name are considered. 122 | 123 | For example, in the following script:: 124 | 125 | h = 1.0 126 | w = 2.0 127 | foo = 'bar' 128 | 129 | def some_function(): 130 | x = 1 131 | 132 | h, w, and foo will be overridable script variables, but x is not. 133 | 134 | You can list the variables defined in the model by using the return value of the parse method:: 135 | 136 | model = cqgi.parse(user_script) 137 | 138 | //a dictionary of InputParameter objects 139 | parameters = model.metadata.parameters 140 | 141 | The key of the dictionary is a string , and the value is a :py:class:`cadquery.cqgi.InputParameter` object 142 | See the CQGI API docs for more details. 143 | 144 | Future enhancements will include a safer sandbox to prevent malicious scripts. 145 | 146 | Important CQGI Methods 147 | ------------------------- 148 | 149 | These are the most important Methods and classes of the CQGI 150 | 151 | .. currentmodule:: cadquery.cqgi 152 | 153 | .. autosummary:: 154 | parse 155 | CQModel.build 156 | BuildResult 157 | ScriptCallback.show_object 158 | 159 | Complete CQGI api 160 | ----------------- 161 | 162 | .. automodule:: cadquery.cqgi 163 | :members: 164 | 165 | -------------------------------------------------------------------------------- /doc/designprinciples.rst: -------------------------------------------------------------------------------- 1 | .. _designprinciples: 2 | 3 | 4 | =========================== 5 | CadQuery Design Principles 6 | =========================== 7 | 8 | 9 | Principle 1: Intuitive Construction 10 | ==================================== 11 | 12 | CadQuery aims to make building models using python scripting easy and intuitive. 13 | CadQuery strives to allow scripts to read roughly as a human would describe an object verbally. 14 | 15 | For example, consider this object: 16 | 17 | .. image:: _static/quickstart.png 18 | 19 | A human would describe this as: 20 | 21 | "A block 80mm square x 30mm thick , with countersunk holes for M2 socket head cap screws 22 | at the corners, and a circular pocket 22mm in diameter in the middle for a bearing" 23 | 24 | The goal is to have the CadQuery script that produces this object be as close as possible to the english phrase 25 | a human would use. 26 | 27 | 28 | Principle 2: Capture Design Intent 29 | ==================================== 30 | 31 | The features that are **not** part of the part description above are just as important as those that are. For example, most 32 | humans will assume that: 33 | 34 | * The countersunk holes are spaced a uniform distance from the edges 35 | * The circular pocket is in the center of the block, no matter how big the block is 36 | 37 | If you have experience with 3D CAD systems, you also know that there is a key design intent built into this object. 38 | After the base block is created, how the hole is located is key. If it is located from one edge, changing the block 39 | size will have a different affect than if the hole is located from the center. 40 | 41 | Many scripting languages do not provide a way to capture design intent-- because they require that you always work in 42 | global coordinates. CadQuery is different-- you can locate features relative to others in a relative way-- preserving 43 | the design intent just like a human would when creating a drawing or building an object. 44 | 45 | In fact, though many people know how to use 3D CAD systems, few understand how important the way that an object is built 46 | impact its maintainability and resiliency to design changes. 47 | 48 | 49 | Principle 3: Plugins as first class citizens 50 | ============================================ 51 | 52 | Any system for building 3D models will evolve to contain an immense number of libraries and feature builders. It is 53 | important that these can be seamlessly included into the core and used alongside the built in libraries. Plugins 54 | should be easy to install and familiar to use. 55 | 56 | 57 | Principle 4: CAD models as source code makes sense 58 | ================================================================== 59 | 60 | It is surprising that the world of 3D CAD is primarily dominated by systems that create opaque binary files. 61 | Just like the world of software, CAD models are very complex. 62 | 63 | CAD models have many things in common with software, and would benefit greatly from the use of tools that are standard 64 | in the software industry, such as: 65 | 66 | 1. Easily re-using features between objects 67 | 2. Storing objects using version control systems 68 | 3. Computing the differences between objects by using source control tools 69 | 4. Share objects on the internet 70 | 5. Automate testing and generation by allowing objects to be built from within libraries 71 | 72 | CadQuery is designed to make 3D content creation easy enough that the above benefits can be attained without more work 73 | than using existing 'opaque', 'point and click' solutions. 74 | 75 | -------------------------------------------------------------------------------- /doc/extending.rst: -------------------------------------------------------------------------------- 1 | .. _extending: 2 | 3 | Extending CadQuery 4 | ====================== 5 | 6 | 7 | If you find that CadQuery doesn't suit your needs, you can easily extend it. CadQuery provides several extension 8 | methods: 9 | 10 | * You can load plugins others have developed. This is by far the easiest way to access other code 11 | * you can define your own plugins. 12 | * you can use FreeCAD script directly 13 | 14 | 15 | Using FreeCAD Script 16 | ----------------------- 17 | 18 | The easiest way to extend CadQuery is to simply use FreeCAD script inside of your build method. Just about 19 | any valid FreeCAD script will execute just fine. For example, this simple CadQuery script:: 20 | 21 | return cq.Workplane("XY").box(1.0,2.0,3.0).val() 22 | 23 | is actually equivalent to:: 24 | 25 | return Part.makeBox(1.0,2.0,3.0) 26 | 27 | As long as you return a valid FreeCAD Shape, you can use any FreeCAD methods you like. You can even mix and match the 28 | two. For example, consider this script, which creates a FreeCAD box, but then uses CadQuery to select its faces:: 29 | 30 | box = Part.makeBox(1.0,2.0,3.0) 31 | cq = CQ(box).faces(">Z").size() # returns 6 32 | 33 | 34 | Extending CadQuery: Plugins 35 | ---------------------------- 36 | 37 | Though you can get a lot done with FreeCAD, the code gets pretty nasty in a hurry. CadQuery shields you from 38 | a lot of the complexity of the FreeCAD api. 39 | 40 | You can get the best of both worlds by wrapping your freecad script into a CadQuery plugin. 41 | 42 | A CadQuery plugin is simply a function that is attached to the CadQuery :py:meth:`cadquery.CQ` or :py:meth:`cadquery.Workplane` class. 43 | When connected, your plugin can be used in the chain just like the built-in functions. 44 | 45 | There are a few key concepts important to understand when building a plugin 46 | 47 | 48 | The Stack 49 | ------------------- 50 | 51 | Every CadQuery object has a local stack, which contains a list of items. The items on the stack will be 52 | one of these types: 53 | 54 | * **A CadQuery SolidReference object**, which holds a reference to a FreeCAD solid 55 | * **A FreeCAD object**, a Vertex, Edge, Wire, Face, Shell, Solid, or Compound 56 | 57 | The stack is available by using self.objects, and will always contain at least one object. 58 | 59 | .. note:: 60 | 61 | Objects and points on the stack are **always** in global coordinates. Similarly, any objects you 62 | create must be created in terms of global coordinates as well! 63 | 64 | 65 | Preserving the Chain 66 | ----------------------- 67 | 68 | CadQuery's fluent api relies on the ability to chain calls together one after another. For this to work, 69 | you must return a valid CadQuery object as a return value. If you choose not to return a CadQuery object, 70 | then your plugin will end the chain. Sometimes this is desired for example :py:meth:`cadquery.CQ.size` 71 | 72 | There are two ways you can safely continue the chain: 73 | 74 | 1. **return self** If you simply wish to modify the stack contents, you can simply return a reference to 75 | self. This approach is destructive, because the contents of the stack are modified, but it is also the 76 | simplest. 77 | 2. :py:meth:`cadquery.CQ.newObject` Most of the time, you will want to return a new object. Using newObject will 78 | return a new CQ or Workplane object having the stack you specify, and will link this object to the 79 | previous one. This preserves the original object and its stack. 80 | 81 | 82 | Helper Methods 83 | ----------------------- 84 | 85 | When you implement a CadQuery plugin, you are extending CadQuery's base objects. As a result, you can call any 86 | CadQuery or Workplane methods from inside of your extension. You can also call a number of internal methods that 87 | are designed to aid in plugin creation: 88 | 89 | 90 | * :py:meth:`cadquery.Workplane._makeWireAtPoints` will invoke a factory function you supply for all points on the stack, 91 | and return a properly constructed cadquery object. This function takes care of registering wires for you 92 | and everything like that 93 | 94 | * :py:meth:`cadquery.Workplane.newObject` returns a new Workplane object with the provided stack, and with its parent set 95 | to the current object. The preferred way to continue the chain 96 | 97 | * :py:meth:`cadquery.CQ.findSolid` returns the first Solid found in the chain, working from the current object upwards 98 | in the chain. commonly used when your plugin will modify an existing solid, or needs to create objects and 99 | then combine them onto the 'main' part that is in progress 100 | 101 | * :py:meth:`cadquery.Workplane._addPendingWire` must be called if you add a wire. This allows the base class to track all the wires 102 | that are created, so that they can be managed when extrusion occurs. 103 | 104 | * :py:meth:`cadquery.Workplane.wire` gathers up all of the edges that have been drawn ( eg, by line, vline, etc ), and 105 | attempts to combine them into a single wire, which is returned. This should be used when your plugin creates 106 | 2-d edges, and you know it is time to collect them into a single wire. 107 | 108 | * :py:meth:`cadquery.Workplane.plane` provides a reference to the workplane, which allows you to convert between workplane 109 | coordinates and global coordinates: 110 | * :py:meth:`cadquery.freecad_impl.geom.Plane.toWorldCoords` will convert local coordinates to global ones 111 | * :py:meth:`cadquery.freecad_impl.geom.Plane.toLocalCoords` will convert from global coordinates to local coordinates 112 | 113 | Coordinate Systems 114 | ----------------------- 115 | 116 | Keep in mind that the user may be using a work plane that has created a local coordinate system. Consequently, 117 | the orientation of shapes that you create are often implicitly defined by the user's workplane. 118 | 119 | Any objects that you create must be fully defined in *global coordinates*, even though some or all of the users' 120 | inputs may be defined in terms of local coordinates. 121 | 122 | 123 | Linking in your plugin 124 | ----------------------- 125 | 126 | Your plugin is a single method, which is attached to the main Workplane or CadQuery object. 127 | 128 | Your plugin method's first parameter should be 'self', which will provide a reference to base class functionality. 129 | You can also accept other arguments. 130 | 131 | To install it, simply attach it to the CadQuery or Workplane object, like this:: 132 | 133 | def _yourFunction(self,arg1,arg): 134 | do stuff 135 | return whatever_you_want 136 | 137 | cq.Workplane.yourPlugin = _yourFunction 138 | 139 | That's it! 140 | 141 | CadQueryExample Plugins 142 | ----------------------- 143 | Some core CadQuery code is intentionally written exactly like a plugin. 144 | If you are writing your own plugins, have a look at these methods for inspiration: 145 | 146 | * :py:meth:`cadquery.Workplane.polygon` 147 | * :py:meth:`cadquery.Workplane.cboreHole` 148 | 149 | 150 | Plugin Example 151 | ----------------------- 152 | 153 | This ultra simple plugin makes cubes of the specified size for each stack point. 154 | 155 | (The cubes are off-center because the boxes have their lower left corner at the reference points.) 156 | 157 | .. cq_plot:: 158 | 159 | def makeCubes(self,length): 160 | #self refers to the CQ or Workplane object 161 | 162 | #inner method that creates a cube 163 | def _singleCube(pnt): 164 | #pnt is a location in local coordinates 165 | #since we're using eachpoint with useLocalCoordinates=True 166 | return cq.Solid.makeBox(length,length,length,pnt) 167 | 168 | #use CQ utility method to iterate over the stack, call our 169 | #method, and convert to/from local coordinates. 170 | return self.eachpoint(_singleCube,True) 171 | 172 | #link the plugin into cadQuery 173 | cq.Workplane.makeCubes = makeCubes 174 | 175 | #use the plugin 176 | result = cq.Workplane("XY").box(6.0,8.0,0.5).faces(">Z")\ 177 | .rect(4.0,4.0,forConstruction=True).vertices() \ 178 | .makeCubes(1.0).combineSolids() 179 | show_object(result) 180 | -------------------------------------------------------------------------------- /doc/fileformat.rst: -------------------------------------------------------------------------------- 1 | .. _cadquery_reference: 2 | 3 | CadQuery Scripts and Object Output 4 | ====================================== 5 | 6 | CadQuery scripts are pure python scripts, that may follow a few conventions. 7 | 8 | If you are using cadquery as a library, there are no constraints. 9 | 10 | If you are using cadquery scripts inside of a cadquery execution environment, 11 | like `The CadQuery Freecad Module `_ or 12 | `Jupyter notebooks `_, there are a few conventions you need to be aware of: 13 | 14 | * cadquery is imported as 'cq' 15 | * to return an object to the container, you need to call the show_object() method. 16 | 17 | Each script generally has three sections: 18 | 19 | * Variable Assignments and metadata definitions 20 | * cadquery and other python code 21 | * object exports, via the export_object() function 22 | 23 | 24 | see the :ref:`cqgi` section for more details. -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | CadQuery Documentation 4 | =================================== 5 | 6 | CadQuery is an intuitive, easy-to-use python library for building parametric 3D CAD models. It has several goals: 7 | 8 | * Build models with scripts that are as close as possible to how you'd describe the object to a human, 9 | using a standard, already established programming language 10 | 11 | * Create parametric models that can be very easily customized by end users 12 | 13 | * Output high quality CAD formats like STEP and AMF in addition to traditional STL 14 | 15 | * Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser 16 | 17 | See CadQuery in Action 18 | ------------------------- 19 | 20 | This `Getting Started Video `_ will show you what CadQuery can do. 21 | 22 | 23 | Quick Links 24 | ------------------ 25 | 26 | * :ref:`quickstart` 27 | * `CadQuery CheatSheet <_static/cadquery_cheatsheet.html>`_ 28 | * :ref:`apireference` 29 | 30 | Table Of Contents 31 | ------------------- 32 | 33 | .. toctree:: 34 | :maxdepth: 2 35 | 36 | intro.rst 37 | installation.rst 38 | quickstart.rst 39 | designprinciples.rst 40 | primer.rst 41 | fileformat.rst 42 | examples.rst 43 | apireference.rst 44 | selectors.rst 45 | classreference.rst 46 | cqgi.rst 47 | extending.rst 48 | roadmap.rst 49 | 50 | 51 | 52 | Indices and tables 53 | ------------------- 54 | 55 | * :ref:`genindex` 56 | * :ref:`modindex` 57 | * :ref:`search` 58 | 59 | -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installing CadQuery 4 | =================================== 5 | 6 | CadQuery is based on `FreeCAD `_, 7 | which is turn based on the open-source `OpenCascade `_ modelling kernel. 8 | 9 | Prerequisites--FreeCAD and Python 2.6 or 2.7 10 | ---------------------------------------------- 11 | CadQuery requires FreeCAD and Python version 2.6.x or 2.7.x *Python 3.x is NOT supported* 12 | 13 | Installation 14 | ------------------- 15 | 16 | Ubuntu 17 | ^^^^^^^^^^^^^^ 18 | 19 | On Ubuntu, you can type:: 20 | 21 | sudo apt-get install -y freecad freecad-doc 22 | pip install cadquery 23 | 24 | This `Unix Installation Video `_ will walk you through the installation 25 | 26 | 27 | Windows & Mac 28 | ^^^^^^^^^^^^^^^^^^^^ 29 | 30 | 1. Install FreeCAD using the appropriate installer for your platform, on `www.freecadweb.org `_ 31 | 2. pip install cadquery 32 | 33 | This `Windows Installation video `_ will walk you through the installation on Windows 34 | 35 | 36 | Anaconda (cross-platform) 37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | Alternatively you can use Anaconda's package management system to install 40 | cadquery and its dependencies, including FreeCAD. 41 | 42 | 1. Install `Anaconda `_ 43 | 2. Open an anaconda prompt and run: 44 | 45 | :: 46 | 47 | conda install -c conda-forge cadquery 48 | 49 | 50 | Test Your Installation 51 | ------------------------ 52 | 53 | If all has gone well, you can open a command line/prompt, and type:: 54 | 55 | $ python 56 | >>> import cadquery 57 | >>> cadquery.Workplane('XY').box(1,2,3).toSvg() 58 | 59 | Adding a Nicer GUI via the cadquery-freecad-module 60 | -------------------------------------------------------- 61 | 62 | If you prefer to have a GUI available, your best option is to use 63 | `The CadQuery Freecad Module `_. 64 | 65 | Simply extract cadquery-freecad-module into your FreeCAD installation. You'll end up 66 | with a cadquery workbench that allows you to interactively run scripts, and then see the results in the FreeCAD GUI 67 | 68 | Zero Step Install 69 | ------------------------------------------------- 70 | 71 | If you would like to use cadquery with no installation all, you can 72 | use mybinder to `launch a Jupyter Notebook Server `_ pre-configured to run CadQuery. 73 | -------------------------------------------------------------------------------- /doc/intro.rst: -------------------------------------------------------------------------------- 1 | .. _what_is_cadquery: 2 | 3 | ********************* 4 | Introduction 5 | ********************* 6 | 7 | What is CadQuery 8 | ======================================== 9 | 10 | CadQuery is an intuitive, easy-to-use python library for building parametric 3D CAD models. It has several goals: 11 | 12 | * Build models with scripts that are as close as possible to how you'd describe the object to a human, 13 | using a standard, already established programming language 14 | 15 | * Create parametric models that can be very easily customized by end users 16 | 17 | * Output high quality CAD formats like STEP and AMF in addition to traditional STL 18 | 19 | * Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser 20 | 21 | CadQuery is based on 22 | `FreeCAD `_, 23 | which is turn based on the open-source `OpenCascade `_ modelling kernel. 24 | 25 | Using CadQuery, you can build fully parametric models with a very small amount of code. For example, this simple script 26 | produces a flat plate with a hole in the middle:: 27 | 28 | thickness = 0.5 29 | width=2.0 30 | result = Workplane("front").box(width,width,thickness).faces(">Z").hole(thickness) 31 | 32 | .. image:: _static/simpleblock.png 33 | 34 | That is a simple example, but it is similar to a more useful part: a parametric pillow block for a 35 | standard 608-size ball bearing:: 36 | 37 | (length,height,diam, thickness,padding) = ( 30.0,40.0,22.0,10.0,8.0) 38 | 39 | result = Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(diam)\ 40 | .faces(">Z").workplane() \ 41 | .rect(length-padding,height-padding,forConstruction=True) \ 42 | .vertices().cboreHole(2.4,4.4,2.1) 43 | 44 | .. image:: _static/pillowblock.png 45 | 46 | Many examples are available in the :ref:`examples` 47 | 48 | CadQuery is a library, GUIs are separate 49 | ============================================== 50 | 51 | CadQuery is a library, that's intentionally designed to be usable as a GUI-less library. This enables 52 | its use in a variety of engineering and scientific applications that create 3d models programmatically. 53 | 54 | If you would like a GUI, you have a couple of options: 55 | 56 | * Install cadquery as a part of `The CadQuery Freecad Module `_ 57 | * Use `tryCQ `_, a Jupyter Notebook server using `mybinder.org `_. This solution runs entirely in your browser. 58 | 59 | 60 | Why CadQuery instead of OpenSCAD? 61 | ============================================ 62 | 63 | Like OpenSCAD, CadQuery is an open-source, script based, parametric model generator. However, CadQuery stands out in many ways and has several key advantages: 64 | 65 | 1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure. 66 | This includes many standard libraries and IDEs 67 | 68 | 2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively 69 | by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations, 70 | in addition to the standard CSG operations supported by CGAL 71 | 72 | 3. **Ability to import/export STEP** the ability to begin with a STEP model, created in a CAD package, 73 | and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format 74 | 75 | 4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate 76 | features based on the position of other features, workplanes, vertices, etc. 77 | 78 | 5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD. 79 | 80 | Where does the name CadQuery come from? 81 | ======================================== 82 | 83 | CadQuery is inspired by `jQuery `_ , a popular framework that 84 | revolutionized web development involving javascript. 85 | 86 | CadQuery is for 3D CAD what jQuery is for javascript. 87 | If you are familiar with how jQuery works, you will probably recognize several jQuery features that CadQuery uses: 88 | 89 | * A fluent api to create clean, easy to read code 90 | 91 | * Ability to use the library along side other python libraries 92 | 93 | * Clear and complete documentation, with plenty of samples. 94 | 95 | 96 | -------------------------------------------------------------------------------- /doc/primer.rst: -------------------------------------------------------------------------------- 1 | .. _3d_cad_primer: 2 | 3 | 4 | CadQuery Concepts 5 | =================================== 6 | 7 | 8 | 3D BREP Topology Concepts 9 | --------------------------- 10 | Before talking about CadQuery, it makes sense to talk a little about 3D CAD Topology. CadQuery is based upon the 11 | OpenCascade kernel, which is uses Boundary Representations ( BREP ) for objects. This just means that objects 12 | are defined by their enclosing surfaces. 13 | 14 | When working in a BREP system, these fundamental constructs exist to define a shape ( working up the food chain): 15 | 16 | :vertex: a single point in space 17 | :edge: a connection between two or more vertices along a particular path ( called a curve ) 18 | :wire: a collection of edges that are connected together. 19 | :face: a set of edges or wires that enclose a surface 20 | :shell: a collection of faces that are connected together along some of their edges 21 | :solid: a shell that has a closed interior 22 | :compound: a collection of solids 23 | 24 | When using CadQuery, all of these objects are created, hopefully with the least possible work. In the actual CAD 25 | kernel, there are another set of Geometrical constructs involved as well. For example, an arc-shaped edge will 26 | hold a reference to an underlying curve that is a full cricle, and each linear edge holds underneath it the equation 27 | for a line. CadQuery shields you from these constructs. 28 | 29 | 30 | CQ, the CadQuery Object 31 | --------------------------- 32 | 33 | The CadQuery object wraps a BREP feature, and provides functionality around it. Typical examples include rotating, 34 | transforming, combining objects, and creating workplanes. 35 | 36 | See :ref:`apireference` to learn more. 37 | 38 | 39 | Workplanes 40 | --------------------------- 41 | 42 | Workplanes represent a plane in space, from which other features can be located. They have a center point and a local 43 | coordinate system. 44 | 45 | The most common way to create a workplane is to locate one on the face of a solid. You can also create new workplanes 46 | in space, or relative to other planes using offsets or rotations. 47 | 48 | The most powerful feature of workplanes is that they allow you to work in 2D space in the coordinate system of the 49 | workplane, and then build 3D features based on local coordinates. This makes scripts much easier to create and maintain. 50 | 51 | See :py:class:`cadquery.Workplane` to learn more 52 | 53 | 54 | 2D Construction 55 | --------------------------- 56 | 57 | Once you create a workplane, you can work in 2D, and then later use the features you create to make 3D objects. 58 | You'll find all of the 2D constructs you expect-- circles, lines, arcs, mirroring, points, etc. 59 | 60 | See :ref:`2dOperations` to learn more. 61 | 62 | 63 | 3D Construction 64 | --------------------------- 65 | 66 | You can construct 3D primitives such as boxes, spheres, wedges, and cylinders directly. You can also sweep, extrude, 67 | and loft 2D geometry to form 3D features. Of course the basic primitive operations are also available. 68 | 69 | See :ref:`3doperations` to learn more. 70 | 71 | 72 | 73 | Selectors 74 | --------------------------- 75 | 76 | Selectors allow you to select one or more features, for use to define new features. As an example, you might 77 | extrude a box, and then select the top face as the location for a new feature. Or, you might extrude a box, and 78 | then select all of the vertical edges so that you can apply a fillet to them. 79 | 80 | You can select Vertices, Edges, Faces, Solids, and Wires using selectors. 81 | 82 | Think of selectors as the equivalent of your hand and mouse, were you to build an object using a conventional CAD system. 83 | 84 | You can learn more about selectors :ref:`selectors` 85 | 86 | 87 | Construction Geometry 88 | --------------------------- 89 | Construction geometry are features that are not part of the object, but are only defined to aid in building the object. 90 | A common example might be to define a rectangle, and then use the corners to define a the location of a set of holes. 91 | 92 | Most CadQuery construction methods provide a forConstruction keyword, which creates a feature that will only be used 93 | to locate other features 94 | 95 | 96 | The Stack 97 | --------------------------- 98 | 99 | As you work in CadQuery, each operation returns a new CadQuery object with the result of that operations. Each CadQuery 100 | object has a list of objects, and a reference to its parent. 101 | 102 | You can always go backwards to older operations by removing the current object from the stack. For example:: 103 | 104 | CQ(someObject).faces(">Z").first().vertices() 105 | 106 | returns a CadQuery object that contains all of the vertices on highest face of someObject. But you can always move 107 | backwards in the stack to get the face as well:: 108 | 109 | CQ(someObject).faces(">Z").first().vertices().end() #returns the same as CQ(someObject).faces(">Z").first() 110 | 111 | You can browse stack access methods here :ref:`stackMethods` 112 | 113 | 114 | Chaining 115 | --------------------------- 116 | 117 | All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use 118 | the core CQ methods to get at the objects that were created. 119 | 120 | 121 | The Context Solid 122 | --------------------------- 123 | 124 | Most of the time, you are building a single object, and adding features to that single object. CadQuery watches 125 | your operations, and defines the first solid object created as the 'context solid'. After that, any features 126 | you create are automatically combined ( unless you specify otherwise) with that solid. This happens even if the 127 | solid was created a long way up in the stack. For example:: 128 | 129 | Workplane('XY').box(1,2,3).faces(">Z").circle(0.25).extrude() 130 | 131 | Will create a 1x2x3 box, with a cylindrical boss extending from the top face. It was not necessary to manually 132 | combine the cylinder created by extruding the circle with the box, because the default behavior for extrude is 133 | to combine the result with the context solid. The hole() method works similarly-- CadQuery presumes that you want 134 | to subtract the hole from the context solid. 135 | 136 | If you want to avoid this, you can specified combine=False, and CadQuery will create the solid separately. 137 | 138 | 139 | Iteration 140 | --------------------------- 141 | 142 | CAD models often have repeated geometry, and its really annoying to resort to for loops to construct features. 143 | Many CadQuery methods operate automatically on each element on the stack, so that you don't have to write loops. 144 | For example, this:: 145 | 146 | Workplane('XY').box(1,2,3).faces(">Z").vertices().circle(0.5) 147 | 148 | Will actually create 4 circles, because vertices() selects 4 vertices of a rectangular face, and the circle() method 149 | iterates on each member of the stack. 150 | 151 | This is really useful to remember when you author your own plugins. :py:meth:`cadquery.CQ.Workplane.each` is useful for this purpose. 152 | 153 | 154 | -------------------------------------------------------------------------------- /doc/roadmap.rst: -------------------------------------------------------------------------------- 1 | .. _roadmap: 2 | 3 | 4 | RoadMap: Planned Features 5 | ============================== 6 | 7 | **CadQuery is not even close to finished!!!** 8 | 9 | Many features are planned for later versions. This page tracks them. If you find that you need features 10 | not listed here, let us know! 11 | 12 | Core 13 | -------------------- 14 | 15 | end(n) 16 | allows moving backwards a fixed number of parents in the chain, eg end(3) is same as end().end().end() 17 | 18 | FreeCAD object wrappers 19 | return CQ wrappers for FreeCAD shapes instead of the native FreeCAD objects. 20 | 21 | Improved iteration tools for plugin developers 22 | make it easier to iterate over points and wires for plugins 23 | 24 | More parameter types (String? ) 25 | 26 | face.outerWire 27 | allow selecting the outerWire of a face, so that it can be used for reference geometry or offsets 28 | 29 | Selectors 30 | -------------------- 31 | 32 | tagged entities 33 | support tagging entities when they are created, so they can be selected later on using that tag. 34 | ideally, tags are propagated to features that are created from these features ( ie, an edge tagged with 'foo' 35 | that is later extruded into a face means that face would be tagged with 'foo' as well ) 36 | 37 | 38 | Workplanes 39 | -------------------- 40 | 41 | rotated workplanes 42 | support creation of workplanes at an angle to another plane or face 43 | 44 | workplane local rotations 45 | rotate the coordinate system of a workplane by an angle. 46 | 47 | make a workplane from a wire 48 | useful to select outer wire and then operate from there, to allow offsets 49 | 50 | 2-d operations 51 | ------------------- 52 | 53 | offsets 54 | offset profiles, including circles, rects, and other profiles. 55 | 56 | ellipses 57 | create elipses and portions of elipses 58 | 59 | regular polygons 60 | several construction methods: 61 | * number of sides and side length 62 | * number of sides inscribed in circle 63 | * number of sides circumscribed by circle 64 | 65 | arc construction using relative measures 66 | instead of forcing use of absolute workplane coordinates 67 | 68 | tangent arcs 69 | after a line 70 | 71 | centerpoint arcs 72 | including portions of arcs as well as with end points specified 73 | 74 | trimming 75 | ability to use construction geometry to trim other entities 76 | 77 | construction lines 78 | especially centerlines 79 | 80 | 2-d fillets 81 | for a rectangle, or for consecutive selected lines 82 | 83 | 2-d chamfers 84 | based on rectangles, polygons, polylines, or adjacent selected lines 85 | 86 | mirror around centerline 87 | using centerline construction geometry 88 | 89 | rectangular array 90 | automate creation of equally spread points 91 | 92 | polar array 93 | create equally spaced copies of a feature around a circle 94 | perhaps based on a construction circle? 95 | 96 | midpoint selection 97 | select midpoints of lines, arcs 98 | 99 | face center 100 | explicit selection of face center 101 | 102 | manipulate spline control points 103 | so that the shape of a spline can be more accurately controlled 104 | 105 | feature snap 106 | project geometry in the rest of the part into the work plane, so that 107 | they can be selected and used as references for other features. 108 | 109 | polyline edges 110 | allow polyline to be combined with other edges/curves 111 | 112 | create text 113 | ideally, in various fonts. 114 | 115 | 3-d operations 116 | --------------------- 117 | 118 | rotation/transform that return a copy 119 | The current rotateAboutCenter and translate method modify the object, rather than returning a copy 120 | 121 | primitive creation 122 | Need primitive creation for: 123 | * cone 124 | * sphere 125 | * cylinder 126 | * torus 127 | * wedge 128 | 129 | extrude/cut up to surface 130 | allow a cut or extrude to terminate at another surface, rather than either through all or a fixed distance 131 | 132 | extrude along a path 133 | rather than just normal to the plane. This would include 134 | 135 | STEP import 136 | allow embedding and importing step solids created in other tools, which 137 | can then be further manipulated parametrically 138 | 139 | Dome 140 | very difficult to do otherwise 141 | 142 | primitive boolean operations 143 | * intersect 144 | * union 145 | * subtract 146 | 147 | 148 | Algorithms 149 | --------------------- 150 | 151 | Wire Discretization 152 | Sample wires at point interval to improve closet wire computations 153 | 154 | 155 | -------------------------------------------------------------------------------- /doc/selectors.rst: -------------------------------------------------------------------------------- 1 | .. _selector_reference: 2 | 3 | String Selectors Reference 4 | ============================= 5 | 6 | 7 | CadQuery selector strings allow filtering various types of object lists. Most commonly, Edges, Faces, and Vertices are 8 | used, but all objects types can be filtered. 9 | 10 | String selectors are simply shortcuts for using the full object equivalents. If you pass one of the 11 | string patterns in, CadQuery will automatically use the associated selector object. 12 | 13 | * :py:meth:`cadquery.CQ.faces` 14 | * :py:meth:`cadquery.CQ.edges` 15 | * :py:meth:`cadquery.CQ.vertices` 16 | * :py:meth:`cadquery.CQ.solids` 17 | * :py:meth:`cadquery.CQ.shells` 18 | 19 | .. note:: 20 | 21 | String selectors are shortcuts to concrete selector classes, which you can use or extend. See 22 | :ref:`classreference` for more details 23 | 24 | If you find that the built-in selectors are not sufficient, you can easily plug in your own. 25 | See :ref:`extending` to see how. 26 | 27 | 28 | Combining Selectors 29 | ========================== 30 | 31 | Selectors can be combined logically, currently defined operators include **and**, **or**, **not** and **exc[ept]** (set difference). For example: 32 | 33 | .. cq_plot:: 34 | 35 | result = cq.Workplane("XY").box(2, 2, 2) \ 36 | .edges("|Z and >Y") \ 37 | .chamfer(0.2) 38 | 39 | show_object(result) 40 | 41 | Much more complex expressions are possible as well: 42 | 43 | .. cq_plot:: 44 | 45 | result = cq.Workplane("XY").box(2, 2, 2) \ 46 | .faces(">Z") \ 47 | .shell(-0.2) \ 48 | .faces(">Z") \ 49 | .edges("not(X or Y)") \ 50 | .chamfer(0.1) 51 | 52 | show_object(result) 53 | 54 | .. _filteringfaces: 55 | 56 | Filtering Faces 57 | ---------------- 58 | 59 | All types of filters work on faces. In most cases, the selector refers to the direction of the **normal vector** 60 | of the face. 61 | 62 | .. warning:: 63 | 64 | If a face is not planar, selectors are evaluated at the center of mass of the face. This can lead 65 | to results that are quite unexpected. 66 | 67 | The axis used in the listing below are for illustration: any axis would work similarly in each case. 68 | 69 | ========= ======================================= ======================================================= ========================== 70 | Selector Selects Selector Class # objects returned 71 | ========= ======================================= ======================================================= ========================== 72 | +Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0..many 73 | \|Z Faces parallel to xy plane :py:class:`cadquery.ParallelDirSelector` 0..many 74 | -X Faces with normal in neg x direction :py:class:`cadquery.DirectionSelector` 0..many 75 | #Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many 76 | %Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many 77 | >Y Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many 78 | Y[-2] 2nd Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many 80 | Y Edges farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many 108 | Y[1] 2nd closest edge in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many 110 | Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many 125 | (-1,1,0)').chamfer(1) 139 | 140 | show_object(result) 141 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex001_Simple_Block.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | length = 80.0 # Length of the block 5 | height = 60.0 # Height of the block 6 | thickness = 10.0 # Thickness of the block 7 | 8 | # Create a 3D block based on the dimension variables above. 9 | # 1. Establishes a workplane that an object can be built on. 10 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 11 | # positive Z direction is "up", and the negative Z direction is "down". 12 | result = cq.Workplane("XY").box(length, height, thickness) 13 | 14 | # The following method is now outdated, but can still be used to display the 15 | # results of the script if you want 16 | # from Helpers import show 17 | # show(result) # Render the result of this script 18 | 19 | show_object(result) 20 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex002_Block_With_Bored_Center_Hole.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | length = 80.0 # Length of the block 5 | height = 60.0 # Height of the block 6 | thickness = 10.0 # Thickness of the block 7 | center_hole_dia = 22.0 # Diameter of center hole in block 8 | 9 | # Create a block based on the dimensions above and add a 22mm center hole. 10 | # 1. Establishes a workplane that an object can be built on. 11 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 12 | # positive Z direction is "up", and the negative Z direction is "down". 13 | # 2. The highest (max) Z face is selected and a new workplane is created on it. 14 | # 3. The new workplane is used to drill a hole through the block. 15 | # 3a. The hole is automatically centered in the workplane. 16 | result = cq.Workplane("XY").box(length, height, thickness) \ 17 | .faces(">Z").workplane().hole(center_hole_dia) 18 | 19 | # Displays the result of this script 20 | show_object(result) 21 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex003_Pillow_Block_With_Counterbored_Holes.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | length = 80.0 # Length of the block 5 | height = 60.0 # Height of the block 6 | thickness = 10.0 # Thickness of the block 7 | center_hole_dia = 22.0 # Diameter of center hole in block 8 | cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter 9 | cbore_diameter = 4.4 # Bolt head pocket hole diameter 10 | cbore_depth = 2.1 # Bolt head pocket hole depth 11 | 12 | # Create a 3D block based on the dimensions above and add a 22mm center hold 13 | # and 4 counterbored holes for bolts 14 | # 1. Establishes a workplane that an object can be built on. 15 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 16 | # positive Z direction is "up", and the negative Z direction is "down". 17 | # 2. The highest(max) Z face is selected and a new workplane is created on it. 18 | # 3. The new workplane is used to drill a hole through the block. 19 | # 3a. The hole is automatically centered in the workplane. 20 | # 4. The highest(max) Z face is selected and a new workplane is created on it. 21 | # 5. A for-construction rectangle is created on the workplane based on the 22 | # block's overall dimensions. 23 | # 5a. For-construction objects are used only to place other geometry, they 24 | # do not show up in the final displayed geometry. 25 | # 6. The vertices of the rectangle (corners) are selected, and a counter-bored 26 | # hole is placed at each of the vertices (all 4 of them at once). 27 | result = cq.Workplane("XY").box(length, height, thickness) \ 28 | .faces(">Z").workplane().hole(center_hole_dia) \ 29 | .faces(">Z").workplane() \ 30 | .rect(length - 8.0, height - 8.0, forConstruction=True) \ 31 | .vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth) 32 | 33 | # Displays the result of this script 34 | show_object(result) 35 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex004_Extruded_Cylindrical_Plate.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | circle_radius = 50.0 # Radius of the plate 5 | thickness = 13.0 # Thickness of the plate 6 | rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate 7 | rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate 8 | 9 | # Extrude a cylindrical plate with a rectangular hole in the middle of it. 10 | # 1. Establishes a workplane that an object can be built on. 11 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 12 | # that the positive Z direction is "up", and the negative Z direction 13 | # is "down". 14 | # 2. The 2D geometry for the outer circle is created at the same time as the 15 | # rectangle that will create the hole in the center. 16 | # 2a. The circle and the rectangle will be automatically centered on the 17 | # workplane. 18 | # 2b. Unlike some other functions like the hole(), circle() takes 19 | # a radius and not a diameter. 20 | # 3. The circle and rectangle are extruded together, creating a cylindrical 21 | # plate with a rectangular hole in the center. 22 | # 3a. circle() and rect() could be changed to any other shape to completely 23 | # change the resulting plate and/or the hole in it. 24 | result = cq.Workplane("front").circle(circle_radius) \ 25 | .rect(rectangle_width, rectangle_length) \ 26 | .extrude(thickness) 27 | 28 | # Displays the result of this script 29 | show_object(result) 30 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex005_Extruded_Lines_and_Arcs.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | width = 2.0 # Overall width of the plate 5 | thickness = 0.25 # Thickness of the plate 6 | 7 | # Extrude a plate outline made of lines and an arc 8 | # 1. Establishes a workplane that an object can be built on. 9 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 10 | # that the positive Z direction is "up", and the negative Z direction 11 | # is "down". 12 | # 2. Draws a line from the origin to an X position of the plate's width. 13 | # 2a. The starting point of a 2D drawing like this will be at the center of the 14 | # workplane (0, 0) unless the moveTo() function moves the starting point. 15 | # 3. A line is drawn from the last position straight up in the Y direction 16 | # 1.0 millimeters. 17 | # 4. An arc is drawn from the last point, through point (1.0, 1.5) which is 18 | # half-way back to the origin in the X direction and 0.5 mm above where 19 | # the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm 20 | # above (in the Y direction) where our first line started from. 21 | # 5. An arc is drawn from the last point that ends on (-0.5, 1.0), the sag of 22 | # the curve 0.2 determines that the curve is concave with the midpoint 0.1 mm 23 | # from the arc baseline. If the sag was -0.2 the arc would be convex. 24 | # This convention is valid when the profile is drawn counterclockwise. 25 | # The reverse is true if the profile is drawn clockwise. 26 | # Clockwise: +sag => convex, -sag => concave 27 | # Counterclockwise: +sag => concave, -sag => convex 28 | # 6. An arc is drawn from the last point that ends on (-0.7, -0.2), the arc is 29 | # determined by the radius of -1.5 mm. 30 | # Clockwise: +radius => convex, -radius => concave 31 | # Counterclockwise: +radius => concave, -radius => convex 32 | # 7. close() is called to automatically draw the last line for us and close 33 | # the sketch so that it can be extruded. 34 | # 7a. Without the close(), the 2D sketch will be left open and the extrude 35 | # operation will provide unpredictable results. 36 | # 8. The 2D sketch is extruded into a solid object of the specified thickness. 37 | result = cq.Workplane("front").lineTo(width, 0) \ 38 | .lineTo(width, 1.0) \ 39 | .threePointArc((1.0, 1.5), (0.0, 1.0)) \ 40 | .sagittaArc((-0.5, 1.0), 0.2) \ 41 | .radiusArc((-0.7, -0.2), -1.5) \ 42 | .close().extrude(thickness) 43 | 44 | # Displays the result of this script 45 | show_object(result) 46 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex006_Moving_the_Current_Working_Point.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | circle_radius = 3.0 # The outside radius of the plate 5 | thickness = 0.25 # The thickness of the plate 6 | 7 | # Make a plate with two cutouts in it by moving the workplane center point 8 | # 1. Establishes a workplane that an object can be built on. 9 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 10 | # that the positive Z direction is "up", and the negative Z direction 11 | # is "down". 12 | # 1b. The initial workplane center point is the center of the circle, at (0,0). 13 | # 2. A circle is created at the center of the workplane 14 | # 2a. Notice that circle() takes a radius and not a diameter 15 | result = cq.Workplane("front").circle(circle_radius) 16 | 17 | # 3. The work center is movide to (1.5, 0.0) by calling center(). 18 | # 3a. The new center is specified relative to the previous center,not 19 | # relative to global coordinates. 20 | # 4. A 0.5mm x 0.5mm 2D square is drawn inside the circle. 21 | # 4a. The plate has not been extruded yet, only 2D geometry is being created. 22 | result = result.center(1.5, 0.0).rect(0.5, 0.5) 23 | 24 | # 5. The work center is moved again, this time to (-1.5, 1.5). 25 | # 6. A 2D circle is created at that new center with a radius of 0.25mm. 26 | result = result.center(-1.5, 1.5).circle(0.25) 27 | 28 | # 7. All 2D geometry is extruded to the specified thickness of the plate. 29 | # 7a. The small circle and the square are enclosed in the outer circle of the 30 | # plate and so it is assumed that we want them to be cut out of the plate. 31 | # A separate cut operation is not needed. 32 | result = result.extrude(thickness) 33 | 34 | # Displays the result of this script 35 | show_object(result) 36 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex007_Using_Point_Lists.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | plate_radius = 2.0 # The radius of the plate that will be extruded 5 | hole_pattern_radius = 0.25 # Radius of circle where the holes will be placed 6 | thickness = 0.125 # The thickness of the plate that will be extruded 7 | 8 | # Make a plate with 4 holes in it at various points in a polar arrangement from 9 | # the center of the workplane. 10 | # 1. Establishes a workplane that an object can be built on. 11 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 12 | # that the positive Z direction is "up", and the negative Z direction 13 | # is "down". 14 | # 2. A 2D circle is drawn that will become though outer profile of the plate. 15 | r = cq.Workplane("front").circle(plate_radius) 16 | 17 | # 3. Push 4 points on the stack that will be used as the center points of the 18 | # holes. 19 | r = r.pushPoints([(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)]) 20 | 21 | # 4. This circle() call will operate on all four points, putting a circle at 22 | # each one. 23 | r = r.circle(hole_pattern_radius) 24 | 25 | # 5. All 2D geometry is extruded to the specified thickness of the plate. 26 | # 5a. The small hole circles are enclosed in the outer circle of the plate and 27 | # so it is assumed that we want them to be cut out of the plate. A 28 | # separate cut operation is not needed. 29 | result = r.extrude(thickness) 30 | 31 | # Displays the result of this script 32 | show_object(result) 33 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex008_Polygon_Creation.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | width = 3.0 # The width of the plate 5 | height = 4.0 # The height of the plate 6 | thickness = 0.25 # The thickness of the plate 7 | polygon_sides = 6 # The number of sides that the polygonal holes should have 8 | polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points 9 | 10 | # Create a plate with two polygons cut through it 11 | # 1. Establishes a workplane that an object can be built on. 12 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 13 | # that the positive Z direction is "up", and the negative Z direction 14 | # is "down". 15 | # 2. A 3D box is created in one box() operation to represent the plate. 16 | # 2a. The box is centered around the origin, which creates a result that may 17 | # be unituitive when the polygon cuts are made. 18 | # 3. 2 points are pushed onto the stack and will be used as centers for the 19 | # polygonal holes. 20 | # 4. The two polygons are created, on for each point, with one call to 21 | # polygon() using the number of sides and the circle that bounds the 22 | # polygon. 23 | # 5. The polygons are cut thru all objects that are in the line of extrusion. 24 | # 5a. A face was not selected, and so the polygons are created on the 25 | # workplane. Since the box was centered around the origin, the polygons end 26 | # up being in the center of the box. This makes them cut from the center to 27 | # the outside along the normal (positive direction). 28 | # 6. The polygons are cut through all objects, starting at the center of the 29 | # box/plate and going "downward" (opposite of normal) direction. Functions 30 | # like cutBlind() assume a positive cut direction, but cutThruAll() assumes 31 | # instead that the cut is made from a max direction and cuts downward from 32 | # that max through all objects. 33 | result = cq.Workplane("front").box(width, height, thickness) \ 34 | .pushPoints([(0, 0.75), (0, -0.75)]) \ 35 | .polygon(polygon_sides, polygon_dia) \ 36 | .cutThruAll() 37 | 38 | # Displays the result of this script 39 | show_object(result) 40 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex009_Polylines.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # These can be modified rather than hardcoding values for each dimension. 4 | # Define up our Length, Height, Width, and thickness of the beam 5 | (L, H, W, t) = (100.0, 20.0, 20.0, 1.0) 6 | 7 | # Define the points that the polyline will be drawn to/thru 8 | pts = [ 9 | (W/2.0, H/2.0), 10 | (W/2.0, (H/2.0 - t)), 11 | (t/2.0, (H/2.0-t)), 12 | (t/2.0, (t - H/2.0)), 13 | (W/2.0, (t - H/2.0)), 14 | (W/2.0, H/-2.0), 15 | (0, H/-2.0) 16 | ] 17 | 18 | # We generate half of the I-beam outline and then mirror it to create the full 19 | # I-beam. 20 | # 1. Establishes a workplane that an object can be built on. 21 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 22 | # that the positive Z direction is "up", and the negative Z direction 23 | # is "down". 24 | # 2. moveTo() is used to move the first point from the origin (0, 0) to 25 | # (0, 10.0), with 10.0 being half the height (H/2.0). If this is not done 26 | # the first line will start from the origin, creating an extra segment that 27 | # will cause the extrude to have an invalid shape. 28 | # 3. The polyline function takes a list of points and generates the lines 29 | # through all the points at once. 30 | # 3. Only half of the I-beam profile has been drawn so far. That half is 31 | # mirrored around the Y-axis to create the complete I-beam profile. 32 | # 4. The I-beam profile is extruded to the final length of the beam. 33 | result = cq.Workplane("front").moveTo(0, H/2.0) \ 34 | .polyline(pts) \ 35 | .mirrorY() \ 36 | .extrude(L) 37 | 38 | # Displays the result of this script 39 | show_object(result) 40 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex010_Defining_an_Edge_with_a_Spline.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # 1. Establishes a workplane to create the spline on to extrude. 4 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 5 | # positive Z direction is "up", and the negative Z direction is "down". 6 | s = cq.Workplane("XY") 7 | 8 | # The points that the spline will pass through 9 | sPnts = [ 10 | (2.75, 1.5), 11 | (2.5, 1.75), 12 | (2.0, 1.5), 13 | (1.5, 1.0), 14 | (1.0, 1.25), 15 | (0.5, 1.0), 16 | (0, 1.0) 17 | ] 18 | 19 | # 2. Generate our plate with the spline feature and make sure it is a 20 | # closed entity 21 | r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts).close() 22 | 23 | # 3. Extrude to turn the wire into a plate 24 | result = r.extrude(0.5) 25 | 26 | # Displays the result of this script 27 | show_object(result) 28 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex011_Mirroring_Symmetric_Geometry.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # 1. Establishes a workplane that an object can be built on. 4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 5 | # that the positive Z direction is "up", and the negative Z direction 6 | # is "down". 7 | # 2. A horizontal line is drawn on the workplane with the hLine function. 8 | # 2a. 1.0 is the distance, not coordinate. hLineTo allows using xCoordinate 9 | # not distance. 10 | r = cq.Workplane("front").hLine(1.0) 11 | 12 | # 3. Draw a series of vertical and horizontal lines with the vLine and hLine 13 | # functions. 14 | r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0) 15 | 16 | # 4. Mirror the geometry about the Y axis and extrude it into a 3D object. 17 | result = r.mirrorY().extrude(0.25) 18 | 19 | # Displays the result of this script 20 | show_object(result) 21 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex012_Creating_Workplanes_on_Faces.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # 1. Establishes a workplane that an object can be built on. 4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 5 | # that the positive Z direction is "up", and the negative Z direction 6 | # is "down". 7 | # 2. Creates a 3D box that will have a hole placed in it later. 8 | result = cq.Workplane("front").box(2, 3, 0.5) 9 | 10 | # 3. Find the top-most face with the >Z max selector. 11 | # 3a. Establish a new workplane to build geometry on. 12 | # 3b. Create a hole down into the box. 13 | result = result.faces(">Z").workplane().hole(0.5) 14 | 15 | # Displays the result of this script 16 | show_object(result) 17 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex013_Locating_a_Workplane_on_a_Vertex.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # 1. Establishes a workplane that an object can be built on. 4 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 5 | # that the positive Z direction is "up", and the negative Z direction 6 | # is "down". 7 | # 2. Creates a 3D box that will have a hole placed in it later. 8 | result = cq.Workplane("front").box(3, 2, 0.5) 9 | 10 | # 3. Select the lower left vertex and make a workplane. 11 | # 3a. The top-most Z face is selected using the >Z selector. 12 | # 3b. The lower-left vertex of the faces is selected with the Z").vertices("Z") \ 17 | .workplane() \ 18 | .transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0)) \ 19 | .rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25) 20 | 21 | # Displays the result of this script 22 | show_object(result) 23 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex016_Using_Construction_Geometry.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a block with holes in each corner of a rectangle on that workplane. 4 | # 1. Establishes a workplane that an object can be built on. 5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 6 | # that the positive Z direction is "up", and the negative Z direction 7 | # is "down". 8 | # 2. Creates a plain box to base future geometry on with the box() function. 9 | # 3. Selects the top-most Z face of the box. 10 | # 4. Creates a new workplane to build new geometry on. 11 | # 5. Creates a for-construction rectangle that only exists to use for placing 12 | # other geometry. 13 | # 6. Selects the vertices of the for-construction rectangle. 14 | # 7. Places holes at the center of each selected vertex. 15 | result = cq.Workplane("front").box(2, 2, 0.5)\ 16 | .faces(">Z").workplane() \ 17 | .rect(1.5, 1.5, forConstruction=True).vertices() \ 18 | .hole(0.125) 19 | 20 | # Displays the result of this script 21 | show_object(result) 22 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex017_Shelling_to_Create_Thin_Features.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a hollow box that's open on both ends with a thin wall. 4 | # 1. Establishes a workplane that an object can be built on. 5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 6 | # that the positive Z direction is "up", and the negative Z direction 7 | # is "down". 8 | # 2. Creates a plain box to base future geometry on with the box() function. 9 | # 3. Selects faces with normal in +z direction. 10 | # 4. Create a shell by cutting out the top-most Z face. 11 | result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.05) 12 | 13 | # Displays the result of this script 14 | show_object(result) 15 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex018_Making_Lofts.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a lofted section between a rectangle and a circular section. 4 | # 1. Establishes a workplane that an object can be built on. 5 | # 1a. Uses the named plane orientation "front" to define the workplane, meaning 6 | # that the positive Z direction is "up", and the negative Z direction 7 | # is "down". 8 | # 2. Creates a plain box to base future geometry on with the box() function. 9 | # 3. Selects the top-most Z face of the box. 10 | # 4. Draws a 2D circle at the center of the the top-most face of the box. 11 | # 5. Creates a workplane 3 mm above the face the circle was drawn on. 12 | # 6. Draws a 2D circle on the new, offset workplane. 13 | # 7. Creates a loft between the circle and the rectangle. 14 | result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \ 15 | .circle(1.5).workplane(offset=3.0) \ 16 | .rect(0.75, 0.5) \ 17 | .loft(combine=True) 18 | 19 | # Displays the result of this script 20 | show_object(result) 21 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex019_Counter_Sunk_Holes.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a plate with 4 counter-sunk holes in it. 4 | # 1. Establishes a workplane using an XY object instead of a named plane. 5 | # 2. Creates a plain box to base future geometry on with the box() function. 6 | # 3. Selects the top-most face of the box and established a workplane on that. 7 | # 4. Draws a for-construction rectangle on the workplane which only exists for 8 | # placing other geometry. 9 | # 5. Selects the corner vertices of the rectangle and places a counter-sink 10 | # hole, using each vertex as the center of a hole using the cskHole() 11 | # function. 12 | # 5a. When the depth of the counter-sink hole is set to None, the hole will be 13 | # cut through. 14 | result = cq.Workplane(cq.Plane.XY()).box(4, 2, 0.5).faces(">Z") \ 15 | .workplane().rect(3.5, 1.5, forConstruction=True) \ 16 | .vertices().cskHole(0.125, 0.25, 82.0, depth=None) 17 | 18 | # Displays the result of this script 19 | show_object(result) 20 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex020_Rounding_Corners_with_Fillets.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a plate with 4 rounded corners in the Z-axis. 4 | # 1. Establishes a workplane that an object can be built on. 5 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 6 | # positive Z direction is "up", and the negative Z direction is "down". 7 | # 2. Creates a plain box to base future geometry on with the box() function. 8 | # 3. Selects all edges that are parallel to the Z axis. 9 | # 4. Creates fillets on each of the selected edges with the specified radius. 10 | result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125) 11 | 12 | # Displays the result of this script 13 | show_object(result) 14 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex021_Splitting_an_Object.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Create a simple block with a hole through it that we can split. 4 | # 1. Establishes a workplane that an object can be built on. 5 | # 1a. Uses the X and Y origins to define the workplane, meaning that the 6 | # positive Z direction is "up", and the negative Z direction is "down". 7 | # 2. Creates a plain box to base future geometry on with the box() function. 8 | # 3. Selects the top-most face of the box and establishes a workplane on it 9 | # that new geometry can be built on. 10 | # 4. Draws a 2D circle on the new workplane and then uses it to cut a hole 11 | # all the way through the box. 12 | c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane() \ 13 | .circle(0.25).cutThruAll() 14 | 15 | # 5. Selects the face furthest away from the origin in the +Y axis direction. 16 | # 6. Creates an offset workplane that is set in the center of the object. 17 | # 6a. One possible improvement to this script would be to make the dimensions 18 | # of the box variables, and then divide the Y-axis dimension by 2.0 and 19 | # use that to create the offset workplane. 20 | # 7. Uses the embedded workplane to split the object, keeping only the "top" 21 | # portion. 22 | result = c.faces(">Y").workplane(-0.5).split(keepTop=True) 23 | 24 | # Displays the result of this script 25 | show_object(result) 26 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex022_Classic_OCC_Bottle.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Set up the length, width, and thickness 4 | (L, w, t) = (20.0, 6.0, 3.0) 5 | s = cq.Workplane("XY") 6 | 7 | # Draw half the profile of the bottle and extrude it 8 | p = s.center(-L / 2.0, 0).vLine(w / 2.0) \ 9 | .threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0)).vLine(-w / 2.0) \ 10 | .mirrorX().extrude(30.0, True) 11 | 12 | # Make the neck 13 | p.faces(">Z").workplane().circle(3.0).extrude(2.0, True) 14 | 15 | # Make a shell 16 | result = p.faces(">Z").shell(0.3) 17 | 18 | # Displays the result of this script 19 | show_object(result) 20 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex023_Parametric_Enclosure.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Parameter definitions 4 | p_outerWidth = 100.0 # Outer width of box enclosure 5 | p_outerLength = 150.0 # Outer length of box enclosure 6 | p_outerHeight = 50.0 # Outer height of box enclosure 7 | 8 | p_thickness = 3.0 # Thickness of the box walls 9 | p_sideRadius = 10.0 # Radius for the curves around the sides of the bo 10 | p_topAndBottomRadius = 2.0 # Radius for the curves on the top and bottom edges 11 | 12 | p_screwpostInset = 12.0 # How far in from the edges the screwposts should be 13 | p_screwpostID = 4.0 # Inner diameter of the screwpost holes, should be roughly screw diameter not including threads 14 | p_screwpostOD = 10.0 # Outer diameter of the screwposts. Determines overall thickness of the posts 15 | 16 | p_boreDiameter = 8.0 # Diameter of the counterbore hole, if any 17 | p_boreDepth = 1.0 # Depth of the counterbore hole, if 18 | p_countersinkDiameter = 0.0 # Outer diameter of countersink. Should roughly match the outer diameter of the screw head 19 | p_countersinkAngle = 90.0 # Countersink angle (complete angle between opposite sides, not from center to one side) 20 | p_lipHeight = 1.0 # Height of lip on the underside of the lid. Sits inside the box body for a snug fit. 21 | 22 | # Outer shell 23 | oshell = cq.Workplane("XY").rect(p_outerWidth, p_outerLength) \ 24 | .extrude(p_outerHeight + p_lipHeight) 25 | 26 | # Weird geometry happens if we make the fillets in the wrong order 27 | if p_sideRadius > p_topAndBottomRadius: 28 | oshell.edges("|Z").fillet(p_sideRadius) 29 | oshell.edges("#Z").fillet(p_topAndBottomRadius) 30 | else: 31 | oshell.edges("#Z").fillet(p_topAndBottomRadius) 32 | oshell.edges("|Z").fillet(p_sideRadius) 33 | 34 | # Inner shell 35 | ishell = oshell.faces("Z").workplane(-p_thickness)\ 48 | .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)\ 49 | .vertices() 50 | 51 | for v in postCenters.all(): 52 | v.circle(p_screwpostOD / 2.0).circle(p_screwpostID / 2.0)\ 53 | .extrude((-1.0) * ((p_outerHeight + p_lipHeight) - (2.0 * p_thickness)), True) 54 | 55 | # Split lid into top and bottom parts 56 | (lid, bottom) = box.faces(">Z").workplane(-p_thickness - p_lipHeight).split(keepTop=True, keepBottom=True).all() 57 | 58 | # Translate the lid, and subtract the bottom from it to produce the lid inset 59 | lowerLid = lid.translate((0, 0, -p_lipHeight)) 60 | cutlip = lowerLid.cut(bottom).translate((p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight)) 61 | 62 | # Compute centers for counterbore/countersink or counterbore 63 | topOfLidCenters = cutlip.faces(">Z").workplane().rect(POSTWIDTH, POSTLENGTH, forConstruction=True).vertices() 64 | 65 | # Add holes of the desired type 66 | if p_boreDiameter > 0 and p_boreDepth > 0: 67 | topOfLid = topOfLidCenters.cboreHole(p_screwpostID, p_boreDiameter, p_boreDepth, (2.0) * p_thickness) 68 | elif p_countersinkDiameter > 0 and p_countersinkAngle > 0: 69 | topOfLid = topOfLidCenters.cskHole(p_screwpostID, p_countersinkDiameter, p_countersinkAngle, (2.0) * p_thickness) 70 | else: 71 | topOfLid= topOfLidCenters.hole(p_screwpostID, 2.0 * p_thickness) 72 | 73 | # Return the combined result 74 | result = topOfLid.combineSolids(bottom) 75 | 76 | # Displays the result of this script 77 | show_object(result) 78 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex024_Using_FreeCAD_Solids_as_CQ_Objects.py: -------------------------------------------------------------------------------- 1 | # This example is meant to be used from within the CadQuery module of FreeCAD. 2 | import cadquery 3 | import FreeCAD 4 | 5 | # Create a new document that we can draw our model on 6 | newDoc = FreeCAD.newDocument() 7 | 8 | # Shows a 1x1x1 FreeCAD cube in the display 9 | initialBox = newDoc.addObject("Part::Box", "initialBox") 10 | newDoc.recompute() 11 | 12 | # Make a CQ object 13 | cqBox = cadquery.CQ(cadquery.Solid(initialBox.Shape)) 14 | 15 | # Extrude a peg 16 | newThing = cqBox.faces(">Z").workplane().circle(0.5).extrude(0.25) 17 | 18 | # Add a FreeCAD object to the tree and then store a CQ object in it 19 | nextShape = newDoc.addObject("Part::Feature", "nextShape") 20 | nextShape.Shape = newThing.val().wrapped 21 | 22 | # Rerender the doc to see what the new solid looks like 23 | newDoc.recompute() 24 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex025_Revolution.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # The dimensions of the model. These can be modified rather than changing the 4 | # shape's code directly. 5 | rectangle_width = 10.0 6 | rectangle_length = 10.0 7 | angle_degrees = 360.0 8 | 9 | # Revolve a cylinder from a rectangle 10 | # Switch comments around in this section to try the revolve operation with different parameters 11 | result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve() 12 | #result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees) 13 | #result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5)) 14 | #result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5)) 15 | #result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False) 16 | 17 | # Revolve a donut with square walls 18 | #result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10)) 19 | 20 | # Displays the result of this script 21 | show_object(result) 22 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex026_Lego_Brick.py: -------------------------------------------------------------------------------- 1 | # This script can create any regular rectangular Lego(TM) Brick 2 | import cadquery as cq 3 | 4 | ##### 5 | # Inputs 6 | ###### 7 | lbumps = 1 # number of bumps long 8 | wbumps = 1 # number of bumps wide 9 | thin = True # True for thin, False for thick 10 | 11 | # 12 | # Lego Brick Constants-- these make a lego brick a lego :) 13 | # 14 | pitch = 8.0 15 | clearance = 0.1 16 | bumpDiam = 4.8 17 | bumpHeight = 1.8 18 | if thin: 19 | height = 3.2 20 | else: 21 | height = 9.6 22 | 23 | t = (pitch - (2 * clearance) - bumpDiam) / 2.0 24 | postDiam = pitch - t # works out to 6.5 25 | total_length = lbumps*pitch - 2.0*clearance 26 | total_width = wbumps*pitch - 2.0*clearance 27 | 28 | # make the base 29 | s = cq.Workplane("XY").box(total_length, total_width, height) 30 | 31 | # shell inwards not outwards 32 | s = s.faces("Z").workplane(). \ 36 | rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \ 37 | .extrude(bumpHeight) 38 | 39 | # add posts on the bottom. posts are different diameter depending on geometry 40 | # solid studs for 1 bump, tubes for multiple, none for 1x1 41 | tmp = s.faces(" 1 and wbumps > 1: 44 | tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \ 45 | circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t) 46 | elif lbumps > 1: 47 | tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \ 48 | circle(t).extrude(height - t) 49 | elif wbumps > 1: 50 | tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \ 51 | circle(t).extrude(height - t) 52 | else: 53 | tmp = s 54 | 55 | # Render the solid 56 | show_object(tmp) 57 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex027_Remote_Enclosure.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | exploded = False # when true, moves the base away from the top so we see 4 | showTop = True # When true, the top is rendered. 5 | showCover = True # When true, the cover is rendered 6 | 7 | width = 2.2 # Nominal x dimension of the part 8 | height = 0.5 # Height from bottom top to the top of the top :P 9 | length = 1.5 # Nominal y dimension of the part 10 | trapezoidFudge = 0.7 # ratio of trapezoid bases. set to 1.0 for cube 11 | xHoleOffset = 0.500 # Holes are distributed symmetrically about each axis 12 | yHoleOffset = 0.500 13 | zFilletRadius = 0.50 # Fillet radius of corners perp. to Z axis. 14 | yFilletRadius = 0.250 # Fillet radius of the top edge of the case 15 | lipHeight = 0.1 # The height of the lip on the inside of the cover 16 | wallThickness = 0.06 # Wall thickness for the case 17 | coverThickness = 0.2 # Thickness of the cover plate 18 | holeRadius = 0.30 # Button hole radius 19 | counterSyncAngle = 100 # Countersink angle. 20 | 21 | xyplane = cq.Workplane("XY") 22 | yzplane = cq.Workplane("YZ") 23 | 24 | 25 | def trapezoid(b1, b2, h): 26 | "Defines a symmetrical trapezoid in the XY plane." 27 | 28 | y = h / 2 29 | x1 = b1 / 2 30 | x2 = b2 / 2 31 | return (xyplane.moveTo(-x1, y) 32 | .polyline([(x1, y), 33 | (x2, -y), 34 | (-x2, -y)]).close()) 35 | 36 | 37 | # Defines our base shape: a box with fillets around the vertical edges. 38 | # This has to be a function because we need to create multiple copies of 39 | # the shape. 40 | def base(h): 41 | return (trapezoid(width, width * trapezoidFudge, length) 42 | .extrude(h) 43 | .translate((0, 0, height / 2)) 44 | .edges("Z") 45 | .fillet(zFilletRadius)) 46 | 47 | # start with the base shape 48 | top = (base(height) 49 | # then fillet the top edge 50 | .edges(">Z") 51 | .fillet(yFilletRadius) 52 | # shell the solid from the bottom face, with a .060" wall thickness 53 | .faces("Z") 57 | .workplane() 58 | .pushPoints([(0, 0), 59 | (-xHoleOffset, 0), 60 | (0, -yHoleOffset), 61 | (xHoleOffset, 0), 62 | (0, yHoleOffset)]) 63 | .cskHole(diameter=holeRadius, 64 | cskDiameter=holeRadius * 1.5, 65 | cskAngle=counterSyncAngle)) 66 | 67 | # the bottom cover begins with the same basic shape as the top 68 | cover = (base(coverThickness) 69 | # we need to move it upwards into the parent solid slightly. 70 | .translate((0, 0, -coverThickness + lipHeight)) 71 | # now we subtract the top from the cover. This produces a lip on the 72 | # solid NOTE: that this does not account for mechanical tolerances. 73 | # But it looks cool. 74 | .cut(top) 75 | # try to fillet the inner edge of the cover lip. Technically this 76 | # fillets every edge perpendicular to the Z axis. 77 | .edges("#Z") 78 | .fillet(.020) 79 | .translate((0, 0, -0.5 if exploded else 0))) 80 | 81 | # Conditionally render the parts 82 | if showTop: 83 | show_object(top) 84 | if showCover: 85 | show_object(cover) 86 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex028_Numpy.py: -------------------------------------------------------------------------------- 1 | # NOTE: This example may not run correctly in some revisions of FreeCAD 0.16 2 | import numpy as np 3 | import cadquery as cq 4 | 5 | # Square side and offset in x and y. 6 | side = 10 7 | offset = 5 8 | 9 | # Define the locations that the polyline will be drawn to/thru. 10 | # The polyline is defined as numpy.array so that operations like translation 11 | # of all points are simplified. 12 | pts = np.array([ 13 | (0, 0), 14 | (side, 0), 15 | (side, side), 16 | (0, side), 17 | (0, 0) 18 | ]) + [offset, offset] 19 | 20 | result = cq.Workplane('XY') \ 21 | .polyline(pts).close().extrude(2) \ 22 | .faces('+Z').workplane().circle(side / 2).extrude(1) 23 | 24 | # Render the solid 25 | show_object(result) 26 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex029_Braille.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import division 4 | 5 | from collections import namedtuple 6 | 7 | import cadquery as cq 8 | 9 | # text_lines is a list of text lines. 10 | # "CadQuery" in braille (converted with braille-converter: 11 | # https://github.com/jpaugh/braille-converter.git). 12 | text_lines = [u'⠠ ⠉ ⠁ ⠙ ⠠ ⠟ ⠥ ⠻ ⠽'] 13 | # See http://www.tiresias.org/research/reports/braille_cell.htm for examples 14 | # of braille cell geometry. 15 | horizontal_interdot = 2.5 16 | vertical_interdot = 2.5 17 | horizontal_intercell = 6 18 | vertical_interline = 10 19 | dot_height = 0.5 20 | dot_diameter = 1.3 21 | 22 | base_thickness = 1.5 23 | 24 | # End of configuration. 25 | BrailleCellGeometry = namedtuple('BrailleCellGeometry', 26 | ('horizontal_interdot', 27 | 'vertical_interdot', 28 | 'intercell', 29 | 'interline', 30 | 'dot_height', 31 | 'dot_diameter')) 32 | 33 | 34 | class Point(object): 35 | def __init__(self, x, y): 36 | self.x = x 37 | self.y = y 38 | 39 | def __add__(self, other): 40 | return Point(self.x + other.x, self.y + other.y) 41 | 42 | def __len__(self): 43 | """Necessary to have it accepted as input to CadQuery""" 44 | return 2 45 | 46 | def __getitem__(self, index): 47 | """Necessary to have it accepted as input to CadQuery""" 48 | return (self.x, self.y)[index] 49 | 50 | def __str__(self): 51 | return '({}, {})'.format(self.x, self.y) 52 | 53 | 54 | def braille_to_points(text, cell_geometry): 55 | # Unicode bit pattern (cf. https://en.wikipedia.org/wiki/Braille_Patterns). 56 | mask1 = 0b00000001 57 | mask2 = 0b00000010 58 | mask3 = 0b00000100 59 | mask4 = 0b00001000 60 | mask5 = 0b00010000 61 | mask6 = 0b00100000 62 | mask7 = 0b01000000 63 | mask8 = 0b10000000 64 | masks = (mask1, mask2, mask3, mask4, mask5, mask6, mask7, mask8) 65 | 66 | # Corresponding dot position 67 | w = cell_geometry.horizontal_interdot 68 | h = cell_geometry.vertical_interdot 69 | pos1 = Point(0, 2 * h) 70 | pos2 = Point(0, h) 71 | pos3 = Point(0, 0) 72 | pos4 = Point(w, 2 * h) 73 | pos5 = Point(w, h) 74 | pos6 = Point(w, 0) 75 | pos7 = Point(0, -h) 76 | pos8 = Point(w, -h) 77 | pos = (pos1, pos2, pos3, pos4, pos5, pos6, pos7, pos8) 78 | 79 | # Braille blank pattern (u'\u2800'). 80 | blank = u'⠀' 81 | points = [] 82 | # Position of dot1 along the x-axis (horizontal). 83 | character_origin = 0 84 | for c in text: 85 | for m, p in zip(masks, pos): 86 | delta_to_blank = ord(c) - ord(blank) 87 | if (m & delta_to_blank): 88 | points.append(p + Point(character_origin, 0)) 89 | character_origin += cell_geometry.intercell 90 | return points 91 | 92 | 93 | def get_plate_height(text_lines, cell_geometry): 94 | # cell_geometry.vertical_interdot is also used as space between base 95 | # borders and characters. 96 | return (2 * cell_geometry.vertical_interdot + 97 | 2 * cell_geometry.vertical_interdot + 98 | (len(text_lines) - 1) * cell_geometry.interline) 99 | 100 | 101 | def get_plate_width(text_lines, cell_geometry): 102 | # cell_geometry.horizontal_interdot is also used as space between base 103 | # borders and characters. 104 | max_len = max([len(t) for t in text_lines]) 105 | return (2 * cell_geometry.horizontal_interdot + 106 | cell_geometry.horizontal_interdot + 107 | (max_len - 1) * cell_geometry.intercell) 108 | 109 | 110 | def get_cylinder_radius(cell_geometry): 111 | """Return the radius the cylinder should have 112 | 113 | The cylinder have the same radius as the half-sphere that make the dots 114 | (the hidden and the shown part of the dots). 115 | The radius is such that the spherical cap with diameter 116 | cell_geometry.dot_diameter has a height of cell_geometry.dot_height. 117 | """ 118 | h = cell_geometry.dot_height 119 | r = cell_geometry.dot_diameter / 2 120 | return (r ** 2 + h ** 2) / 2 / h 121 | 122 | 123 | def get_base_plate_thickness(plate_thickness, cell_geometry): 124 | """Return the height on which the half spheres will sit""" 125 | return (plate_thickness + 126 | get_cylinder_radius(cell_geometry) - 127 | cell_geometry.dot_height) 128 | 129 | 130 | def make_base(text_lines, cell_geometry, plate_thickness): 131 | base_width = get_plate_width(text_lines, cell_geometry) 132 | base_height = get_plate_height(text_lines, cell_geometry) 133 | base_thickness = get_base_plate_thickness(plate_thickness, cell_geometry) 134 | base = cq.Workplane('XY').box(base_width, base_height, base_thickness, 135 | centered=(False, False, False)) 136 | return base 137 | 138 | 139 | def make_embossed_plate(text_lines, cell_geometry): 140 | """Make an embossed plate with dots as spherical caps 141 | 142 | Method: 143 | - make a thin plate, called base, on which sit cylinders 144 | - fillet the upper edge of the cylinders so to get pseudo half-spheres 145 | - make the union with a thicker plate so that only the sphere caps stay 146 | "visible". 147 | """ 148 | base = make_base(text_lines, cell_geometry, base_thickness) 149 | 150 | dot_pos = [] 151 | base_width = get_plate_width(text_lines, cell_geometry) 152 | base_height = get_plate_height(text_lines, cell_geometry) 153 | y = base_height - 3 * cell_geometry.vertical_interdot 154 | line_start_pos = Point(cell_geometry.horizontal_interdot, y) 155 | for text in text_lines: 156 | dots = braille_to_points(text, cell_geometry) 157 | dots = [p + line_start_pos for p in dots] 158 | dot_pos += dots 159 | line_start_pos += Point(0, -cell_geometry.interline) 160 | 161 | r = get_cylinder_radius(cell_geometry) 162 | base = base.faces('>Z').vertices('Z').edges() \ 167 | .fillet(r - 0.001) 168 | hidding_box = cq.Workplane('XY').box( 169 | base_width, base_height, base_thickness, centered=(False, False, False)) 170 | result = hidding_box.union(base) 171 | return result 172 | 173 | _cell_geometry = BrailleCellGeometry( 174 | horizontal_interdot, 175 | vertical_interdot, 176 | horizontal_intercell, 177 | vertical_interline, 178 | dot_height, 179 | dot_diameter) 180 | 181 | if base_thickness < get_cylinder_radius(_cell_geometry): 182 | raise ValueError('Base thickness should be at least {}'.format(dot_height)) 183 | 184 | show_object(make_embossed_plate(text_lines, _cell_geometry)) 185 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex030_Panel_with_Various_Holes_for_Connector_Installation.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # The dimensions of the model. These can be modified rather than changing the 4 | # object's code directly. 5 | width = 400 6 | height = 500 7 | thickness = 2 8 | 9 | # Create a plate with two polygons cut through it 10 | result = cq.Workplane("front").box(width, height, thickness) 11 | 12 | h_sep = 60 13 | for idx in range(4): 14 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(157,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll() 15 | 16 | for idx in range(4): 17 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(157,-30-idx*h_sep).moveTo(-16.65,0).circle(1.6).moveTo(16.65,0).circle(1.6).moveTo(-10.1889,-5.7).threePointArc((-12.59306,-4.70416),(-13.5889,-2.3)).lineTo(-14.4,2.3).threePointArc((-13.40416,4.70416),(-11,5.7)).lineTo(11,5.7).threePointArc((13.40416,4.70416),(14.4,2.3)).lineTo(13.5889,-2.3).threePointArc((12.59306,-4.70416),(10.1889,-5.7)).close().cutThruAll() 18 | 19 | h_sep4DB9 = 30 20 | for idx in range(8): 21 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(91,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll() 22 | 23 | for idx in range(4): 24 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(25,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll() 25 | 26 | for idx in range(4): 27 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(25,-30-idx*h_sep).moveTo(-16.65,0).circle(1.6).moveTo(16.65,0).circle(1.6).moveTo(-10.1889,-5.7).threePointArc((-12.59306,-4.70416),(-13.5889,-2.3)).lineTo(-14.4,2.3).threePointArc((-13.40416,4.70416),(-11,5.7)).lineTo(11,5.7).threePointArc((13.40416,4.70416),(14.4,2.3)).lineTo(13.5889,-2.3).threePointArc((12.59306,-4.70416),(10.1889,-5.7)).close().cutThruAll() 28 | 29 | for idx in range(8): 30 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-41,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll() 31 | 32 | for idx in range(4): 33 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-107,210-idx*h_sep).moveTo(-23.5,0).circle(1.6).moveTo(23.5,0).circle(1.6).moveTo(-17.038896,-5.7).threePointArc((-19.44306,-4.70416),(-20.438896,-2.3)).lineTo(-21.25,2.3).threePointArc((-20.25416,4.70416),(-17.85,5.7)).lineTo(17.85,5.7).threePointArc((20.25416,4.70416),(21.25,2.3)).lineTo(20.438896,-2.3).threePointArc((19.44306,-4.70416),(17.038896,-5.7)).close().cutThruAll() 34 | 35 | for idx in range(4): 36 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-107,-30-idx*h_sep).circle(14).rect(24.7487,24.7487, forConstruction=True).vertices().hole(3.2).cutThruAll() 37 | 38 | for idx in range(8): 39 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-173,225-idx*h_sep4DB9).moveTo(-12.5,0).circle(1.6).moveTo(12.5,0).circle(1.6).moveTo(-6.038896,-5.7).threePointArc((-8.44306,-4.70416),(-9.438896,-2.3)).lineTo(-10.25,2.3).threePointArc((-9.25416,4.70416),(-6.85,5.7)).lineTo(6.85,5.7).threePointArc((9.25416,4.70416),(10.25,2.3)).lineTo(9.438896,-2.3).threePointArc((8.44306,-4.70416),(6.038896,-5.7)).close().cutThruAll() 40 | 41 | for idx in range(4): 42 | result = result.workplane(offset=1, centerOption='CenterOfBoundBox').center(-173,-30-idx*h_sep).moveTo(-2.9176,-5.3).threePointArc((-6.05,0),(-2.9176,5.3)).lineTo(2.9176,5.3).threePointArc((6.05,0),(2.9176,-5.3)).close().cutThruAll() 43 | 44 | # Render the solid 45 | show_object(result) 46 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex031_Sweep.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # Points we will use to create spline and polyline paths to sweep over 4 | pts = [ 5 | (0, 1), 6 | (1, 2), 7 | (2, 4) 8 | ] 9 | 10 | # Spline path generated from our list of points (tuples) 11 | path = cq.Workplane("XZ").spline(pts) 12 | 13 | # Sweep a circle with a diameter of 1.0 units along the spline path we just created 14 | defaultSweep = cq.Workplane("XY").circle(1.0).sweep(path) 15 | 16 | # Sweep defaults to making a solid and not generating a Frenet solid. Setting Frenet to True helps prevent creep in 17 | # the orientation of the profile as it is being swept 18 | frenetShell = cq.Workplane("XY").circle(1.0).sweep(path, makeSolid=True, isFrenet=True) 19 | 20 | # We can sweep shapes other than circles 21 | defaultRect = cq.Workplane("XY").rect(1.0, 1.0).sweep(path) 22 | 23 | # Switch to a polyline path, but have it use the same points as the spline 24 | path = cq.Workplane("XZ").polyline(pts) 25 | 26 | # Using a polyline path leads to the resulting solid having segments rather than a single swept outer face 27 | plineSweep = cq.Workplane("XY").circle(1.0).sweep(path) 28 | 29 | # Switch to an arc for the path 30 | path = cq.Workplane("XZ").threePointArc((1.0, 1.5), (0.0, 1.0)) 31 | 32 | # Use a smaller circle section so that the resulting solid looks a little nicer 33 | arcSweep = cq.Workplane("XY").circle(0.5).sweep(path) 34 | 35 | # Translate the resulting solids so that they do not overlap and display them left to right 36 | show_object(defaultSweep) 37 | show_object(frenetShell.translate((5, 0, 0))) 38 | show_object(defaultRect.translate((10, 0, 0))) 39 | show_object(plineSweep.translate((15, 0, 0))) 40 | show_object(arcSweep.translate((20, 0, 0))) -------------------------------------------------------------------------------- /examples/FreeCAD/Ex032_3D_Printer_Extruder_Support.py: -------------------------------------------------------------------------------- 1 | # 3d printer for mounting hotend to X-carriage inspired by the P3steel Toolson 2 | # edition - http://www.thingiverse.com/thing:1054909 3 | import cadquery as cq 4 | 5 | 6 | def move_to_center(cqObject, shape): 7 | ''' 8 | Moves the origin of the current Workplane to the center of a given 9 | geometry object 10 | ''' 11 | 12 | # transform to workplane local coordinates 13 | shape_center = shape.Center().sub(cqObject.plane.origin) 14 | 15 | # project onto plane using dot product 16 | x_offset = shape_center.dot(cqObject.plane.xDir) 17 | y_offset = shape_center.dot(cqObject.plane.yDir) 18 | 19 | return cqObject.center(x_offset, y_offset) 20 | 21 | # Parameter definitions 22 | 23 | main_plate_size_y = 67 # size of the main plate in y direction 24 | main_plate_size_x = 50. # size of the main plate in x direction 25 | main_plate_thickness = 10. # thickness of the main plate 26 | 27 | wing_size_x = 10. # size of the side wing supporting the bridge in x direction 28 | wing_size_y = 10. # size of the side wing supporting the bridge in y direction 29 | 30 | bridge_depth = 35. # depth of the bridge 31 | 32 | support_depth = 18. # depth of the bridge support 33 | 34 | cutout_depth = 15. # depth of the hotend cutout 35 | cutout_rad = 8. # radius of the cutout (cf groove mount sizes of E3D hotends) 36 | cutout_offset = 2. # delta radius of the second cutout (cf groove mount sizes of E3D hotends) 37 | 38 | extruder_hole_spacing = 50. # spacing of the extruder mounting holes (Wade's geared extruder) 39 | 40 | m4_predrill = 3.7 # hole diameter for m4 tapping 41 | m3_predrill = 2.5 # hole diameter for m3 tapping 42 | m3_cbore = 5. # counterbore size for m3 socket screw 43 | 44 | mounting_hole_spacing = 28. # spacing of the mounting holes for attaching to x-carriage 45 | 46 | aux_hole_depth = 6. # depth of the auxiliary holes at the sides of the object 47 | aux_hole_spacing = 5. # spacing of the auxiliary holes within a group 48 | aux_hole_N = 2 # number of the auxiliary hole per group 49 | 50 | # make the main plate 51 | res = cq.Workplane('front').box(main_plate_size_x, 52 | main_plate_size_y, 53 | main_plate_thickness) 54 | 55 | 56 | def add_wing(obj, sign=1): 57 | ''' 58 | Adds a wing to the main plate, defined to keep the code DRY 59 | ''' 60 | obj = obj.workplane()\ 61 | .hLine(sign*wing_size_x)\ 62 | .vLine(-wing_size_y)\ 63 | .line(-sign*wing_size_x, -2*wing_size_y)\ 64 | .close().extrude(main_plate_thickness) 65 | return obj 66 | 67 | # add wings 68 | 69 | # add right wing 70 | res = res.faces('XY') 71 | res = add_wing(res) 72 | 73 | # store sides of the plate for further reuse, their area is used later on to calculate "optimum" spacing of the aux hole groups 74 | face_right = res.faces('>X[1]').val() 75 | face_left = res.faces('>X[-2]').val() 76 | 77 | # add left wing 78 | res = res.faces('Y').vertices('Z') # select top face 83 | e = wp.edges('>Y') # select most extreme edge in Y direction 84 | 85 | bridge_length = e.val().Length() # the width of the bridge equals to the length of the selected edge 86 | 87 | # draw the bridge x-section and extrude 88 | res = e.vertices('Z[1]') # take all faces in Z direction and select the middle one; note the new selector syntax 96 | edge = faces.edges('>Y') # select the top edge of this face... 97 | res = move_to_center(faces.workplane(), edge.val()).\ 98 | transformed(rotate=(0, 90, 0)) # ...and make a workplane that is centered in this edge and oriented along X direction 99 | 100 | res = res.vLine(-support_depth).\ 101 | line(-support_depth, support_depth).\ 102 | close() # draw a triangle 103 | 104 | res = res.extrude(main_plate_size_x/2, both=True, clean=True) # extrude the triangle, now the bridge has a nice support making it much more stiff 105 | 106 | # Start cutting out a slot for hotend mounting 107 | face = res.faces('>Y') # select the most extreme face in Y direction, i.e. top of the "bridge" 108 | res = move_to_center(face.workplane(), face.edges('>Z').val()) # shift the workplane to the center of the most extreme edge of the bridge 109 | 110 | 111 | def make_slot(obj, depth=None): 112 | ''' 113 | Utility function that makes a slot for hotend mounting 114 | ''' 115 | obj = obj.moveTo(cutout_rad, -cutout_depth).\ 116 | threePointArc((0, -cutout_depth-cutout_rad), 117 | (-cutout_rad, -cutout_depth)).\ 118 | vLineTo(0).hLineTo(cutout_rad).close() 119 | 120 | if depth is None: 121 | obj = obj.cutThruAll() 122 | else: 123 | obj = obj.cutBlind(depth) 124 | 125 | return obj 126 | 127 | res = make_slot(res, None) # make the smaller slot 128 | 129 | cutout_rad += cutout_offset # increase the cutout radius... 130 | res = make_slot(res.end().end(), -main_plate_thickness/2) # ...and make a slightly larger slot 131 | 132 | res = res.end().moveTo(0, 0) \ 133 | .pushPoints([(-extruder_hole_spacing/2, -cutout_depth), (extruder_hole_spacing/2, -cutout_depth)]) \ 134 | .hole(m4_predrill) # add extruder mounting holes at the top of the bridge 135 | 136 | 137 | # make additional slot in the bridge support which allows the hotend's radiator to fit 138 | cutout_rad += 3*cutout_offset 139 | res = make_slot(res.end().moveTo(0, 0).workplane(offset=-main_plate_thickness)) 140 | 141 | # add reinforcement holes 142 | cutout_rad -= 2*cutout_offset 143 | res = res.faces('>Z').workplane().\ 144 | pushPoints([(-cutout_rad, -main_plate_thickness/4), 145 | (cutout_rad, -main_plate_thickness/4)]).\ 146 | hole(m3_predrill) 147 | 148 | # add aux holes on the front face 149 | res = res.moveTo(-main_plate_size_x/2., 0).workplane().rarray(aux_hole_spacing, 1, aux_hole_N, 1) \ 150 | .hole(m3_predrill, depth=aux_hole_depth) 151 | res = res.moveTo(main_plate_size_x, 0).workplane().rarray(aux_hole_spacing, 1, aux_hole_N, 1) \ 152 | .hole(m3_predrill, depth=aux_hole_depth) 153 | 154 | # make a hexagonal cutout 155 | res = res.faces('>Z[1]') 156 | res = res.workplane(offset=bridge_depth). \ 157 | transformed(rotate=(0, 0, 90)). \ 158 | polygon(6, 30).cutThruAll() 159 | 160 | # make 4 mounting holes with cbores 161 | res = res.end().moveTo(0, 0). \ 162 | rect(mounting_hole_spacing, 163 | mounting_hole_spacing, forConstruction=True) 164 | 165 | res = res.vertices(). \ 166 | cboreHole(m3_predrill, 167 | m3_cbore, 168 | bridge_depth+m3_cbore/2) 169 | 170 | # make cutout and holes for mounting of the fan 171 | res = res.transformed(rotate=(0, 0, 45)). \ 172 | rect(35, 35).cutBlind(-bridge_depth).end(). \ 173 | rect(25, 25, forConstruction=True).vertices().hole(m3_predrill) 174 | 175 | 176 | def make_aux_holes(workplane, holes_span, N_hole_groups=3): 177 | ''' 178 | Utility function for creation of auxiliary mouting holes at the sides of the object 179 | ''' 180 | res = workplane.moveTo(-holes_span/2).workplane().rarray(aux_hole_spacing, 1, aux_hole_N, 1) \ 181 | .hole(m3_predrill, depth=aux_hole_depth) 182 | for i in range(N_hole_groups-1): 183 | res = res.moveTo(holes_span/(N_hole_groups-1.)).workplane().rarray(aux_hole_spacing, 1, aux_hole_N, 1) \ 184 | .hole(m3_predrill, depth=aux_hole_depth) 185 | 186 | return res 187 | 188 | # make aux holes at the bottom 189 | res = res.faces('X').workplane().transformed((90, 0, 0)) 196 | res = make_aux_holes(res, main_plate_size_x*2/3., 3) 197 | 198 | # make aux holes at the side (@main plate) 199 | res = res.faces('|X').edges('X') 200 | res = res.workplane() 201 | res = move_to_center(res, face_right) 202 | res = res.transformed((90, 0, 0)) 203 | hole_sep = 0.5*face_right.Area()/main_plate_thickness 204 | res = make_aux_holes(res, hole_sep, 2) 205 | 206 | # make aux holes at the side (@main plate) 207 | res = res.faces('|X').edges('Z").shell(-0.2).\ 7 | faces(">Z").edges("not(X or Y)").\ 8 | chamfer(0.125) 9 | 10 | show_object(result) 11 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex034_Sweep_Along_List_Of_Wires.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | 3 | # X axis line length 20.0 4 | path = cq.Workplane("XZ").moveTo(-10, 0).lineTo(10, 0) 5 | 6 | # Sweep a circle from diameter 2.0 to diameter 1.0 to diameter 2.0 along X axis length 10.0 + 10.0 7 | defaultSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(2.0). \ 8 | workplane(offset=10.0).circle(1.0). \ 9 | workplane(offset=10.0).circle(2.0).sweep(path, sweepAlongWires=True) 10 | 11 | # We can sweep thrue different shapes 12 | recttocircleSweep = cq.Workplane("YZ").workplane(offset=-10.0).rect(2.0, 2.0). \ 13 | workplane(offset=8.0).circle(1.0).workplane(offset=4.0).circle(1.0). \ 14 | workplane(offset=8.0).rect(2.0, 2.0).sweep(path, sweepAlongWires=True) 15 | 16 | circletorectSweep = cq.Workplane("YZ").workplane(offset=-10.0).circle(1.0). \ 17 | workplane(offset=7.0).rect(2.0, 2.0).workplane(offset=6.0).rect(2.0, 2.0). \ 18 | workplane(offset=7.0).circle(1.0).sweep(path, sweepAlongWires=True) 19 | 20 | 21 | # Placement of the Shape is important otherwise could produce unexpected shape 22 | specialSweep = cq.Workplane("YZ").circle(1.0).workplane(offset=10.0).rect(2.0, 2.0). \ 23 | sweep(path, sweepAlongWires=True) 24 | 25 | # Switch to an arc for the path : line l=5.0 then half circle r=4.0 then line l=5.0 26 | path = cq.Workplane("XZ").moveTo(-5, 4).lineTo(0, 4). \ 27 | threePointArc((4, 0), (0, -4)).lineTo(-5, -4) 28 | 29 | # Placement of different shapes should follow the path 30 | # cylinder r=1.5 along first line 31 | # then sweep allong arc from r=1.5 to r=1.0 32 | # then cylinder r=1.0 along last line 33 | arcSweep = cq.Workplane("YZ").workplane(offset=-5).moveTo(0, 4).circle(1.5). \ 34 | workplane(offset=5).circle(1.5). \ 35 | moveTo(0, -8).circle(1.0). \ 36 | workplane(offset=-5).circle(1.0). \ 37 | sweep(path, sweepAlongWires=True) 38 | 39 | 40 | # Translate the resulting solids so that they do not overlap and display them left to right 41 | show_object(defaultSweep) 42 | show_object(circletorectSweep.translate((0, 5, 0))) 43 | show_object(recttocircleSweep.translate((0, 10, 0))) 44 | show_object(specialSweep.translate((0, 15, 0))) 45 | show_object(arcSweep.translate((0, -5, 0))) 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/FreeCAD/Ex035_Reinforce_Junction_UsingFillet.py: -------------------------------------------------------------------------------- 1 | import cadquery as cq 2 | from cadquery import selectors 3 | 4 | # This exemple demonstrates the use of a fillet to reinforce a junction between two parts. 5 | # It relies on the selection of an edge of the weak junction, and the use of fillet. 6 | 7 | # 1 - The construction of the model : a pipe connector 8 | # In that model, the junction surface between the box and the cylinder is small. 9 | # This makes the junction between the two too weak. 10 | model = cq.Workplane("XY").box(15.0, 15.0, 2.0)\ 11 | .faces(">Z").rect(10.0, 10.0, forConstruction=True)\ 12 | .vertices().cskHole(2.0, 4.0, 82)\ 13 | .faces(">Z").circle(4.0).extrude(10.0)\ 14 | .faces(">Z").hole(6) 15 | 16 | # 2 - Reinforcement of the junction 17 | # Two steps here : 18 | # - select the edge to reinforce. Here we search the closest edge from the center on the top face of the box. 19 | # - apply a fillet or a chamfer to that edge 20 | result = model.faces(' -1 ) 30 | else: 31 | s = io.StringIO() 32 | exporters.exportShape(p,eType,s,0.1) 33 | result = s.getvalue() 34 | for q in stringsToFind: 35 | self.assertTrue(q in result) 36 | 37 | return result 38 | 39 | def testSTL(self): 40 | self._exportBox(exporters.ExportTypes.STL,['facet normal']) 41 | 42 | def testSVG(self): 43 | self._exportBox(exporters.ExportTypes.SVG,['']) 47 | 48 | def testSTEP(self): 49 | self._exportBox(exporters.ExportTypes.STEP,['FILE_SCHEMA']) 50 | 51 | def testTJS(self): 52 | self._exportBox(exporters.ExportTypes.TJS,['vertices','formatVersion','faces']) 53 | -------------------------------------------------------------------------------- /tests/TestImporters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests file importers such as STEP 3 | """ 4 | #core modules 5 | import tempfile 6 | 7 | from cadquery import * 8 | from cadquery import exporters 9 | from cadquery import importers 10 | from cadquery.freecad_impl import suppress_stdout_stderr 11 | from tests import BaseTest 12 | 13 | #where unit test output will be saved 14 | OUTDIR = tempfile.gettempdir() 15 | 16 | class TestImporters(BaseTest): 17 | def importBox(self, importType, fileName): 18 | """ 19 | Exports a simple box to a STEP file and then imports it again 20 | :param importType: The type of file we're importing (STEP, STL, etc) 21 | :param fileName: The path and name of the file to write to 22 | """ 23 | #We're importing a STEP file 24 | if importType == importers.ImportTypes.STEP: 25 | #We first need to build a simple shape to export 26 | shape = Workplane("XY").box(1, 2, 3).val() 27 | 28 | #Export the shape to a temporary file 29 | shape.exportStep(fileName) 30 | 31 | # Reimport the shape from the new STEP file 32 | importedShape = importers.importShape(importType,fileName) 33 | 34 | # Check to make sure we got a solid back 35 | self.assertTrue(importedShape.val().ShapeType() == "Solid") 36 | 37 | # Check the number of faces and vertices per face to make sure we have a box shape 38 | self.assertTrue(importedShape.faces("+X").size() == 1 and importedShape.faces("+X").vertices().size() == 4) 39 | self.assertTrue(importedShape.faces("+Y").size() == 1 and importedShape.faces("+Y").vertices().size() == 4) 40 | self.assertTrue(importedShape.faces("+Z").size() == 1 and importedShape.faces("+Z").vertices().size() == 4) 41 | 42 | def testSTEP(self): 43 | """ 44 | Tests STEP file import 45 | """ 46 | with suppress_stdout_stderr(): 47 | self.importBox(importers.ImportTypes.STEP, OUTDIR + "/tempSTEP.step") 48 | 49 | def testSTEPFromURL(self): 50 | """ 51 | Tests STEP file import from a URL 52 | """ 53 | stepURL = "https://raw.githubusercontent.com/dcowden/cadquery/master/doc/_static/box_export.step" 54 | 55 | importedShape = importers.importStepFromURL(stepURL) 56 | 57 | # Check to make sure we got a solid back 58 | self.assertTrue(importedShape.val().ShapeType() == "Solid") 59 | 60 | # Check the number of faces and vertices per face to make sure we have a box shape 61 | self.assertTrue(importedShape.faces("+X").size() == 1 and importedShape.faces("+X").vertices().size() == 4) 62 | self.assertTrue(importedShape.faces("+Y").size() == 1 and importedShape.faces("+Y").vertices().size() == 4) 63 | self.assertTrue(importedShape.faces("+Z").size() == 1 and importedShape.faces("+Z").vertices().size() == 4) 64 | 65 | if __name__ == '__main__': 66 | import unittest 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /tests/TestLogging.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | from copy import copy 4 | from tests import BaseTest 5 | 6 | import logging 7 | 8 | # Units under test 9 | import cadquery 10 | from cadquery.freecad_impl import console_logging 11 | 12 | 13 | class TestLogging(BaseTest): 14 | def setUp(self): 15 | # save root logger's state 16 | root_logger = logging.getLogger() 17 | self._initial_level = root_logger.level 18 | self._initial_logging_handlers = copy(root_logger.handlers) 19 | 20 | def tearDown(self): 21 | # forcefully re-establish original log state 22 | root_logger = logging.getLogger() 23 | root_logger.level = self._initial_level 24 | root_logger.handlers = self._initial_logging_handlers 25 | # reset console_logging's global state 26 | cadquery.freecad_impl.console_logging._logging_handler = None 27 | 28 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 29 | def testConsoleMessage(self, mock_freecad): 30 | console_logging.enable() 31 | log = logging.getLogger('test') 32 | 33 | log.info('foo') 34 | mock_freecad.Console.PrintMessage.assert_called_once_with('foo\n') 35 | mock_freecad.Console.PrintWarning.assert_not_called() 36 | mock_freecad.Console.PrintError.assert_not_called() 37 | 38 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 39 | def testConsoleWarning(self, mock_freecad): 40 | console_logging.enable() 41 | log = logging.getLogger('test') 42 | 43 | log.warning('bar') 44 | mock_freecad.Console.PrintMessage.assert_not_called() 45 | mock_freecad.Console.PrintWarning.assert_called_once_with('bar\n') 46 | mock_freecad.Console.PrintError.assert_not_called() 47 | 48 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 49 | def testConsoleError(self, mock_freecad): 50 | console_logging.enable() 51 | log = logging.getLogger('test') 52 | 53 | log.error('roo') 54 | mock_freecad.Console.PrintMessage.assert_not_called() 55 | mock_freecad.Console.PrintWarning.assert_not_called() 56 | mock_freecad.Console.PrintError.assert_called_once_with('roo\n') 57 | 58 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 59 | def testConsoleDebugOffDefault(self, mock_freecad): 60 | console_logging.enable() 61 | log = logging.getLogger('test') 62 | 63 | log.debug('no show') 64 | mock_freecad.Console.PrintMessage.assert_not_called() 65 | mock_freecad.Console.PrintWarning.assert_not_called() 66 | mock_freecad.Console.PrintError.assert_not_called() 67 | 68 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 69 | def testConsoleSetLevelDebug(self, mock_freecad): 70 | console_logging.enable(level=logging.DEBUG) 71 | log = logging.getLogger('test') 72 | 73 | log.debug('now showing') 74 | mock_freecad.Console.PrintMessage.assert_called_once_with('now showing\n') 75 | 76 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 77 | def testConsoleSetLevelWarning(self, mock_freecad): 78 | console_logging.enable(level=logging.WARNING) 79 | log = logging.getLogger('test') 80 | 81 | log.info('no show') 82 | log.warning('be warned') 83 | mock_freecad.Console.PrintMessage.assert_not_called() 84 | mock_freecad.Console.PrintWarning.assert_called_once_with('be warned\n') 85 | 86 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 87 | def testConsoleLogFormat(self, mock_freecad): 88 | console_logging.enable(format=">> %(message)s <<") 89 | log = logging.getLogger('test') 90 | 91 | log.info('behold brackets!') 92 | mock_freecad.Console.PrintMessage.assert_called_once_with('>> behold brackets! <<\n') 93 | 94 | @mock.patch('cadquery.freecad_impl.console_logging.FreeCAD') 95 | def testConsoleEnableDisable(self, mock_freecad): 96 | console_logging.enable() 97 | console_logging.disable() 98 | log = logging.getLogger('test') 99 | 100 | log.error('nope, disabled') 101 | mock_freecad.Console.PrintError.assert_not_called() 102 | -------------------------------------------------------------------------------- /tests/TestWorkplanes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests basic workplane functionality 3 | """ 4 | #core modules 5 | 6 | #my modules 7 | from cadquery import * 8 | from tests import BaseTest,toTuple 9 | 10 | xAxis_ = Vector(1, 0, 0) 11 | yAxis_ = Vector(0, 1, 0) 12 | zAxis_ = Vector(0, 0, 1) 13 | xInvAxis_ = Vector(-1, 0, 0) 14 | yInvAxis_ = Vector(0, -1, 0) 15 | zInvAxis_ = Vector(0, 0, -1) 16 | 17 | class TestWorkplanes(BaseTest): 18 | 19 | def testYZPlaneOrigins(self): 20 | #xy plane-- with origin at x=0.25 21 | base = Vector(0.25,0,0) 22 | p = Plane(base, Vector(0,1,0), Vector(1,0,0)) 23 | 24 | #origin is always (0,0,0) in local coordinates 25 | self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) 26 | 27 | #(0,0,0) is always the original base in global coordinates 28 | self.assertTupleAlmostEquals(base.toTuple(), p.toWorldCoords((0,0)).toTuple() ,2 ) 29 | 30 | def testXYPlaneOrigins(self): 31 | base = Vector(0,0,0.25) 32 | p = Plane(base, Vector(1,0,0), Vector(0,0,1)) 33 | 34 | #origin is always (0,0,0) in local coordinates 35 | self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) 36 | 37 | #(0,0,0) is always the original base in global coordinates 38 | self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 ) 39 | 40 | def testXZPlaneOrigins(self): 41 | base = Vector(0,0.25,0) 42 | p = Plane(base, Vector(0,0,1), Vector(0,1,0)) 43 | 44 | #(0,0,0) is always the original base in global coordinates 45 | self.assertTupleAlmostEquals(toTuple(base), p.toWorldCoords((0,0)).toTuple() ,2 ) 46 | 47 | #origin is always (0,0,0) in local coordinates 48 | self.assertTupleAlmostEquals((0,0,0), p.toLocalCoords(p.origin).toTuple() ,2 ) 49 | 50 | def testPlaneBasics(self): 51 | p = Plane.XY() 52 | #local to world 53 | self.assertTupleAlmostEquals((1.0,1.0,0),p.toWorldCoords((1,1)).toTuple(),2 ) 54 | self.assertTupleAlmostEquals((-1.0,-1.0,0), p.toWorldCoords((-1,-1)).toTuple(),2 ) 55 | 56 | #world to local 57 | self.assertTupleAlmostEquals((-1.0,-1.0), p.toLocalCoords(Vector(-1,-1,0)).toTuple() ,2 ) 58 | self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,1,0)).toTuple() ,2 ) 59 | 60 | p = Plane.YZ() 61 | self.assertTupleAlmostEquals((0,1.0,1.0),p.toWorldCoords((1,1)).toTuple() ,2 ) 62 | 63 | #world to local 64 | self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(0,1,1)).toTuple() ,2 ) 65 | 66 | p = Plane.XZ() 67 | r = p.toWorldCoords((1,1)).toTuple() 68 | self.assertTupleAlmostEquals((1.0,0.0,1.0),r ,2 ) 69 | 70 | #world to local 71 | self.assertTupleAlmostEquals((1.0,1.0), p.toLocalCoords(Vector(1,0,1)).toTuple() ,2 ) 72 | 73 | def testOffsetPlanes(self): 74 | "Tests that a plane offset from the origin works ok too" 75 | p = Plane.XY(origin=(10.0,10.0,0)) 76 | 77 | 78 | self.assertTupleAlmostEquals((11.0,11.0,0.0),p.toWorldCoords((1.0,1.0)).toTuple(),2 ) 79 | self.assertTupleAlmostEquals((2.0,2.0), p.toLocalCoords(Vector(12.0,12.0,0)).toTuple() ,2 ) 80 | 81 | #TODO test these offsets in the other dimensions too 82 | p = Plane.YZ(origin=(0,2,2)) 83 | self.assertTupleAlmostEquals((0.0,5.0,5.0), p.toWorldCoords((3.0,3.0)).toTuple() ,2 ) 84 | self.assertTupleAlmostEquals((10,10.0,0.0), p.toLocalCoords(Vector(0.0,12.0,12.0)).toTuple() ,2 ) 85 | 86 | p = Plane.XZ(origin=(2,0,2)) 87 | r = p.toWorldCoords((1.0,1.0)).toTuple() 88 | self.assertTupleAlmostEquals((3.0,0.0,3.0),r ,2 ) 89 | self.assertTupleAlmostEquals((10.0,10.0), p.toLocalCoords(Vector(12.0,0.0,12.0)).toTuple() ,2 ) 90 | 91 | def testXYPlaneBasics(self): 92 | p = Plane.named('XY') 93 | self.assertTupleAlmostEquals(p.zDir.toTuple(), zAxis_.toTuple(), 4) 94 | self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4) 95 | self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4) 96 | 97 | def testYZPlaneBasics(self): 98 | p = Plane.named('YZ') 99 | self.assertTupleAlmostEquals(p.zDir.toTuple(), xAxis_.toTuple(), 4) 100 | self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4) 101 | self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4) 102 | 103 | def testZXPlaneBasics(self): 104 | p = Plane.named('ZX') 105 | self.assertTupleAlmostEquals(p.zDir.toTuple(), yAxis_.toTuple(), 4) 106 | self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4) 107 | self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4) 108 | 109 | def testXZPlaneBasics(self): 110 | p = Plane.named('XZ') 111 | self.assertTupleAlmostEquals(p.zDir.toTuple(), yInvAxis_.toTuple(), 4) 112 | self.assertTupleAlmostEquals(p.xDir.toTuple(), xAxis_.toTuple(), 4) 113 | self.assertTupleAlmostEquals(p.yDir.toTuple(), zAxis_.toTuple(), 4) 114 | 115 | def testYXPlaneBasics(self): 116 | p = Plane.named('YX') 117 | self.assertTupleAlmostEquals(p.zDir.toTuple(), zInvAxis_.toTuple(), 4) 118 | self.assertTupleAlmostEquals(p.xDir.toTuple(), yAxis_.toTuple(), 4) 119 | self.assertTupleAlmostEquals(p.yDir.toTuple(), xAxis_.toTuple(), 4) 120 | 121 | def testZYPlaneBasics(self): 122 | p = Plane.named('ZY') 123 | self.assertTupleAlmostEquals(p.zDir.toTuple(), xInvAxis_.toTuple(), 4) 124 | self.assertTupleAlmostEquals(p.xDir.toTuple(), zAxis_.toTuple(), 4) 125 | self.assertTupleAlmostEquals(p.yDir.toTuple(), yAxis_.toTuple(), 4) 126 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from cadquery import * 2 | import unittest 3 | import sys 4 | import os 5 | from contextlib import contextmanager 6 | 7 | import FreeCAD 8 | 9 | import Part as P 10 | from FreeCAD import Vector as V 11 | 12 | 13 | def readFileAsString(fileName): 14 | f= open(fileName, 'r') 15 | s = f.read() 16 | f.close() 17 | return s 18 | 19 | 20 | def writeStringToFile(strToWrite, fileName): 21 | f = open(fileName, 'w') 22 | f.write(strToWrite) 23 | f.close() 24 | 25 | 26 | def makeUnitSquareWire(): 27 | return Solid.cast(P.makePolygon([V(0, 0, 0), V(1, 0, 0), V(1, 1, 0), V(0, 1, 0), V(0, 0, 0)])) 28 | 29 | 30 | def makeUnitCube(): 31 | return makeCube(1.0) 32 | 33 | 34 | def makeCube(size): 35 | return Solid.makeBox(size, size, size) 36 | 37 | 38 | def toTuple(v): 39 | """convert a vector or a vertex to a 3-tuple: x,y,z""" 40 | pnt = v 41 | if type(v) == FreeCAD.Base.Vector: 42 | return (v.Point.x, v.Point.y, v.Point.z) 43 | elif type(v) == Vector: 44 | return v.toTuple() 45 | else: 46 | raise RuntimeError("don't know how to convert type %s to tuple" % str(type(v)) ) 47 | 48 | 49 | class BaseTest(unittest.TestCase): 50 | 51 | def assertTupleAlmostEquals(self, expected, actual, places=7): 52 | for i, j in zip(actual, expected): 53 | self.assertAlmostEqual(i, j, places) 54 | 55 | 56 | __all__ = [ 57 | 'TestCQGI', 58 | 'TestCQSelectors', 59 | 'TestCQSelectors', 60 | 'TestCadObjects', 61 | 'TestCadQuery', 62 | 'TestExporters', 63 | 'TestImporters', 64 | 'TestLogging', 65 | 'TestWorkplanes', 66 | ] 67 | --------------------------------------------------------------------------------