Added the CadQuery library back as just a directory.
1
Libs/cadquery/.coverage
Normal file
7
Libs/cadquery/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
build/
|
||||
*.pyc
|
||||
doc/_build/*
|
||||
dist/*
|
||||
.idea/*
|
||||
cadquery.egg-info
|
||||
target/*
|
32
Libs/cadquery/.travis.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
language: python
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:freecad-maintainers/freecad-stable
|
||||
- sudo apt-get update -qq
|
||||
install:
|
||||
- sudo apt-get install -y freecad freecad-doc
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
- python ./setup.py install
|
||||
- pip install -r requirements-dev.txt
|
||||
- pip install travis-sphinx
|
||||
|
||||
script:
|
||||
- coverage run --source=cadquery ./runtests.py
|
||||
- travis-sphinx --nowarn --source=doc build
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
- travis-sphinx deploy
|
||||
|
||||
branches:
|
||||
except:
|
||||
- pythonocc
|
||||
- 2_0_branch
|
||||
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: dcowden
|
||||
password:
|
||||
secure: aP02wBbry1j3hYG/w++siF1lk26teuRQlPAx1c+ec8fxUw+bECa2HbPQHcIvSXB5N6nc6P3L9LjHt9ktm+Dn6FLJu3qWYNGAZx9PTn24ug0iAmB+JyNrsET3nK6WUKR1XpBqvjKgdpukd1Hknh2FSzYoyUvFWH9/CovITCFN3jo=
|
||||
on:
|
||||
tags: true
|
5
Libs/cadquery/AUTHORS.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Core CQ Developers
|
||||
|
||||
* [Dave Cowden](https://github.com/dcowden), Creator
|
||||
* [Adam Urbanczyk](https://github.com/adam-urbanczyk)
|
||||
* [Jeremy Wright](https://github.com/jmwright)
|
50
Libs/cadquery/Dockerfile
Normal file
|
@ -0,0 +1,50 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
MAINTAINER <dave.cowden@gmail.com>
|
||||
|
||||
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
|
||||
|
||||
ENV CQ_HOME=/opt/cadquery
|
||||
|
||||
#from continuum: https://hub.docker.com/r/continuumio/anaconda/~/dockerfile/
|
||||
RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates \
|
||||
libglib2.0-0 libxext6 libsm6 libxrender1 \
|
||||
git mercurial subversion
|
||||
|
||||
RUN echo 'export PATH=/opt/conda/bin:$PATH' > /etc/profile.d/conda.sh && \
|
||||
wget --quiet https://repo.continuum.io/archive/Anaconda2-5.0.0-Linux-x86_64.sh -O ~/anaconda.sh && \
|
||||
/bin/bash ~/anaconda.sh -b -p /opt/conda && \
|
||||
rm ~/anaconda.sh
|
||||
|
||||
RUN apt-get install -y curl grep sed dpkg && \
|
||||
TINI_VERSION=`curl https://github.com/krallin/tini/releases/latest | grep -o "/v.*\"" | sed 's:^..\(.*\).$:\1:'` && \
|
||||
curl -L "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini_${TINI_VERSION}.deb" > tini.deb && \
|
||||
dpkg -i tini.deb && \
|
||||
rm tini.deb && \
|
||||
apt-get clean
|
||||
|
||||
ENV PATH /opt/conda/bin:$PATH
|
||||
|
||||
RUN apt-get install -y software-properties-common
|
||||
|
||||
RUN add-apt-repository -y ppa:freecad-maintainers/freecad-stable && \
|
||||
apt-get update && apt-get install -y freecad
|
||||
|
||||
RUN mkdir -p $CQ_HOME
|
||||
RUN mkdir -p $CQ_HOME/build_data
|
||||
VOLUME $CQ_HOME/build_data
|
||||
|
||||
COPY requirements-dev.txt runtests.py cq_cmd.py cq_cmd.sh setup.py README.md MANIFEST setup.cfg $CQ_HOME/
|
||||
COPY cadquery $CQ_HOME/cadquery
|
||||
COPY examples $CQ_HOME/examples
|
||||
COPY tests $CQ_HOME/tests
|
||||
|
||||
|
||||
RUN pip install -r /opt/cadquery/requirements-dev.txt
|
||||
RUN cd $CQ_HOME && python ./setup.py install
|
||||
RUN chmod +x $CQ_HOME/cq_cmd.sh
|
||||
RUN useradd -ms /bin/bash cq
|
||||
USER cq
|
||||
WORKDIR /home/cq
|
||||
|
||||
ENTRYPOINT [ "/opt/cadquery/cq_cmd.sh" ]
|
208
Libs/cadquery/LICENSE
Normal file
|
@ -0,0 +1,208 @@
|
|||
CadQuery
|
||||
Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the Apache Public License, v 2.0
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [Parametric Products Intellectual Holdings, LLC]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
24
Libs/cadquery/MANIFEST
Normal file
|
@ -0,0 +1,24 @@
|
|||
README.txt
|
||||
README.md
|
||||
setup.cfg
|
||||
setup.py
|
||||
cadquery\cq.py
|
||||
cadquery\__init__.py
|
||||
cadquery\cq_directive.py
|
||||
cadquery\selectors.py
|
||||
cadquery\cqgi.py
|
||||
cadquery\contrib\__init__.py
|
||||
cadquery\freecad_impl\__init__.py
|
||||
cadquery\freecad_impl\exporters.py
|
||||
cadquery\freecad_impl\importers.py
|
||||
cadquery\freecad_impl\geom.py
|
||||
cadquery\freecad_impl\shapes.py
|
||||
cadquery\plugins\__init__.py
|
||||
tests\TestCQSelectors.py
|
||||
tests\TestCadObjects.py
|
||||
tests\TestCadQuery.py
|
||||
tests\TestExporters.py
|
||||
tests\TestImporters.py
|
||||
tests\TestWorkplanes.py
|
||||
tests\TestCQGI.py
|
||||
tests\__init__.py
|
1
Libs/cadquery/MANIFEST.in
Normal file
|
@ -0,0 +1 @@
|
|||
include README.md
|
302
Libs/cadquery/README.md
Normal file
|
@ -0,0 +1,302 @@
|
|||
What is a CadQuery?
|
||||
========================================
|
||||
|
||||
[](https://travis-ci.org/dcowden/cadquery?branch=master)
|
||||
[](https://coveralls.io/r/dcowden/cadquery)
|
||||
[](https://github.com/dcowden/cadquery/releases/tag/v0.3.0)
|
||||
[](https://github.com/dcowden/cadquery/blob/master/LICENSE)
|
||||
|
||||
CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
|
||||
|
||||
CadQuery has several goals:
|
||||
|
||||
* Build lD models with scripts that are as close as possible to how you'd describe the object to a human.
|
||||
* Create parametric models that can be very easily customized by end users
|
||||
* Output high quality (loss-less) CAD formats like STEP and AMF in addition to traditional STL
|
||||
* Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
|
||||
|
||||
Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized.
|
||||
|
||||
Getting Started with the docker image
|
||||
=======================================
|
||||
The caduery docker image (https://hub.docker.com/r/dcowden/cadquery/) includes cadquery and all of its dependencies. It can be used to run cadquery scripts without any installation required ( other than docker, of course)
|
||||
|
||||
Examples:
|
||||
|
||||
Display the Documentation::
|
||||
|
||||
docker run dcowden/cadquery:latest
|
||||
|
||||
Build a local model using stdin/stdout::
|
||||
|
||||
cat Ex001_Simple_Block.py | docker run -i dcowden/cadquery:latest build --in_spec stdin --format STEP --out_spec stdout
|
||||
|
||||
... STEP output on the console
|
||||
|
||||
Build local models and output to the same directory::
|
||||
|
||||
docker run -v $PWD:/home/cq -i dcowden/cadquery:latest build --in_spec Ex001_Simple_Block.py --format STEP
|
||||
INFO: Reading from file 'Ex001_Simple_Block.py'
|
||||
INFO: Parsed Script 'Ex001_Simple_Block.py'.
|
||||
INFO: This script provides parameters length,thickness,height, which can be customized at build time.
|
||||
INFO: The script will run with default variable values
|
||||
INFO: use --param_file to provide a json file that contains values to override the defaults
|
||||
INFO: Output Format is 'STEP'. Use --output-format to change it.
|
||||
INFO: Output Path is './cqobject-%(counter)d.%(format)s'. Use --out_spec to change it.
|
||||
INFO: Script Generated 1 result Objects
|
||||
INFO: Writing STEP Output to './cqobject-1.STEP'
|
||||
|
||||
|
||||
|
||||
Full Documentation
|
||||
============================
|
||||
You can find the full cadquery documentation at http://dcowden.github.io/cadquery
|
||||
|
||||
Getting Started With CadQuery
|
||||
========================================
|
||||
|
||||
The easiest way to get started with CadQuery is to Install FreeCAD (version 16+) (http://www.freecadweb.org/), and then to use our great CadQuery-FreeCAD plugin here: https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
|
||||
It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
|
||||
|
||||
It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
|
||||
|
||||
We also have a Google Group to make it easy to get help from other CadQuery users. Please join the group and introduce yourself, and we would also love to hear what you are doing with CadQuery. https://groups.google.com/forum/#!forum/cadquery
|
||||
|
||||
Examples
|
||||
======================
|
||||
|
||||
This resin mold was modeled using cadquery and then created on a CNC machine:
|
||||
|
||||
<p align="center">
|
||||
<img src="doc/_static/hyOzd-cablefix.png" width="350"/>
|
||||
<img src="doc/_static/hyOzd-finished.jpg" width="350"/>
|
||||
</p>
|
||||
|
||||
The cadquery script is surprisingly short, and allows easily customizing any of the variables::
|
||||
|
||||
```python
|
||||
import cadquery as cq
|
||||
from Helpers import show
|
||||
BS = cq.selectors.BoxSelector
|
||||
|
||||
# PARAMETERS
|
||||
mount_holes = True
|
||||
|
||||
# mold size
|
||||
mw = 40
|
||||
mh = 13
|
||||
ml = 120
|
||||
|
||||
# wire and fix size
|
||||
wd = 6 # wire diameter
|
||||
rt = 7 # resin thickness
|
||||
rl = 50 # resin length
|
||||
rwpl = 10 # resin to wire pass length
|
||||
|
||||
# pocket fillet
|
||||
pf = 18
|
||||
|
||||
# mount holes
|
||||
mhd = 7 # hole diameter
|
||||
mht = 3 # hole distance from edge
|
||||
|
||||
# filling hole
|
||||
fhd = 6
|
||||
|
||||
# DRAWING
|
||||
|
||||
# draw base
|
||||
base = cq.Workplane("XY").box(ml, mw, mh, (True, True, False))
|
||||
|
||||
# draw wire
|
||||
pocket = cq.Workplane("XY", (0, 0, mh)).moveTo(-ml/2., 0).line(0, wd/2.)\
|
||||
.line((ml-rl)/2.-rwpl, 0).line(rwpl, rt).line(rl, 0)\
|
||||
.line(rwpl, -rt).line((ml-rl)/2.-rwpl, 0)\
|
||||
.line(0, -(wd/2.)).close().revolve(axisEnd=(1, 0))\
|
||||
.edges(BS((-rl/2.-rwpl-.1, -100, -100), (rl/2.+rwpl+.1, 100, 100)))\
|
||||
.fillet(pf)
|
||||
|
||||
r = base.cut(pocket)
|
||||
|
||||
# mount holes
|
||||
if mount_holes:
|
||||
px = ml/2.-mht-mhd/2.
|
||||
py = mw/2.-mht-mhd/2
|
||||
r = r.faces("<Z").workplane().pushPoints([
|
||||
(px, py),
|
||||
(-px, py),
|
||||
(-px, -py),
|
||||
(px, -py)
|
||||
]).hole(mhd)
|
||||
|
||||
# fill holes
|
||||
r = r.faces("<Y").workplane().center(0, mh/2.).pushPoints([
|
||||
(-rl/2., 0),
|
||||
(0, 0),
|
||||
(rl/2., 0)
|
||||
]).hole(fhd, mw/2.)
|
||||
|
||||
show(r)
|
||||
```
|
||||
|
||||
Thanks go to cadquery contributor hyOzd ( Altu Technology ) for the example!
|
||||
|
||||
Projects Using CadQuery
|
||||
=========================
|
||||
|
||||
KiCad uses cadquery to build high quality models of electrictronic components. ( https://github.com/KiCad/packages3D )
|
||||
|
||||
<p align="center">
|
||||
<img src="https://forum.freecadweb.org/download/file.php?id=33797&sid=b8584f80928497722e9ee9d582a3fa43" width="350"/>
|
||||
</p>
|
||||
|
||||
This Prusa i3 extruder support uses cadquery to build the model (https://github.com/adam-urbanczyk/cadquery-models) :
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/adam-urbanczyk/cadquery-models/raw/master/extruder_support.png" width="350"/>
|
||||
</p>
|
||||
|
||||
The mach30 project used cadquery to develop a tool that will create a rocket thruster directly from the appropriate equations (https://opendesignengine.net/projects/yavin-thruster/wiki):
|
||||
<p align="center">
|
||||
<img src="http://opendesignengine.net/dmsf_files/480?download=" width="700"/>
|
||||
</p>
|
||||
|
||||
This example uses Jupyter notebook to produce a really cool web-based scripting environment ( https://github.com/RustyVermeer/avnb/blob/master/readme.md ) :
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/RustyVermeer/avnb/raw/master/example.gif" width="700"/>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
We would love to link to your cadquery based project. Just let us know and we'll add it here.
|
||||
|
||||
|
||||
|
||||
|
||||
Why CadQuery instead of OpenSCAD?
|
||||
========================================
|
||||
|
||||
CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
|
||||
|
||||
The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
|
||||
|
||||
CadQuery scripts have several key advantages over OpenSCAD:
|
||||
|
||||
1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
|
||||
This includes many standard libraries and IDEs
|
||||
|
||||
2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
|
||||
by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
|
||||
in addition to the standard CSG operations supported by CGAL
|
||||
|
||||
3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
|
||||
and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
|
||||
|
||||
4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
|
||||
features based on the position of other features, workplanes, vertices, etc.
|
||||
|
||||
5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
CadQuery is licensed under the terms of the Apache Public License, version 2.0.
|
||||
A copy of the license can be found at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
CadQuery GUI Interfaces
|
||||
=======================
|
||||
|
||||
There are currently several known CadQuery GUIs:
|
||||
|
||||
### CadQuery FreeCAD Module
|
||||
You can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
### CadQuery GUI (under active development)
|
||||
Work is underway on a stand-alone gui here: https://github.com/jmwright/cadquery-gui
|
||||
|
||||
### ParametricParts.com
|
||||
If you are impatient and want to see a working example with no installation, have a look at this lego brick example http://parametricparts.com/parts/vqb5dy69/.
|
||||
|
||||
The script that generates the model is on the 'modelscript' tab.
|
||||
|
||||
|
||||
Installing -- FreeStanding Installation
|
||||
========================================
|
||||
|
||||
Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
|
||||
|
||||
1. install FreeCAD, version 0.15 or greater for your platform. https://github.com/FreeCAD/FreeCAD/releases.
|
||||
|
||||
2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
|
||||
preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
|
||||
lib directory to your path. On (*Nix)::
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.append('/usr/lib/freecad/lib')
|
||||
```
|
||||
|
||||
or on Windows::
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.append('/c/apps/FreeCAD/bin')
|
||||
```
|
||||
|
||||
*NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
|
||||
|
||||
3. install cadquery::
|
||||
```bash
|
||||
pip install cadquery
|
||||
```
|
||||
4. installing cadquery should install pyparsing as well, but if not::
|
||||
```bash
|
||||
pip install pyparsing
|
||||
```
|
||||
5. test your installation::
|
||||
```python
|
||||
from cadquery import *
|
||||
box = Workplane("XY").box(1,2,3)
|
||||
exporters.toString(box,'STL')
|
||||
```
|
||||
You're up and running!
|
||||
|
||||
Installing -- Using CadQuery from Inside FreeCAD
|
||||
=================================================
|
||||
|
||||
Use the CadQuery module for FreeCAD here:
|
||||
https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
It includes a distribution of the latest version of cadquery.
|
||||
|
||||
Roadmap/Future Work
|
||||
=======================
|
||||
|
||||
Work has begun on Cadquery 2.0, which will feature:
|
||||
|
||||
1. Feature trees, for more powerful selection
|
||||
2. Direct use of OpenCascade Community Edition(OCE), so that it is no longer required to install FreeCAD
|
||||
3. https://github.com/jmwright/cadquery-gui, which will allow visualization of workplanes
|
||||
|
||||
The project page can be found here: https://github.com/dcowden/cadquery/projects/1
|
||||
|
||||
A more detailed description of the plan for CQ 2.0 is here: https://docs.google.com/document/d/1cXuxBkVeYmGOo34MGRdG7E3ILypQqkrJ26oVf3CUSPQ
|
||||
|
||||
Where does the name CadQuery come from?
|
||||
========================================
|
||||
|
||||
CadQuery is inspired by jQuery, a popular framework that
|
||||
revolutionized web development involving javascript.
|
||||
|
||||
If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
|
||||
|
||||
* A fluent api to create clean, easy to read code
|
||||
* Language features that make selection and iteration incredibly easy
|
||||
*
|
||||
* Ability to use the library along side other python libraries
|
||||
* Clear and complete documentation, with plenty of samples.
|
125
Libs/cadquery/README.txt
Normal file
|
@ -0,0 +1,125 @@
|
|||
What is a CadQuery?
|
||||
========================================
|
||||
|
||||
CadQuery is an intuitive, easy-to-use python based language for building parametric 3D CAD models. CadQuery is for 3D CAD what jQuery is for javascript. Imagine selecting Faces of a 3d object the same way you select DOM objects with JQuery!
|
||||
|
||||
CadQuery has several goals:
|
||||
|
||||
* Build models with scripts that are as close as possible to how you'd describe the object to a human.
|
||||
* Create parametric models that can be very easily customized by end users
|
||||
* Output high quality CAD formats like STEP and AMF in addition to traditional STL
|
||||
* Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
|
||||
|
||||
Using CadQuery, you can write short, simple scripts that produce high quality CAD models. It is easy to make many different objects using a single script that can be customized.
|
||||
|
||||
Getting Started With CadQuery
|
||||
========================================
|
||||
|
||||
The easiest way to get started with CadQuery is to Install FreeCAD ( version 14 recommended ) (http://www.freecadweb.org/) , and then to use our CadQuery-FreeCAD plugin here:
|
||||
|
||||
https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
|
||||
It includes the latest version of cadquery alreadby bundled, and has super-easy installation on Mac, Windows, and Unix.
|
||||
|
||||
It has tons of awesome features like integration with FreeCAD so you can see your objects, code-autocompletion, an examples bundle, and script saving/loading. Its definitely the best way to kick the tires!
|
||||
|
||||
|
||||
Recently Added Features
|
||||
========================================
|
||||
|
||||
* 12/5/14 -- New FreeCAD/CadQuery Module! https://github.com/jmwright/cadquery-freecad-module
|
||||
* 10/25/14 -- Added Revolution Feature ( thanks Jeremy ! )
|
||||
|
||||
|
||||
Why CadQuery instead of OpenSCAD?
|
||||
========================================
|
||||
|
||||
CadQuery is based on OpenCasCade. CadQuery shares many features with OpenSCAD, another open source, script based, parametric model generator.
|
||||
|
||||
The primary advantage of OpenSCAD is the large number of already existing model libaries that exist already. So why not simply use OpenSCAD?
|
||||
|
||||
CadQuery scripts have several key advantages over OpenSCAD:
|
||||
|
||||
1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
|
||||
This includes many standard libraries and IDEs
|
||||
|
||||
2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
|
||||
by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
|
||||
in addition to the standard CSG operations supported by CGAL
|
||||
|
||||
3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
|
||||
and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
|
||||
|
||||
4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
|
||||
features based on the position of other features, workplanes, vertices, etc.
|
||||
|
||||
5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
CadQuery is licensed under the terms of the LGPLv3. http://www.gnu.org/copyleft/lesser.html
|
||||
|
||||
Where is the GUI?
|
||||
==================
|
||||
|
||||
If you would like IDE support, you can use CadQuery inside of FreeCAD. There's an excellent plugin module here https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
CadQuery also provides the backbone of http://parametricparts.com, so the easiest way to see it in action is to review the samples and objects there.
|
||||
|
||||
Installing -- FreeStanding Installation
|
||||
========================================
|
||||
|
||||
Use these steps if you would like to write CadQuery scripts as a python API. In this case, FreeCAD is used only as a CAD kernel.
|
||||
|
||||
1. install FreeCAD, version 0.14 or greater for your platform. http://sourceforge.net/projects/free-cad/.
|
||||
|
||||
2. adjust your path if necessary. FreeCAD bundles a python interpreter, but you'll probably want to use your own,
|
||||
preferably one that has virtualenv available. To use FreeCAD from any python interpreter, just append the FreeCAD
|
||||
lib directory to your path. On (*Nix)::
|
||||
|
||||
import sys
|
||||
sys.path.append('/usr/lib/freecad/lib')
|
||||
|
||||
or on Windows::
|
||||
|
||||
import sys
|
||||
sys.path.append('/c/apps/FreeCAD/bin')
|
||||
|
||||
*NOTE* FreeCAD on Windows will not work with python 2.7-- you must use pthon 2.6.X!!!!
|
||||
|
||||
3. install cadquery::
|
||||
|
||||
pip install cadquery
|
||||
|
||||
3. test your installation::
|
||||
|
||||
from cadquery import *
|
||||
box = Workplane("XY").box(1,2,3)
|
||||
exporters.toString(box,'STL')
|
||||
|
||||
You're up and running!
|
||||
|
||||
Installing -- Using CadQuery from Inside FreeCAD
|
||||
=================================================
|
||||
|
||||
Use the Excellent CadQuery-FreeCAD plugin here:
|
||||
https://github.com/jmwright/cadquery-freecad-module
|
||||
|
||||
It includes a distribution of the latest version of cadquery.
|
||||
|
||||
Where does the name CadQuery come from?
|
||||
========================================
|
||||
|
||||
CadQuery is inspired by ( `jQuery <http://www.jquery.com>`_ ), a popular framework that
|
||||
revolutionized web development involving javascript.
|
||||
|
||||
If you are familiar with how jQuery, you will probably recognize several jQuery features that CadQuery uses:
|
||||
|
||||
* A fluent api to create clean, easy to read code
|
||||
* Language features that make selection and iteration incredibly easy
|
||||
*
|
||||
* Ability to use the library along side other python libraries
|
||||
* Clear and complete documentation, with plenty of samples.
|
||||
|
2
Libs/cadquery/build-docs.sh
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
sphinx-build -b html doc target/docs
|
34
Libs/cadquery/build_docker.sh
Normal file
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
#builds and tests the docker image
|
||||
docker build -t dcowden/cadquery .
|
||||
|
||||
# set up tests
|
||||
CQ_TEST_DIR=/tmp/cq_docker-test
|
||||
mkdir -p $CQ_TEST_DIR
|
||||
rm -rf $CQ_TEST_DIR/*.*
|
||||
cp examples/FreeCAD/Ex001_Simple_Block.py $CQ_TEST_DIR
|
||||
|
||||
|
||||
fail_test( ){
|
||||
"Test Failed."
|
||||
}
|
||||
|
||||
echo "Running Tests..."
|
||||
echo "No arguments prints documentation..."
|
||||
docker run dcowden/cadquery | grep "CadQuery Docker Image" || fail_test
|
||||
echo "OK"
|
||||
|
||||
echo "Std in and stdout..."
|
||||
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
|
||||
echo "OK"
|
||||
|
||||
echo "Mount a directory and produce output..."
|
||||
docker run -i -v $CQ_TEST_DIR:/home/cq dcowden/cadquery build --in_spec Ex001_Simple_Block.py --format STEP
|
||||
ls $CQ_TEST_DIR | grep "cqobject-1.STEP" || fail_test
|
||||
echo "OK"
|
||||
|
||||
echo "Future Server EntryPoint"
|
||||
docker run -i dcowden/cadquery runserver | grep "Future CadQuery Server" || fail_test
|
||||
echo "OK"
|
8
Libs/cadquery/cadquery/README.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
***
|
||||
Core CadQuery implementation.
|
||||
|
||||
No files should depend on or import FreeCAD , pythonOCC, or other CAD Kernel libraries!!!
|
||||
Dependencies should be on the classes provided by implementation packages, which in turn
|
||||
can depend on CAD libraries.
|
||||
|
||||
***
|
21
Libs/cadquery/cadquery/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
#these items point to the freecad implementation
|
||||
from .freecad_impl.geom import Plane,BoundBox,Vector,Matrix,sortWiresByBuildOrder
|
||||
from .freecad_impl.shapes import Shape,Vertex,Edge,Face,Wire,Solid,Shell,Compound
|
||||
from .freecad_impl import exporters
|
||||
from .freecad_impl import importers
|
||||
|
||||
#these items are the common implementation
|
||||
|
||||
#the order of these matter
|
||||
from .selectors import *
|
||||
from .cq import *
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CQ','Workplane','plugins','selectors','Plane','BoundBox','Matrix','Vector','sortWiresByBuildOrder',
|
||||
'Shape','Vertex','Edge','Wire','Face','Solid','Shell','Compound','exporters', 'importers',
|
||||
'NearestToPointSelector','ParallelDirSelector','DirectionSelector','PerpendicularDirSelector',
|
||||
'TypeSelector','DirectionMinMaxSelector','StringSyntaxSelector','Selector','plugins',
|
||||
]
|
||||
|
||||
__version__ = "1.0.0"
|
18
Libs/cadquery/cadquery/contrib/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This file is part of CadQuery.
|
||||
|
||||
CadQuery is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
CadQuery is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; If not, see <http://www.gnu.org/licenses/>
|
||||
"""
|
2565
Libs/cadquery/cadquery/cq.py
Normal file
85
Libs/cadquery/cadquery/cq_directive.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
"""
|
||||
A special directive for including a cq object.
|
||||
|
||||
"""
|
||||
|
||||
import traceback
|
||||
from cadquery import *
|
||||
from cadquery import cqgi
|
||||
import io
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
template = """
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="cq" style="text-align:%(txt_align)s;float:left;">
|
||||
%(out_svg)s
|
||||
</div>
|
||||
<div style="clear:both;">
|
||||
</div>
|
||||
|
||||
"""
|
||||
template_content_indent = ' '
|
||||
|
||||
|
||||
def cq_directive(name, arguments, options, content, lineno,
|
||||
content_offset, block_text, state, state_machine):
|
||||
# only consider inline snippets
|
||||
plot_code = '\n'.join(content)
|
||||
|
||||
# Since we don't have a filename, use a hash based on the content
|
||||
# the script must define a variable called 'out', which is expected to
|
||||
# be a CQ object
|
||||
out_svg = "Your Script Did not assign call build_output() function!"
|
||||
|
||||
try:
|
||||
_s = io.StringIO()
|
||||
result = cqgi.parse(plot_code).build()
|
||||
|
||||
if result.success:
|
||||
exporters.exportShape(result.first_result.shape, "SVG", _s)
|
||||
out_svg = _s.getvalue()
|
||||
else:
|
||||
raise result.exception
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
out_svg = traceback.format_exc()
|
||||
|
||||
# now out
|
||||
# Now start generating the lines of output
|
||||
lines = []
|
||||
|
||||
# get rid of new lines
|
||||
out_svg = out_svg.replace('\n', '')
|
||||
|
||||
txt_align = "left"
|
||||
if "align" in options:
|
||||
txt_align = options['align']
|
||||
|
||||
lines.extend((template % locals()).split('\n'))
|
||||
|
||||
lines.extend(['::', ''])
|
||||
lines.extend([' %s' % row.rstrip()
|
||||
for row in plot_code.split('\n')])
|
||||
lines.append('')
|
||||
|
||||
if len(lines):
|
||||
state_machine.insert_input(
|
||||
lines, state_machine.input_lines.source(0))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def setup(app):
|
||||
setup.app = app
|
||||
setup.config = app.config
|
||||
setup.confdir = app.confdir
|
||||
|
||||
options = {'height': directives.length_or_unitless,
|
||||
'width': directives.length_or_percentage_or_unitless,
|
||||
'align': directives.unchanged
|
||||
}
|
||||
|
||||
app.add_directive('cq_plot', cq_directive, True, (0, 2, 0), **options)
|
482
Libs/cadquery/cadquery/cqgi.py
Normal file
|
@ -0,0 +1,482 @@
|
|||
"""
|
||||
The CadQuery Gateway Interface.
|
||||
Provides classes and tools for executing CadQuery scripts
|
||||
"""
|
||||
import ast
|
||||
import traceback
|
||||
import time
|
||||
import cadquery
|
||||
|
||||
CQSCRIPT = "<cqscript>"
|
||||
|
||||
def parse(script_source):
|
||||
"""
|
||||
Parses the script as a model, and returns a model.
|
||||
|
||||
If you would prefer to access the underlying model without building it,
|
||||
for example, to inspect its available parameters, construct a CQModel object.
|
||||
|
||||
:param script_source: the script to run. Must be a valid cadquery script
|
||||
:return: a CQModel object that defines the script and allows execution
|
||||
|
||||
"""
|
||||
model = CQModel(script_source)
|
||||
return model
|
||||
|
||||
|
||||
class CQModel(object):
|
||||
"""
|
||||
Represents a Cadquery Script.
|
||||
|
||||
After construction, the metadata property contains
|
||||
a ScriptMetaData object, which describes the model in more detail,
|
||||
and can be used to retrive the parameters defined by the model.
|
||||
|
||||
the build method can be used to generate a 3d model
|
||||
"""
|
||||
def __init__(self, script_source):
|
||||
"""
|
||||
Create an object by parsing the supplied python script.
|
||||
:param script_source: a python script to parse
|
||||
"""
|
||||
self.metadata = ScriptMetadata()
|
||||
self.ast_tree = ast.parse(script_source, CQSCRIPT)
|
||||
self.script_source = script_source
|
||||
self._find_vars()
|
||||
|
||||
# TODO: pick up other scirpt metadata:
|
||||
# describe
|
||||
# pick up validation methods
|
||||
self._find_descriptions()
|
||||
|
||||
def _find_vars(self):
|
||||
"""
|
||||
Parse the script, and populate variables that appear to be
|
||||
overridable.
|
||||
"""
|
||||
#assumption here: we assume that variable declarations
|
||||
#are only at the top level of the script. IE, we'll ignore any
|
||||
#variable definitions at lower levels of the script
|
||||
|
||||
#we dont want to use the visit interface because here we excplicitly
|
||||
#want to walk only the top level of the tree.
|
||||
assignment_finder = ConstantAssignmentFinder(self.metadata)
|
||||
|
||||
for node in self.ast_tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
assignment_finder.visit_Assign(node)
|
||||
|
||||
def _find_descriptions(self):
|
||||
description_finder = ParameterDescriptionFinder(self.metadata)
|
||||
description_finder.visit(self.ast_tree)
|
||||
|
||||
def validate(self, params):
|
||||
"""
|
||||
Determine if the supplied parameters are valid.
|
||||
NOT IMPLEMENTED YET-- raises NotImplementedError
|
||||
:param params: a dictionary of parameters
|
||||
|
||||
"""
|
||||
raise NotImplementedError("not yet implemented")
|
||||
|
||||
def build(self, build_parameters=None, build_options=None):
|
||||
"""
|
||||
Executes the script, using the optional parameters to override those in the model
|
||||
:param build_parameters: a dictionary of variables. The variables must be
|
||||
assignable to the underlying variable type. These variables override default values in the script
|
||||
:param build_options: build options for how to build the model. Build options include things like
|
||||
timeouts, tesselation tolerances, etc
|
||||
:raises: Nothing. If there is an exception, it will be on the exception property of the result.
|
||||
This is the interface so that we can return other information on the result, such as the build time
|
||||
:return: a BuildResult object, which includes the status of the result, and either
|
||||
a resulting shape or an exception
|
||||
"""
|
||||
if not build_parameters:
|
||||
build_parameters = {}
|
||||
|
||||
start = time.clock()
|
||||
result = BuildResult()
|
||||
|
||||
try:
|
||||
self.set_param_values(build_parameters)
|
||||
collector = ScriptCallback()
|
||||
env = EnvironmentBuilder().with_real_builtins().with_cadquery_objects() \
|
||||
.add_entry("show_object", collector.show_object) \
|
||||
.add_entry("debug", collector.debug) \
|
||||
.add_entry("describe_parameter",collector.describe_parameter) \
|
||||
.build()
|
||||
|
||||
c = compile(self.ast_tree, CQSCRIPT, 'exec')
|
||||
exec (c, env)
|
||||
result.set_debug(collector.debugObjects )
|
||||
result.set_success_result(collector.outputObjects)
|
||||
|
||||
except Exception as ex:
|
||||
#print "Error Executing Script:"
|
||||
result.set_failure_result(ex)
|
||||
#traceback.print_exc()
|
||||
#print "Full Text of Script:"
|
||||
#print self.script_source
|
||||
|
||||
end = time.clock()
|
||||
result.buildTime = end - start
|
||||
return result
|
||||
|
||||
def set_param_values(self, params):
|
||||
model_parameters = self.metadata.parameters
|
||||
|
||||
for k, v in params.items():
|
||||
if k not in model_parameters:
|
||||
raise InvalidParameterError("Cannot set value '%s': not a parameter of the model." % k)
|
||||
|
||||
p = model_parameters[k]
|
||||
p.set_value(v)
|
||||
|
||||
|
||||
class ShapeResult(object):
|
||||
"""
|
||||
An object created by a build, including the user parameters provided
|
||||
"""
|
||||
def __init__(self):
|
||||
self.shape = None
|
||||
self.options = None
|
||||
|
||||
class BuildResult(object):
|
||||
"""
|
||||
The result of executing a CadQuery script.
|
||||
The success property contains whether the exeuction was successful.
|
||||
|
||||
If successful, the results property contains a list of all results,
|
||||
and the first_result property contains the first result.
|
||||
|
||||
If unsuccessful, the exception property contains a reference to
|
||||
the stack trace that occurred.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.buildTime = None
|
||||
self.results = [] #list of ShapeResult
|
||||
self.debugObjects = [] #list of ShapeResult
|
||||
self.first_result = None
|
||||
self.success = False
|
||||
self.exception = None
|
||||
|
||||
def set_failure_result(self, ex):
|
||||
self.exception = ex
|
||||
self.success = False
|
||||
|
||||
def set_debug(self, debugObjects):
|
||||
self.debugObjects = debugObjects
|
||||
|
||||
def set_success_result(self, results):
|
||||
self.results = results
|
||||
if len(self.results) > 0:
|
||||
self.first_result = self.results[0]
|
||||
self.success = True
|
||||
|
||||
|
||||
class ScriptMetadata(object):
|
||||
"""
|
||||
Defines the metadata for a parsed CQ Script.
|
||||
the parameters property is a dict of InputParameter objects.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.parameters = {}
|
||||
|
||||
def add_script_parameter(self, p):
|
||||
self.parameters[p.name] = p
|
||||
|
||||
def add_parameter_description(self,name,description):
|
||||
#print 'Adding Parameter name=%s, desc=%s' % ( name, description )
|
||||
p = self.parameters[name]
|
||||
p.desc = description
|
||||
|
||||
|
||||
class ParameterType(object):
|
||||
pass
|
||||
|
||||
|
||||
class NumberParameterType(ParameterType):
|
||||
pass
|
||||
|
||||
|
||||
class StringParameterType(ParameterType):
|
||||
pass
|
||||
|
||||
|
||||
class BooleanParameterType(ParameterType):
|
||||
pass
|
||||
|
||||
|
||||
class InputParameter:
|
||||
"""
|
||||
Defines a parameter that can be supplied when the model is executed.
|
||||
|
||||
Name, varType, and default_value are always available, because they are computed
|
||||
from a variable assignment line of code:
|
||||
|
||||
The others are only available if the script has used define_parameter() to
|
||||
provide additional metadata
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
|
||||
#: the default value for the variable.
|
||||
self.default_value = None
|
||||
|
||||
#: the name of the parameter.
|
||||
self.name = None
|
||||
|
||||
#: type of the variable: BooleanParameter, StringParameter, NumericParameter
|
||||
self.varType = None
|
||||
|
||||
#: help text describing the variable. Only available if the script used describe_parameter()
|
||||
self.desc = None
|
||||
|
||||
#: valid values for the variable. Only available if the script used describe_parameter()
|
||||
self.valid_values = []
|
||||
|
||||
self.ast_node = None
|
||||
|
||||
@staticmethod
|
||||
def create(ast_node, var_name, var_type, default_value, valid_values=None, desc=None):
|
||||
|
||||
if valid_values is None:
|
||||
valid_values = []
|
||||
|
||||
p = InputParameter()
|
||||
p.ast_node = ast_node
|
||||
p.default_value = default_value
|
||||
p.name = var_name
|
||||
p.desc = desc
|
||||
p.varType = var_type
|
||||
p.valid_values = valid_values
|
||||
return p
|
||||
|
||||
def set_value(self, new_value):
|
||||
|
||||
if len(self.valid_values) > 0 and new_value not in self.valid_values:
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': not a valid value. Valid values are {2:s} "
|
||||
.format(str(new_value), self.name, str(self.valid_values)))
|
||||
|
||||
if self.varType == NumberParameterType:
|
||||
try:
|
||||
# Sometimes a value must stay as an int for the script to work properly
|
||||
if isinstance(new_value, int):
|
||||
f = int(new_value)
|
||||
else:
|
||||
f = float(new_value)
|
||||
|
||||
self.ast_node.n = f
|
||||
except ValueError:
|
||||
raise InvalidParameterError(
|
||||
"Cannot set value '{0:s}' for parameter '{1:s}': parameter must be numeric."
|
||||
.format(str(new_value), self.name))
|
||||
|
||||
elif self.varType == StringParameterType:
|
||||
self.ast_node.s = str(new_value)
|
||||
elif self.varType == BooleanParameterType:
|
||||
if new_value:
|
||||
self.ast_node.id = 'True'
|
||||
else:
|
||||
self.ast_node.id = 'False'
|
||||
else:
|
||||
raise ValueError("Unknown Type of var: ", str(self.varType))
|
||||
|
||||
def __str__(self):
|
||||
return "InputParameter: {name=%s, type=%s, defaultValue=%s" % (
|
||||
self.name, str(self.varType), str(self.default_value))
|
||||
|
||||
|
||||
class ScriptCallback(object):
|
||||
"""
|
||||
Allows a script to communicate with the container
|
||||
the show_object() method is exposed to CQ scripts, to allow them
|
||||
to return objects to the execution environment
|
||||
"""
|
||||
def __init__(self):
|
||||
self.outputObjects = []
|
||||
self.debugObjects = []
|
||||
|
||||
def show_object(self, shape,options={}):
|
||||
"""
|
||||
return an object to the executing environment, with options
|
||||
:param shape: a cadquery object
|
||||
:param options: a dictionary of options that will be made available to the executing envrionment
|
||||
"""
|
||||
o = ShapeResult()
|
||||
o.options=options
|
||||
o.shape = shape
|
||||
self.outputObjects.append(o)
|
||||
|
||||
def debug(self,obj,args={}):
|
||||
"""
|
||||
Debug print/output an object, with optional arguments.
|
||||
"""
|
||||
s = ShapeResult()
|
||||
s.shape = obj
|
||||
s.options = args
|
||||
self.debugObjects.append(s)
|
||||
|
||||
def describe_parameter(self,var_data ):
|
||||
"""
|
||||
Do Nothing-- we parsed the ast ahead of exection to get what we need.
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_error(self, param, field_list):
|
||||
"""
|
||||
Not implemented yet: allows scripts to indicate that there are problems with inputs
|
||||
"""
|
||||
pass
|
||||
|
||||
def has_results(self):
|
||||
return len(self.outputObjects) > 0
|
||||
|
||||
|
||||
|
||||
class InvalidParameterError(Exception):
|
||||
"""
|
||||
Raised when an attempt is made to provide a new parameter value
|
||||
that cannot be assigned to the model
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NoOutputError(Exception):
|
||||
"""
|
||||
Raised when the script does not execute the show_object() method to
|
||||
return a solid
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ScriptExecutionError(Exception):
|
||||
"""
|
||||
Represents a script syntax error.
|
||||
Useful for helping clients pinpoint issues with the script
|
||||
interactively
|
||||
"""
|
||||
|
||||
def __init__(self, line=None, message=None):
|
||||
if line is None:
|
||||
self.line = 0
|
||||
else:
|
||||
self.line = line
|
||||
|
||||
if message is None:
|
||||
self.message = "Unknown Script Error"
|
||||
else:
|
||||
self.message = message
|
||||
|
||||
def full_message(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __repr__(self):
|
||||
return "ScriptError [Line %s]: %s" % (self.line, self.message)
|
||||
|
||||
|
||||
class EnvironmentBuilder(object):
|
||||
"""
|
||||
Builds an execution environment for a cadquery script.
|
||||
The environment includes the builtins, as well as
|
||||
the other methods the script will need.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.env = {}
|
||||
|
||||
def with_real_builtins(self):
|
||||
return self.with_builtins(__builtins__)
|
||||
|
||||
def with_builtins(self, env_dict):
|
||||
self.env['__builtins__'] = env_dict
|
||||
return self
|
||||
|
||||
def with_cadquery_objects(self):
|
||||
self.env['cadquery'] = cadquery
|
||||
self.env['cq'] = cadquery
|
||||
return self
|
||||
|
||||
def add_entry(self, name, value):
|
||||
self.env[name] = value
|
||||
return self
|
||||
|
||||
def build(self):
|
||||
return self.env
|
||||
|
||||
class ParameterDescriptionFinder(ast.NodeTransformer):
|
||||
"""
|
||||
Visits a parse tree, looking for function calls to describe_parameter(var, description )
|
||||
"""
|
||||
def __init__(self, cq_model):
|
||||
self.cqModel = cq_model
|
||||
|
||||
def visit_Call(self,node):
|
||||
"""
|
||||
Called when we see a function call. Is it describe_parameter?
|
||||
"""
|
||||
try:
|
||||
if node.func.id == 'describe_parameter':
|
||||
#looks like we have a call to our function.
|
||||
#first parameter is the variable,
|
||||
#second is the description
|
||||
varname = node.args[0].id
|
||||
desc = node.args[1].s
|
||||
self.cqModel.add_parameter_description(varname,desc)
|
||||
|
||||
except:
|
||||
#print "Unable to handle function call"
|
||||
pass
|
||||
return node
|
||||
|
||||
class ConstantAssignmentFinder(ast.NodeTransformer):
|
||||
"""
|
||||
Visits a parse tree, and adds script parameters to the cqModel
|
||||
"""
|
||||
|
||||
def __init__(self, cq_model):
|
||||
self.cqModel = cq_model
|
||||
|
||||
def handle_assignment(self, var_name, value_node):
|
||||
try:
|
||||
|
||||
if type(value_node) == ast.Num:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, NumberParameterType, value_node.n))
|
||||
elif type(value_node) == ast.Str:
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, StringParameterType, value_node.s))
|
||||
elif type(value_node == ast.Name):
|
||||
if value_node.id == 'True':
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
elif value_node.id == 'False':
|
||||
self.cqModel.add_script_parameter(
|
||||
InputParameter.create(value_node, var_name, BooleanParameterType, True))
|
||||
except:
|
||||
print("Unable to handle assignment for variable '%s'" % var_name)
|
||||
pass
|
||||
|
||||
def visit_Assign(self, node):
|
||||
|
||||
try:
|
||||
left_side = node.targets[0]
|
||||
|
||||
#do not handle attribute assignments
|
||||
if isinstance(left_side,ast.Attribute):
|
||||
return
|
||||
|
||||
if type(node.value) in [ast.Num, ast.Str, ast.Name]:
|
||||
self.handle_assignment(left_side.id, node.value)
|
||||
elif type(node.value) == ast.Tuple:
|
||||
# we have a multi-value assignment
|
||||
for n, v in zip(left_side.elts, node.value.elts):
|
||||
self.handle_assignment(n.id, v)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
print("Unable to handle assignment for node '%s'" % ast.dump(left_side))
|
||||
|
||||
return node
|
3
Libs/cadquery/cadquery/freecad_impl/README.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
It is ok for files in this directory to import FreeCAD, FreeCAD.Base, and FreeCAD.Part.
|
||||
|
||||
Other modules should _not_ depend on FreeCAD
|
144
Libs/cadquery/cadquery/freecad_impl/__init__.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This file is part of CadQuery.
|
||||
|
||||
CadQuery is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
CadQuery is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; If not, see <http://www.gnu.org/licenses/>
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
#FreeCAD has crudified the stdout stream with a bunch of STEP output
|
||||
#garbage
|
||||
#this cleans it up
|
||||
#so it is possible to use stdout for object output
|
||||
class suppress_stdout_stderr(object):
|
||||
'''
|
||||
A context manager for doing a "deep suppression" of stdout and stderr in
|
||||
Python, i.e. will suppress all print, even if the print originates in a
|
||||
compiled C/Fortran sub-function.
|
||||
This will not suppress raised exceptions, since exceptions are printed
|
||||
to stderr just before a script exits, and after the context manager has
|
||||
exited (at least, I think that is why it lets exceptions through).
|
||||
|
||||
'''
|
||||
def __init__(self):
|
||||
# Open a pair of null files
|
||||
self.null_fds = [os.open(os.devnull,os.O_RDWR) for x in range(2)]
|
||||
# Save the actual stdout (1) and stderr (2) file descriptors.
|
||||
self.save_fds = [os.dup(1), os.dup(2)]
|
||||
|
||||
def __enter__(self):
|
||||
# Assign the null pointers to stdout and stderr.
|
||||
os.dup2(self.null_fds[0],1)
|
||||
os.dup2(self.null_fds[1],2)
|
||||
|
||||
def __exit__(self, *_):
|
||||
# Re-assign the real stdout/stderr back to (1) and (2)
|
||||
os.dup2(self.save_fds[0],1)
|
||||
os.dup2(self.save_fds[1],2)
|
||||
# Close all file descriptors
|
||||
for fd in self.null_fds + self.save_fds:
|
||||
os.close(fd)
|
||||
|
||||
def _fc_path():
|
||||
"""Find FreeCAD"""
|
||||
# Look for FREECAD_LIB env variable
|
||||
_PATH = os.environ.get('FREECAD_LIB', '')
|
||||
if _PATH and os.path.exists(_PATH):
|
||||
return _PATH
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
# Make some dangerous assumptions...
|
||||
for _PATH in [
|
||||
os.path.join(os.path.expanduser("~"), "lib/freecad/lib"),
|
||||
"/usr/local/lib/freecad/lib",
|
||||
"/usr/lib/freecad/lib",
|
||||
"/opt/freecad/lib/",
|
||||
"/usr/bin/freecad/lib",
|
||||
"/usr/lib/freecad-daily/lib",
|
||||
"/usr/lib/freecad",
|
||||
"/usr/lib64/freecad/lib",
|
||||
]:
|
||||
if os.path.exists(_PATH):
|
||||
return _PATH
|
||||
|
||||
elif sys.platform.startswith('win'):
|
||||
# Try all the usual suspects
|
||||
for _PATH in [
|
||||
"c:/Program Files/FreeCAD0.12/bin",
|
||||
"c:/Program Files/FreeCAD0.13/bin",
|
||||
"c:/Program Files/FreeCAD0.14/bin",
|
||||
"c:/Program Files/FreeCAD0.15/bin",
|
||||
"c:/Program Files/FreeCAD0.16/bin",
|
||||
"c:/Program Files/FreeCAD0.17/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.12/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.13/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.14/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.15/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.16/bin",
|
||||
"c:/Program Files (x86)/FreeCAD0.17/bin",
|
||||
"c:/apps/FreeCAD0.12/bin",
|
||||
"c:/apps/FreeCAD0.13/bin",
|
||||
"c:/apps/FreeCAD0.14/bin",
|
||||
"c:/apps/FreeCAD0.15/bin",
|
||||
"c:/apps/FreeCAD0.16/bin",
|
||||
"c:/apps/FreeCAD0.17/bin",
|
||||
"c:/Program Files/FreeCAD 0.12/bin",
|
||||
"c:/Program Files/FreeCAD 0.13/bin",
|
||||
"c:/Program Files/FreeCAD 0.14/bin",
|
||||
"c:/Program Files/FreeCAD 0.15/bin",
|
||||
"c:/Program Files/FreeCAD 0.16/bin",
|
||||
"c:/Program Files/FreeCAD 0.17/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.12/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.13/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.14/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.15/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.16/bin",
|
||||
"c:/Program Files (x86)/FreeCAD 0.17/bin",
|
||||
"c:/apps/FreeCAD 0.12/bin",
|
||||
"c:/apps/FreeCAD 0.13/bin",
|
||||
"c:/apps/FreeCAD 0.14/bin",
|
||||
"c:/apps/FreeCAD 0.15/bin",
|
||||
"c:/apps/FreeCAD 0.16/bin",
|
||||
"c:/apps/FreeCAD 0.17/bin",
|
||||
]:
|
||||
if os.path.exists(_PATH):
|
||||
return _PATH
|
||||
|
||||
elif sys.platform.startswith('darwin'):
|
||||
# Assume we're dealing with a Mac
|
||||
for _PATH in [
|
||||
"/Applications/FreeCAD.app/Contents/lib",
|
||||
os.path.join(os.path.expanduser("~"),
|
||||
"Library/Application Support/FreeCAD/lib"),
|
||||
]:
|
||||
if os.path.exists(_PATH):
|
||||
return _PATH
|
||||
|
||||
raise ImportError('cadquery was unable to determine freecad library path')
|
||||
|
||||
|
||||
# Make sure that the correct FreeCAD path shows up in Python's system path
|
||||
with suppress_stdout_stderr():
|
||||
try:
|
||||
import FreeCAD
|
||||
except ImportError:
|
||||
path = _fc_path()
|
||||
sys.path.insert(0, path)
|
||||
import FreeCAD
|
||||
|
||||
# logging
|
||||
from . import console_logging
|
111
Libs/cadquery/cadquery/freecad_impl/console_logging.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import sys
|
||||
import logging
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# Logging Handler
|
||||
# Retain a reference to the logging handler so it may be removed on requeset.
|
||||
# Also to prevent 2 handlers being added
|
||||
_logging_handler = None
|
||||
|
||||
# FreeCAD Logging Handler
|
||||
class FreeCADConsoleHandler(logging.Handler):
|
||||
"""logging.Handler class to output to FreeCAD's console"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FreeCADConsoleHandler, self).__init__(*args, **kwargs)
|
||||
|
||||
# Test for expected print functions
|
||||
# (just check they exist, if they don't an exception will be raised)
|
||||
FreeCAD.Console.PrintMessage
|
||||
FreeCAD.Console.PrintWarning
|
||||
FreeCAD.Console.PrintError
|
||||
|
||||
def emit(self, record):
|
||||
log_text = self.format(record) + "\n"
|
||||
if record.levelno >= logging.ERROR:
|
||||
FreeCAD.Console.PrintError(log_text)
|
||||
elif record.levelno >= logging.WARNING:
|
||||
FreeCAD.Console.PrintWarning(log_text)
|
||||
else:
|
||||
FreeCAD.Console.PrintMessage(log_text)
|
||||
|
||||
|
||||
def enable(level=None, format="%(message)s"):
|
||||
"""
|
||||
Enable python builtin logging, and output it somewhere you can see.
|
||||
- FreeCAD Console, or
|
||||
- STDOUT (if output to console fails, for whatever reason)
|
||||
|
||||
Any script can log to FreeCAD console with:
|
||||
|
||||
>>> import cadquery
|
||||
>>> cadquery.freecad_impl.console_logging.enable()
|
||||
>>> import logging
|
||||
>>> log = logging.getLogger(__name__)
|
||||
>>> log.debug("detailed info, not normally displayed")
|
||||
>>> log.info("some information")
|
||||
some information
|
||||
>>> log.warning("some warning text") # orange text
|
||||
some warning text
|
||||
>>> log.error("an error message") # red text
|
||||
an error message
|
||||
|
||||
logging only needs to be enabled once, somewhere in your codebase.
|
||||
debug logging level can be set with:
|
||||
|
||||
>>> import cadquery
|
||||
>>> import logging
|
||||
>>> cadquery.freecad_impl.console_logging.enable(logging.DEBUG)
|
||||
>>> log = logging.getLogger(__name__)
|
||||
>>> log.debug("debug logs will now be displayed")
|
||||
debug logs will now be displayed
|
||||
|
||||
:param level: logging level to display, one of logging.(DEBUG|INFO|WARNING|ERROR)
|
||||
:param format: logging format to display (search for "python logging format" for details)
|
||||
:return: the logging Handler instance in effect
|
||||
"""
|
||||
global _logging_handler
|
||||
|
||||
# Set overall logging level (done even if handler has already been assigned)
|
||||
root_logger = logging.getLogger()
|
||||
if level is not None:
|
||||
root_logger.setLevel(level)
|
||||
elif _logging_handler is None:
|
||||
# level is not specified, and ho handler has been added yet.
|
||||
# assumption: user is enabling logging for the first time with no parameters.
|
||||
# let's make it simple for them and default the level to logging.INFO
|
||||
# (logging default level is logging.WARNING)
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
if _logging_handler is None:
|
||||
# Determine which Handler class to use
|
||||
try:
|
||||
_logging_handler = FreeCADConsoleHandler()
|
||||
except Exception as e:
|
||||
raise
|
||||
# Fall back to STDOUT output (better than nothing)
|
||||
_logging_handler = logging.StreamHandler(sys.stdout)
|
||||
|
||||
# Configure and assign handler to root logger
|
||||
_logging_handler.setLevel(logging.DEBUG)
|
||||
root_logger.addHandler(_logging_handler)
|
||||
|
||||
# Set formatting (can be used to re-define logging format)
|
||||
formatter = logging.Formatter(format)
|
||||
_logging_handler.setFormatter(formatter)
|
||||
|
||||
return _logging_handler
|
||||
|
||||
|
||||
def disable():
|
||||
"""
|
||||
Disables logging to FreeCAD console (or STDOUT).
|
||||
Note, logging may be enabled by another imported module, so this isn't a
|
||||
guarentee; this function undoes logging_enable(), nothing more.
|
||||
"""
|
||||
global _logging_handler
|
||||
if _logging_handler:
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers.remove(_logging_handler)
|
||||
_logging_handler = None
|
396
Libs/cadquery/cadquery/freecad_impl/exporters.py
Normal file
|
@ -0,0 +1,396 @@
|
|||
import cadquery
|
||||
|
||||
import FreeCAD
|
||||
import Drawing
|
||||
|
||||
import tempfile, os, io
|
||||
|
||||
#weird syntax i know
|
||||
from ..freecad_impl import suppress_stdout_stderr
|
||||
|
||||
try:
|
||||
import xml.etree.cElementTree as ET
|
||||
except ImportError:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
class ExportTypes:
|
||||
STL = "STL"
|
||||
STEP = "STEP"
|
||||
AMF = "AMF"
|
||||
SVG = "SVG"
|
||||
TJS = "TJS"
|
||||
|
||||
|
||||
class UNITS:
|
||||
MM = "mm"
|
||||
IN = "in"
|
||||
|
||||
|
||||
def toString(shape, exportType, tolerance=0.1):
|
||||
s = io.StringIO()
|
||||
exportShape(shape, exportType, s, tolerance)
|
||||
return s.getvalue()
|
||||
|
||||
|
||||
|
||||
def exportShape(shape,exportType,fileLike,tolerance=0.1):
|
||||
"""
|
||||
:param shape: the shape to export. it can be a shape object, or a cadquery object. If a cadquery
|
||||
object, the first value is exported
|
||||
:param exportFormat: the exportFormat to use
|
||||
:param tolerance: the tolerance, in model units
|
||||
:param fileLike: a file like object to which the content will be written.
|
||||
The object should be already open and ready to write. The caller is responsible
|
||||
for closing the object
|
||||
"""
|
||||
|
||||
|
||||
if isinstance(shape,cadquery.CQ):
|
||||
shape = shape.val()
|
||||
|
||||
if exportType == ExportTypes.TJS:
|
||||
#tessellate the model
|
||||
|
||||
tess = shape.tessellate(tolerance)
|
||||
|
||||
mesher = JsonMesh() #warning: needs to be changed to remove buildTime and exportTime!!!
|
||||
#add vertices
|
||||
for vec in tess[0]:
|
||||
mesher.addVertex(vec.x, vec.y, vec.z)
|
||||
|
||||
#add faces
|
||||
for f in tess[1]:
|
||||
mesher.addTriangleFace(f[0],f[1], f[2])
|
||||
fileLike.write( mesher.toJson())
|
||||
elif exportType == ExportTypes.SVG:
|
||||
fileLike.write(getSVG(shape.wrapped))
|
||||
elif exportType == ExportTypes.AMF:
|
||||
tess = shape.tessellate(tolerance)
|
||||
aw = AmfWriter(tess).writeAmf(fileLike)
|
||||
else:
|
||||
|
||||
#all these types required writing to a file and then
|
||||
#re-reading. this is due to the fact that FreeCAD writes these
|
||||
(h, outFileName) = tempfile.mkstemp()
|
||||
#weird, but we need to close this file. the next step is going to write to
|
||||
#it from c code, so it needs to be closed.
|
||||
#FreeCAD junks up stdout with a bunch of messages, so this context
|
||||
#manager supresses that stuff in the case we're trying to write to stdout
|
||||
os.close(h)
|
||||
with suppress_stdout_stderr():
|
||||
if exportType == ExportTypes.STEP:
|
||||
shape.exportStep(outFileName)
|
||||
elif exportType == ExportTypes.STL:
|
||||
shape.wrapped.exportStl(outFileName)
|
||||
else:
|
||||
raise ValueError("No idea how i got here")
|
||||
|
||||
res = readAndDeleteFile(outFileName)
|
||||
fileLike.write(res)
|
||||
|
||||
def readAndDeleteFile(fileName):
|
||||
"""
|
||||
read data from file provided, and delete it when done
|
||||
return the contents as a string
|
||||
"""
|
||||
res = ""
|
||||
with open(fileName,'r') as f:
|
||||
res = f.read()
|
||||
|
||||
os.remove(fileName)
|
||||
return res
|
||||
|
||||
|
||||
def guessUnitOfMeasure(shape):
|
||||
"""
|
||||
Guess the unit of measure of a shape.
|
||||
"""
|
||||
bb = shape.BoundBox
|
||||
|
||||
dimList = [ bb.XLength, bb.YLength,bb.ZLength ]
|
||||
#no real part would likely be bigger than 10 inches on any side
|
||||
if max(dimList) > 10:
|
||||
return UNITS.MM
|
||||
|
||||
#no real part would likely be smaller than 0.1 mm on all dimensions
|
||||
if min(dimList) < 0.1:
|
||||
return UNITS.IN
|
||||
|
||||
#no real part would have the sum of its dimensions less than about 5mm
|
||||
if sum(dimList) < 10:
|
||||
return UNITS.IN
|
||||
|
||||
return UNITS.MM
|
||||
|
||||
|
||||
class AmfWriter(object):
|
||||
def __init__(self,tessellation):
|
||||
|
||||
self.units = "mm"
|
||||
self.tessellation = tessellation
|
||||
|
||||
def writeAmf(self,outFile):
|
||||
amf = ET.Element('amf',units=self.units)
|
||||
#TODO: if result is a compound, we need to loop through them
|
||||
object = ET.SubElement(amf,'object',id="0")
|
||||
mesh = ET.SubElement(object,'mesh')
|
||||
vertices = ET.SubElement(mesh,'vertices')
|
||||
volume = ET.SubElement(mesh,'volume')
|
||||
|
||||
#add vertices
|
||||
for v in self.tessellation[0]:
|
||||
vtx = ET.SubElement(vertices,'vertex')
|
||||
coord = ET.SubElement(vtx,'coordinates')
|
||||
x = ET.SubElement(coord,'x')
|
||||
x.text = str(v.x)
|
||||
y = ET.SubElement(coord,'y')
|
||||
y.text = str(v.y)
|
||||
z = ET.SubElement(coord,'z')
|
||||
z.text = str(v.z)
|
||||
|
||||
#add triangles
|
||||
for t in self.tessellation[1]:
|
||||
triangle = ET.SubElement(volume,'triangle')
|
||||
v1 = ET.SubElement(triangle,'v1')
|
||||
v1.text = str(t[0])
|
||||
v2 = ET.SubElement(triangle,'v2')
|
||||
v2.text = str(t[1])
|
||||
v3 = ET.SubElement(triangle,'v3')
|
||||
v3.text = str(t[2])
|
||||
|
||||
ET.ElementTree(amf).write(outFile,encoding="unicode",xml_declaration=True)
|
||||
|
||||
"""
|
||||
Objects that represent
|
||||
three.js JSON object notation
|
||||
https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3.0
|
||||
"""
|
||||
class JsonMesh(object):
|
||||
def __init__(self):
|
||||
|
||||
self.vertices = [];
|
||||
self.faces = [];
|
||||
self.nVertices = 0;
|
||||
self.nFaces = 0;
|
||||
|
||||
def addVertex(self,x,y,z):
|
||||
self.nVertices += 1;
|
||||
self.vertices.extend([x,y,z]);
|
||||
|
||||
#add triangle composed of the three provided vertex indices
|
||||
def addTriangleFace(self, i,j,k):
|
||||
#first position means justa simple triangle
|
||||
self.nFaces += 1;
|
||||
self.faces.extend([0,int(i),int(j),int(k)]);
|
||||
|
||||
"""
|
||||
Get a json model from this model.
|
||||
For now we'll forget about colors, vertex normals, and all that stuff
|
||||
"""
|
||||
def toJson(self):
|
||||
return JSON_TEMPLATE % {
|
||||
'vertices' : str(self.vertices),
|
||||
'faces' : str(self.faces),
|
||||
'nVertices': self.nVertices,
|
||||
'nFaces' : self.nFaces
|
||||
};
|
||||
|
||||
|
||||
def getPaths(freeCadSVG):
|
||||
"""
|
||||
freeCad svg is worthless-- except for paths, which are fairly useful
|
||||
this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element
|
||||
returns two lists-- one list of visible lines, and one list of hidden lines
|
||||
|
||||
HACK ALERT!!!!!
|
||||
FreeCAD does not give a way to determine which lines are hidden and which are not
|
||||
the only way to tell is that hidden lines are in a <g> with 0.15 stroke and visible are 0.35 stroke.
|
||||
so we actually look for that as a way to parse.
|
||||
|
||||
to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we
|
||||
cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick!
|
||||
"""
|
||||
|
||||
hiddenPaths = []
|
||||
visiblePaths = []
|
||||
if len(freeCadSVG) > 0:
|
||||
#yuk, freecad returns svg fragments. stupid stupid
|
||||
fullDoc = "<root>%s</root>" % freeCadSVG
|
||||
e = ET.ElementTree(ET.fromstring(fullDoc))
|
||||
segments = e.findall(".//g")
|
||||
for s in segments:
|
||||
paths = s.findall("path")
|
||||
|
||||
if s.get("stroke-width") == "0.15": #hidden line HACK HACK HACK
|
||||
mylist = hiddenPaths
|
||||
else:
|
||||
mylist = visiblePaths
|
||||
|
||||
for p in paths:
|
||||
mylist.append(p.get("d"))
|
||||
return (hiddenPaths,visiblePaths)
|
||||
else:
|
||||
return ([],[])
|
||||
|
||||
|
||||
def getSVG(shape,opts=None):
|
||||
"""
|
||||
Export a shape to SVG
|
||||
"""
|
||||
|
||||
d = {'width':800,'height':240,'marginLeft':200,'marginTop':20}
|
||||
|
||||
if opts:
|
||||
d.update(opts)
|
||||
|
||||
#need to guess the scale and the coordinate center
|
||||
uom = guessUnitOfMeasure(shape)
|
||||
|
||||
width=float(d['width'])
|
||||
height=float(d['height'])
|
||||
marginLeft=float(d['marginLeft'])
|
||||
marginTop=float(d['marginTop'])
|
||||
|
||||
#TODO: provide option to give 3 views
|
||||
viewVector = FreeCAD.Base.Vector(-1.75,1.1,5)
|
||||
(visibleG0,visibleG1,hiddenG0,hiddenG1) = Drawing.project(shape,viewVector)
|
||||
|
||||
(hiddenPaths,visiblePaths) = getPaths(Drawing.projectToSVG(shape,viewVector,"ShowHiddenLines")) #this param is totally undocumented!
|
||||
|
||||
#get bounding box -- these are all in 2-d space
|
||||
bb = visibleG0.BoundBox
|
||||
bb.add(visibleG1.BoundBox)
|
||||
bb.add(hiddenG0.BoundBox)
|
||||
bb.add(hiddenG1.BoundBox)
|
||||
|
||||
#width pixels for x, height pixesl for y
|
||||
unitScale = min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
|
||||
|
||||
#compute amount to translate-- move the top left into view
|
||||
(xTranslate,yTranslate) = ( (0 - bb.XMin) + marginLeft/unitScale ,(0- bb.YMax) - marginTop/unitScale)
|
||||
|
||||
#compute paths ( again -- had to strip out freecad crap )
|
||||
hiddenContent = ""
|
||||
for p in hiddenPaths:
|
||||
hiddenContent += PATHTEMPLATE % p
|
||||
|
||||
visibleContent = ""
|
||||
for p in visiblePaths:
|
||||
visibleContent += PATHTEMPLATE % p
|
||||
|
||||
svg = SVG_TEMPLATE % (
|
||||
{
|
||||
"unitScale" : str(unitScale),
|
||||
"strokeWidth" : str(1.0/unitScale),
|
||||
"hiddenContent" : hiddenContent ,
|
||||
"visibleContent" :visibleContent,
|
||||
"xTranslate" : str(xTranslate),
|
||||
"yTranslate" : str(yTranslate),
|
||||
"width" : str(width),
|
||||
"height" : str(height),
|
||||
"textboxY" :str(height - 30),
|
||||
"uom" : str(uom)
|
||||
}
|
||||
)
|
||||
#svg = SVG_TEMPLATE % (
|
||||
# {"content": projectedContent}
|
||||
#)
|
||||
return svg
|
||||
|
||||
|
||||
def exportSVG(shape, fileName):
|
||||
"""
|
||||
accept a cadquery shape, and export it to the provided file
|
||||
TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
|
||||
export a view of a part to svg
|
||||
"""
|
||||
|
||||
svg = getSVG(shape.val().wrapped)
|
||||
f = open(fileName,'w')
|
||||
f.write(svg)
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
JSON_TEMPLATE= """\
|
||||
{
|
||||
"metadata" :
|
||||
{
|
||||
"formatVersion" : 3,
|
||||
"generatedBy" : "ParametricParts",
|
||||
"vertices" : %(nVertices)d,
|
||||
"faces" : %(nFaces)d,
|
||||
"normals" : 0,
|
||||
"colors" : 0,
|
||||
"uvs" : 0,
|
||||
"materials" : 1,
|
||||
"morphTargets" : 0
|
||||
},
|
||||
|
||||
"scale" : 1.0,
|
||||
|
||||
"materials": [ {
|
||||
"DbgColor" : 15658734,
|
||||
"DbgIndex" : 0,
|
||||
"DbgName" : "Material",
|
||||
"colorAmbient" : [0.0, 0.0, 0.0],
|
||||
"colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746],
|
||||
"colorSpecular" : [0.5, 0.5, 0.5],
|
||||
"shading" : "Lambert",
|
||||
"specularCoef" : 50,
|
||||
"transparency" : 1.0,
|
||||
"vertexColors" : false
|
||||
}],
|
||||
|
||||
"vertices": %(vertices)s,
|
||||
|
||||
"morphTargets": [],
|
||||
|
||||
"normals": [],
|
||||
|
||||
"colors": [],
|
||||
|
||||
"uvs": [[]],
|
||||
|
||||
"faces": %(faces)s
|
||||
}
|
||||
"""
|
||||
|
||||
SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="%(width)s"
|
||||
height="%(height)s"
|
||||
|
||||
>
|
||||
<g transform="scale(%(unitScale)s, -%(unitScale)s) translate(%(xTranslate)s,%(yTranslate)s)" stroke-width="%(strokeWidth)s" fill="none">
|
||||
<!-- hidden lines -->
|
||||
<g stroke="rgb(160, 160, 160)" fill="none" stroke-dasharray="%(strokeWidth)s,%(strokeWidth)s" >
|
||||
%(hiddenContent)s
|
||||
</g>
|
||||
|
||||
<!-- solid lines -->
|
||||
<g stroke="rgb(0, 0, 0)" fill="none">
|
||||
%(visibleContent)s
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(20,%(textboxY)s)" stroke="rgb(0,0,255)">
|
||||
<line x1="30" y1="-30" x2="75" y2="-33" stroke-width="3" stroke="#000000" />
|
||||
<text x="80" y="-30" style="stroke:#000000">X </text>
|
||||
|
||||
<line x1="30" y1="-30" x2="30" y2="-75" stroke-width="3" stroke="#000000" />
|
||||
<text x="25" y="-85" style="stroke:#000000">Y </text>
|
||||
|
||||
<line x1="30" y1="-30" x2="58" y2="-15" stroke-width="3" stroke="#000000" />
|
||||
<text x="65" y="-5" style="stroke:#000000">Z </text>
|
||||
<!--
|
||||
<line x1="0" y1="0" x2="%(unitScale)s" y2="0" stroke-width="3" />
|
||||
<text x="0" y="20" style="stroke:#000000">1 %(uom)s </text>
|
||||
-->
|
||||
</g>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
PATHTEMPLATE="\t\t\t<path d=\"%s\" />\n"
|
666
Libs/cadquery/cadquery/freecad_impl/geom.py
Normal file
|
@ -0,0 +1,666 @@
|
|||
"""
|
||||
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This file is part of CadQuery.
|
||||
|
||||
CadQuery is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
CadQuery is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; If not, see <http://www.gnu.org/licenses/>
|
||||
"""
|
||||
|
||||
import math
|
||||
import cadquery
|
||||
import FreeCAD
|
||||
import Part as FreeCADPart
|
||||
|
||||
|
||||
def sortWiresByBuildOrder(wireList, plane, result=[]):
|
||||
"""Tries to determine how wires should be combined into faces.
|
||||
|
||||
Assume:
|
||||
The wires make up one or more faces, which could have 'holes'
|
||||
Outer wires are listed ahead of inner wires
|
||||
there are no wires inside wires inside wires
|
||||
( IE, islands -- we can deal with that later on )
|
||||
none of the wires are construction wires
|
||||
|
||||
Compute:
|
||||
one or more sets of wires, with the outer wire listed first, and inner
|
||||
ones
|
||||
|
||||
Returns, list of lists.
|
||||
"""
|
||||
result = []
|
||||
|
||||
remainingWires = list(wireList)
|
||||
while remainingWires:
|
||||
outerWire = remainingWires.pop(0)
|
||||
group = [outerWire]
|
||||
otherWires = list(remainingWires)
|
||||
for w in otherWires:
|
||||
if plane.isWireInside(outerWire, w):
|
||||
group.append(w)
|
||||
remainingWires.remove(w)
|
||||
result.append(group)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Vector(object):
|
||||
"""Create a 3-dimensional vector
|
||||
|
||||
:param args: a 3-d vector, with x-y-z parts.
|
||||
|
||||
you can either provide:
|
||||
* nothing (in which case the null vector is return)
|
||||
* a FreeCAD vector
|
||||
* a vector ( in which case it is copied )
|
||||
* a 3-tuple
|
||||
* three float values, x, y, and z
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
if len(args) == 3:
|
||||
fV = FreeCAD.Base.Vector(args[0], args[1], args[2])
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], Vector):
|
||||
fV = args[0].wrapped
|
||||
elif isinstance(args[0], tuple):
|
||||
fV = FreeCAD.Base.Vector(args[0][0], args[0][1], args[0][2])
|
||||
elif isinstance(args[0], FreeCAD.Base.Vector):
|
||||
fV = args[0]
|
||||
else:
|
||||
fV = args[0]
|
||||
elif len(args) == 0:
|
||||
fV = FreeCAD.Base.Vector(0, 0, 0)
|
||||
else:
|
||||
raise ValueError("Expected three floats, FreeCAD Vector, or 3-tuple")
|
||||
|
||||
self._wrapped = fV
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.wrapped.x
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
self.wrapped.x = value
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self.wrapped.y
|
||||
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
self.wrapped.y = value
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
return self.wrapped.z
|
||||
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
self.wrapped.z = value
|
||||
|
||||
@property
|
||||
def Length(self):
|
||||
return self.wrapped.Length
|
||||
|
||||
@property
|
||||
def wrapped(self):
|
||||
return self._wrapped
|
||||
|
||||
def toTuple(self):
|
||||
return (self.x, self.y, self.z)
|
||||
|
||||
# TODO: is it possible to create a dynamic proxy without all this code?
|
||||
def cross(self, v):
|
||||
return Vector(self.wrapped.cross(v.wrapped))
|
||||
|
||||
def dot(self, v):
|
||||
return self.wrapped.dot(v.wrapped)
|
||||
|
||||
def sub(self, v):
|
||||
return Vector(self.wrapped.sub(v.wrapped))
|
||||
|
||||
def add(self, v):
|
||||
return Vector(self.wrapped.add(v.wrapped))
|
||||
|
||||
def multiply(self, scale):
|
||||
"""Return a copy multiplied by the provided scalar"""
|
||||
tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
|
||||
return Vector(tmp_fc_vector.multiply(scale))
|
||||
|
||||
def normalized(self):
|
||||
"""Return a normalized version of this vector"""
|
||||
tmp_fc_vector = FreeCAD.Base.Vector(self.wrapped)
|
||||
tmp_fc_vector.normalize()
|
||||
return Vector(tmp_fc_vector)
|
||||
|
||||
def Center(self):
|
||||
"""Return the vector itself
|
||||
|
||||
The center of myself is myself.
|
||||
Provided so that vectors, vertexes, and other shapes all support a
|
||||
common interface, when Center() is requested for all objects on the
|
||||
stack.
|
||||
"""
|
||||
return self
|
||||
|
||||
def getAngle(self, v):
|
||||
return self.wrapped.getAngle(v.wrapped)
|
||||
|
||||
def distanceToLine(self):
|
||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
||||
|
||||
def projectToLine(self):
|
||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
||||
|
||||
def distanceToPlane(self):
|
||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
||||
|
||||
def projectToPlane(self):
|
||||
raise NotImplementedError("Have not needed this yet, but FreeCAD supports it!")
|
||||
|
||||
def __add__(self, v):
|
||||
return self.add(v)
|
||||
|
||||
def __sub__(self, v):
|
||||
return self.sub(v)
|
||||
|
||||
def __repr__(self):
|
||||
return self.wrapped.__repr__()
|
||||
|
||||
def __str__(self):
|
||||
return self.wrapped.__str__()
|
||||
|
||||
def __ne__(self, other):
|
||||
if isinstance(other, Vector):
|
||||
return self.wrapped.__ne__(other.wrapped)
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Vector):
|
||||
return self.wrapped.__eq__(other.wrapped)
|
||||
return False
|
||||
|
||||
|
||||
class Matrix:
|
||||
"""A 3d , 4x4 transformation matrix.
|
||||
|
||||
Used to move geometry in space.
|
||||
"""
|
||||
def __init__(self, matrix=None):
|
||||
if matrix is None:
|
||||
self.wrapped = FreeCAD.Base.Matrix()
|
||||
else:
|
||||
self.wrapped = matrix
|
||||
|
||||
def rotateX(self, angle):
|
||||
self.wrapped.rotateX(angle)
|
||||
|
||||
def rotateY(self, angle):
|
||||
self.wrapped.rotateY(angle)
|
||||
|
||||
|
||||
class Plane(object):
|
||||
"""A 2D coordinate system in space
|
||||
|
||||
A 2D coordinate system in space, with the x-y axes on the plane, and a
|
||||
particular point as the origin.
|
||||
|
||||
A plane allows the use of 2-d coordinates, which are later converted to
|
||||
global, 3d coordinates when the operations are complete.
|
||||
|
||||
Frequently, it is not necessary to create work planes, as they can be
|
||||
created automatically from faces.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def named(cls, stdName, origin=(0, 0, 0)):
|
||||
"""Create a predefined Plane based on the conventional names.
|
||||
|
||||
:param stdName: one of (XY|YZ|ZX|XZ|YX|ZY|front|back|left|right|top|bottom)
|
||||
:type stdName: string
|
||||
:param origin: the desired origin, specified in global coordinates
|
||||
:type origin: 3-tuple of the origin of the new plane, in global coorindates.
|
||||
|
||||
Available named planes are as follows. Direction references refer to
|
||||
the global directions.
|
||||
|
||||
=========== ======= ======= ======
|
||||
Name xDir yDir zDir
|
||||
=========== ======= ======= ======
|
||||
XY +x +y +z
|
||||
YZ +y +z +x
|
||||
ZX +z +x +y
|
||||
XZ +x +z -y
|
||||
YX +y +x -z
|
||||
ZY +z +y -x
|
||||
front +x +y +z
|
||||
back -x +y -z
|
||||
left +z +y -x
|
||||
right -z +y +x
|
||||
top +x -z +y
|
||||
bottom +x +z -y
|
||||
=========== ======= ======= ======
|
||||
"""
|
||||
|
||||
namedPlanes = {
|
||||
# origin, xDir, normal
|
||||
'XY': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
'YZ': Plane(origin, (0, 1, 0), (1, 0, 0)),
|
||||
'ZX': Plane(origin, (0, 0, 1), (0, 1, 0)),
|
||||
'XZ': Plane(origin, (1, 0, 0), (0, -1, 0)),
|
||||
'YX': Plane(origin, (0, 1, 0), (0, 0, -1)),
|
||||
'ZY': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
'front': Plane(origin, (1, 0, 0), (0, 0, 1)),
|
||||
'back': Plane(origin, (-1, 0, 0), (0, 0, -1)),
|
||||
'left': Plane(origin, (0, 0, 1), (-1, 0, 0)),
|
||||
'right': Plane(origin, (0, 0, -1), (1, 0, 0)),
|
||||
'top': Plane(origin, (1, 0, 0), (0, 1, 0)),
|
||||
'bottom': Plane(origin, (1, 0, 0), (0, -1, 0))
|
||||
}
|
||||
|
||||
try:
|
||||
return namedPlanes[stdName]
|
||||
except KeyError:
|
||||
raise ValueError('Supported names are {}'.format(
|
||||
list(namedPlanes.keys())))
|
||||
|
||||
@classmethod
|
||||
def XY(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XY', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def YZ(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||
plane = Plane.named('YZ', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def ZX(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('ZX', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def XZ(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('XZ', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def YX(cls, origin=(0, 0, 0), xDir=Vector(0, 1, 0)):
|
||||
plane = Plane.named('YX', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def ZY(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('ZY', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def front(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('front', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def back(cls, origin=(0, 0, 0), xDir=Vector(-1, 0, 0)):
|
||||
plane = Plane.named('back', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def left(cls, origin=(0, 0, 0), xDir=Vector(0, 0, 1)):
|
||||
plane = Plane.named('left', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def right(cls, origin=(0, 0, 0), xDir=Vector(0, 0, -1)):
|
||||
plane = Plane.named('right', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def top(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('top', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
@classmethod
|
||||
def bottom(cls, origin=(0, 0, 0), xDir=Vector(1, 0, 0)):
|
||||
plane = Plane.named('bottom', origin)
|
||||
plane._setPlaneDir(xDir)
|
||||
return plane
|
||||
|
||||
def __init__(self, origin, xDir, normal):
|
||||
"""Create a Plane with an arbitrary orientation
|
||||
|
||||
TODO: project x and y vectors so they work even if not orthogonal
|
||||
:param origin: the origin
|
||||
:type origin: a three-tuple of the origin, in global coordinates
|
||||
:param xDir: a vector representing the xDirection.
|
||||
:type xDir: a three-tuple representing a vector, or a FreeCAD Vector
|
||||
:param normal: the normal direction for the new plane
|
||||
:type normal: a FreeCAD Vector
|
||||
:raises: ValueError if the specified xDir is not orthogonal to the provided normal.
|
||||
:return: a plane in the global space, with the xDirection of the plane in the specified direction.
|
||||
"""
|
||||
normal = Vector(normal)
|
||||
if (normal.Length == 0.0):
|
||||
raise ValueError('normal should be non null')
|
||||
self.zDir = normal.normalized()
|
||||
xDir = Vector(xDir)
|
||||
if (xDir.Length == 0.0):
|
||||
raise ValueError('xDir should be non null')
|
||||
self._setPlaneDir(xDir)
|
||||
|
||||
self.invZDir = self.zDir.multiply(-1.0)
|
||||
|
||||
self.origin = origin
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@origin.setter
|
||||
def origin(self, value):
|
||||
self._origin = Vector(value)
|
||||
self._calcTransforms()
|
||||
|
||||
def setOrigin2d(self, x, y):
|
||||
"""
|
||||
Set a new origin in the plane itself
|
||||
|
||||
Set a new origin in the plane itself. The plane's orientation and
|
||||
xDrection are unaffected.
|
||||
|
||||
:param float x: offset in the x direction
|
||||
:param float y: offset in the y direction
|
||||
:return: void
|
||||
|
||||
The new coordinates are specified in terms of the current 2-d system.
|
||||
As an example:
|
||||
|
||||
p = Plane.XY()
|
||||
p.setOrigin2d(2, 2)
|
||||
p.setOrigin2d(2, 2)
|
||||
|
||||
results in a plane with its origin at (x, y) = (4, 4) in global
|
||||
coordinates. Both operations were relative to local coordinates of the
|
||||
plane.
|
||||
"""
|
||||
self.origin = self.toWorldCoords((x, y))
|
||||
|
||||
def isWireInside(self, baseWire, testWire):
|
||||
"""Determine if testWire is inside baseWire
|
||||
|
||||
Determine if testWire is inside baseWire, after both wires are projected
|
||||
into the current plane.
|
||||
|
||||
:param baseWire: a reference wire
|
||||
:type baseWire: a FreeCAD wire
|
||||
:param testWire: another wire
|
||||
:type testWire: a FreeCAD wire
|
||||
:return: True if testWire is inside baseWire, otherwise False
|
||||
|
||||
If either wire does not lie in the current plane, it is projected into
|
||||
the plane first.
|
||||
|
||||
*WARNING*: This method is not 100% reliable. It uses bounding box
|
||||
tests, but needs more work to check for cases when curves are complex.
|
||||
|
||||
Future Enhancements:
|
||||
* Discretizing points along each curve to provide a more reliable
|
||||
test.
|
||||
"""
|
||||
# TODO: also use a set of points along the wire to test as well.
|
||||
# TODO: would it be more efficient to create objects in the local
|
||||
# coordinate system, and then transform to global
|
||||
# coordinates upon extrusion?
|
||||
|
||||
tBaseWire = baseWire.transformGeometry(self.fG)
|
||||
tTestWire = testWire.transformGeometry(self.fG)
|
||||
|
||||
# These bounding boxes will have z=0, since we transformed them into the
|
||||
# space of the plane.
|
||||
bb = tBaseWire.BoundingBox()
|
||||
tb = tTestWire.BoundingBox()
|
||||
|
||||
# findOutsideBox actually inspects both ways, here we only want to
|
||||
# know if one is inside the other
|
||||
return bb == BoundBox.findOutsideBox2D(bb, tb)
|
||||
|
||||
def toLocalCoords(self, obj):
|
||||
"""Project the provided coordinates onto this plane
|
||||
|
||||
:param obj: an object or vector to convert
|
||||
:type vector: a vector or shape
|
||||
:return: an object of the same type, but converted to local coordinates
|
||||
|
||||
|
||||
Most of the time, the z-coordinate returned will be zero, because most
|
||||
operations based on a plane are all 2-d. Occasionally, though, 3-d
|
||||
points outside of the current plane are transformed. One such example is
|
||||
:py:meth:`Workplane.box`, where 3-d corners of a box are transformed to
|
||||
orient the box in space correctly.
|
||||
|
||||
"""
|
||||
if isinstance(obj, Vector):
|
||||
return Vector(self.fG.multiply(obj.wrapped))
|
||||
elif isinstance(obj, cadquery.Shape):
|
||||
return obj.transformShape(self.rG)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Don't know how to convert type {} to local coordinates".format(
|
||||
type(obj)))
|
||||
|
||||
def toWorldCoords(self, tuplePoint):
|
||||
"""Convert a point in local coordinates to global coordinates
|
||||
|
||||
:param tuplePoint: point in local coordinates to convert.
|
||||
:type tuplePoint: a 2 or three tuple of float. The third value is taken to be zero if not supplied.
|
||||
:return: a Vector in global coordinates
|
||||
"""
|
||||
if isinstance(tuplePoint, Vector):
|
||||
v = tuplePoint
|
||||
elif len(tuplePoint) == 2:
|
||||
v = Vector(tuplePoint[0], tuplePoint[1], 0)
|
||||
else:
|
||||
v = Vector(tuplePoint)
|
||||
return Vector(self.rG.multiply(v.wrapped))
|
||||
|
||||
def rotated(self, rotate=(0, 0, 0)):
|
||||
"""Returns a copy of this plane, rotated about the specified axes
|
||||
|
||||
Since the z axis is always normal the plane, rotating around Z will
|
||||
always produce a plane that is parallel to this one.
|
||||
|
||||
The origin of the workplane is unaffected by the rotation.
|
||||
|
||||
Rotations are done in order x, y, z. If you need a different order,
|
||||
manually chain together multiple rotate() commands.
|
||||
|
||||
:param rotate: Vector [xDegrees, yDegrees, zDegrees]
|
||||
:return: a copy of this plane rotated as requested.
|
||||
"""
|
||||
rotate = Vector(rotate)
|
||||
# Convert to radians.
|
||||
rotate = rotate.multiply(math.pi / 180.0)
|
||||
|
||||
# Compute rotation matrix.
|
||||
m = FreeCAD.Base.Matrix()
|
||||
m.rotateX(rotate.x)
|
||||
m.rotateY(rotate.y)
|
||||
m.rotateZ(rotate.z)
|
||||
|
||||
# Compute the new plane.
|
||||
newXdir = Vector(m.multiply(self.xDir.wrapped))
|
||||
newZdir = Vector(m.multiply(self.zDir.wrapped))
|
||||
|
||||
return Plane(self.origin, newXdir, newZdir)
|
||||
|
||||
def rotateShapes(self, listOfShapes, rotationMatrix):
|
||||
"""Rotate the listOfShapes by the supplied rotationMatrix
|
||||
|
||||
@param listOfShapes is a list of shape objects
|
||||
@param rotationMatrix is a geom.Matrix object.
|
||||
returns a list of shape objects rotated according to the rotationMatrix.
|
||||
"""
|
||||
# Compute rotation matrix (global --> local --> rotate --> global).
|
||||
# rm = self.plane.fG.multiply(matrix).multiply(self.plane.rG)
|
||||
# rm = self.computeTransform(rotationMatrix)
|
||||
|
||||
# There might be a better way, but to do this rotation takes 3 steps:
|
||||
# - transform geometry to local coordinates
|
||||
# - then rotate about x
|
||||
# - then transform back to global coordinates.
|
||||
|
||||
resultWires = []
|
||||
for w in listOfShapes:
|
||||
mirrored = w.transformGeometry(rotationMatrix.wrapped)
|
||||
|
||||
# If the first vertex of the second wire is not coincident with the
|
||||
# first or last vertices of the first wire we have to fix the wire
|
||||
# so that it will mirror correctly.
|
||||
if ((mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[0].X and
|
||||
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[0].Y and
|
||||
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[0].Z) or
|
||||
(mirrored.wrapped.Vertexes[0].X == w.wrapped.Vertexes[-1].X and
|
||||
mirrored.wrapped.Vertexes[0].Y == w.wrapped.Vertexes[-1].Y and
|
||||
mirrored.wrapped.Vertexes[0].Z == w.wrapped.Vertexes[-1].Z)):
|
||||
|
||||
resultWires.append(mirrored)
|
||||
else:
|
||||
# Make sure that our mirrored edges meet up and are ordered
|
||||
# properly.
|
||||
aEdges = w.wrapped.Edges
|
||||
aEdges.extend(mirrored.wrapped.Edges)
|
||||
comp = FreeCADPart.Compound(aEdges)
|
||||
mirroredWire = comp.connectEdgesToWires(False).Wires[0]
|
||||
|
||||
resultWires.append(cadquery.Shape.cast(mirroredWire))
|
||||
|
||||
return resultWires
|
||||
|
||||
def _setPlaneDir(self, xDir):
|
||||
"""Set the vectors parallel to the plane, i.e. xDir and yDir"""
|
||||
if (self.zDir.dot(xDir) > 1e-5):
|
||||
raise ValueError('xDir must be parralel to the plane')
|
||||
xDir = Vector(xDir)
|
||||
self.xDir = xDir.normalized()
|
||||
self.yDir = self.zDir.cross(self.xDir).normalized()
|
||||
|
||||
def _calcTransforms(self):
|
||||
"""Computes transformation matrices to convert between coordinates
|
||||
|
||||
Computes transformation matrices to convert between local and global
|
||||
coordinates.
|
||||
"""
|
||||
# r is the forward transformation matrix from world to local coordinates
|
||||
# ok i will be really honest, i cannot understand exactly why this works
|
||||
# something bout the order of the translation and the rotation.
|
||||
# the double-inverting is strange, and I don't understand it.
|
||||
r = FreeCAD.Base.Matrix()
|
||||
|
||||
# Forward transform must rotate and adjust for origin.
|
||||
(r.A11, r.A12, r.A13) = (self.xDir.x, self.xDir.y, self.xDir.z)
|
||||
(r.A21, r.A22, r.A23) = (self.yDir.x, self.yDir.y, self.yDir.z)
|
||||
(r.A31, r.A32, r.A33) = (self.zDir.x, self.zDir.y, self.zDir.z)
|
||||
|
||||
invR = r.inverse()
|
||||
invR.A14 = self.origin.x
|
||||
invR.A24 = self.origin.y
|
||||
invR.A34 = self.origin.z
|
||||
|
||||
self.rG = invR
|
||||
self.fG = invR.inverse()
|
||||
|
||||
def computeTransform(self, tMatrix):
|
||||
"""Computes the 2-d projection of the supplied matrix"""
|
||||
|
||||
return Matrix(self.fG.multiply(tMatrix.wrapped).multiply(self.rG))
|
||||
|
||||
|
||||
class BoundBox(object):
|
||||
"""A BoundingBox for an object or set of objects. Wraps the FreeCAD one"""
|
||||
def __init__(self, bb):
|
||||
self.wrapped = bb
|
||||
self.xmin = bb.XMin
|
||||
self.xmax = bb.XMax
|
||||
self.xlen = bb.XLength
|
||||
self.ymin = bb.YMin
|
||||
self.ymax = bb.YMax
|
||||
self.ylen = bb.YLength
|
||||
self.zmin = bb.ZMin
|
||||
self.zmax = bb.ZMax
|
||||
self.zlen = bb.ZLength
|
||||
self.center = Vector(bb.Center)
|
||||
self.DiagonalLength = bb.DiagonalLength
|
||||
|
||||
def add(self, obj):
|
||||
"""Returns a modified (expanded) bounding box
|
||||
|
||||
obj can be one of several things:
|
||||
1. a 3-tuple corresponding to x,y, and z amounts to add
|
||||
2. a vector, containing the x,y,z values to add
|
||||
3. another bounding box, where a new box will be created that
|
||||
encloses both.
|
||||
|
||||
This bounding box is not changed.
|
||||
"""
|
||||
tmp = FreeCAD.Base.BoundBox(self.wrapped)
|
||||
if isinstance(obj, tuple):
|
||||
tmp.add(obj[0], obj[1], obj[2])
|
||||
elif isinstance(obj, Vector):
|
||||
tmp.add(obj.fV)
|
||||
elif isinstance(obj, BoundBox):
|
||||
tmp.add(obj.wrapped)
|
||||
|
||||
return BoundBox(tmp)
|
||||
|
||||
@classmethod
|
||||
def findOutsideBox2D(cls, b1, b2):
|
||||
"""Compares bounding boxes
|
||||
|
||||
Compares bounding boxes. Returns none if neither is inside the other.
|
||||
Returns the outer one if either is outside the other.
|
||||
|
||||
BoundBox.isInside works in 3d, but this is a 2d bounding box, so it
|
||||
doesn't work correctly plus, there was all kinds of rounding error in
|
||||
the built-in implementation i do not understand.
|
||||
"""
|
||||
fc_bb1 = b1.wrapped
|
||||
fc_bb2 = b2.wrapped
|
||||
if (fc_bb1.XMin < fc_bb2.XMin and
|
||||
fc_bb1.XMax > fc_bb2.XMax and
|
||||
fc_bb1.YMin < fc_bb2.YMin and
|
||||
fc_bb1.YMax > fc_bb2.YMax):
|
||||
return b1
|
||||
|
||||
if (fc_bb2.XMin < fc_bb1.XMin and
|
||||
fc_bb2.XMax > fc_bb1.XMax and
|
||||
fc_bb2.YMin < fc_bb1.YMin and
|
||||
fc_bb2.YMax > fc_bb1.YMax):
|
||||
return b2
|
||||
|
||||
return None
|
||||
|
||||
def isInside(self, anotherBox):
|
||||
"""Is the provided bounding box inside this one?"""
|
||||
return self.wrapped.isInside(anotherBox.wrapped)
|
71
Libs/cadquery/cadquery/freecad_impl/importers.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
|
||||
import cadquery
|
||||
from .shapes import Shape
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import sys
|
||||
import os
|
||||
import urllib as urlreader
|
||||
import tempfile
|
||||
|
||||
class ImportTypes:
|
||||
STEP = "STEP"
|
||||
|
||||
class UNITS:
|
||||
MM = "mm"
|
||||
IN = "in"
|
||||
|
||||
|
||||
def importShape(importType, fileName):
|
||||
"""
|
||||
Imports a file based on the type (STEP, STL, etc)
|
||||
:param importType: The type of file that we're importing
|
||||
:param fileName: THe name of the file that we're importing
|
||||
"""
|
||||
|
||||
#Check to see what type of file we're working with
|
||||
if importType == ImportTypes.STEP:
|
||||
return importStep(fileName)
|
||||
|
||||
|
||||
#Loads a STEP file into a CQ.Workplane object
|
||||
def importStep(fileName):
|
||||
"""
|
||||
Accepts a file name and loads the STEP file into a cadquery shape
|
||||
:param fileName: The path and name of the STEP file to be imported
|
||||
"""
|
||||
#Now read and return the shape
|
||||
try:
|
||||
#print fileName
|
||||
rshape = Part.read(fileName)
|
||||
|
||||
#Make sure that we extract all the solids
|
||||
solids = []
|
||||
for solid in rshape.Solids:
|
||||
solids.append(Shape.cast(solid))
|
||||
|
||||
return cadquery.Workplane("XY").newObject(solids)
|
||||
except:
|
||||
raise ValueError("STEP File Could not be loaded")
|
||||
|
||||
#Loads a STEP file from an URL into a CQ.Workplane object
|
||||
def importStepFromURL(url):
|
||||
#Now read and return the shape
|
||||
try:
|
||||
webFile = urlreader.urlopen(url)
|
||||
tempFile = tempfile.NamedTemporaryFile(suffix='.step', delete=False)
|
||||
tempFile.write(webFile.read())
|
||||
webFile.close()
|
||||
tempFile.close()
|
||||
|
||||
rshape = Part.read(tempFile.name)
|
||||
|
||||
#Make sure that we extract all the solids
|
||||
solids = []
|
||||
for solid in rshape.Solids:
|
||||
solids.append(Shape.cast(solid))
|
||||
|
||||
return cadquery.Workplane("XY").newObject(solids)
|
||||
except:
|
||||
raise ValueError("STEP File from the URL: " + url + " Could not be loaded")
|
1049
Libs/cadquery/cadquery/freecad_impl/shapes.py
Normal file
18
Libs/cadquery/cadquery/plugins/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
CadQuery
|
||||
Copyright (C) 2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
"""
|
664
Libs/cadquery/cadquery/selectors.py
Normal file
|
@ -0,0 +1,664 @@
|
|||
"""
|
||||
Copyright (C) 2011-2015 Parametric Products Intellectual Holdings, LLC
|
||||
|
||||
This file is part of CadQuery.
|
||||
|
||||
CadQuery is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
CadQuery is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; If not, see <http://www.gnu.org/licenses/>
|
||||
"""
|
||||
|
||||
import re
|
||||
import math
|
||||
from cadquery import Vector,Edge,Vertex,Face,Solid,Shell,Compound
|
||||
from collections import defaultdict
|
||||
from pyparsing import Literal,Word,nums,Optional,Combine,oneOf,upcaseTokens,\
|
||||
CaselessLiteral,Group,infixNotation,opAssoc,Forward,\
|
||||
ZeroOrMore,Keyword
|
||||
from functools import reduce
|
||||
|
||||
|
||||
class Selector(object):
|
||||
"""
|
||||
Filters a list of objects
|
||||
|
||||
Filters must provide a single method that filters objects.
|
||||
"""
|
||||
def filter(self,objectList):
|
||||
"""
|
||||
Filter the provided list
|
||||
:param objectList: list to filter
|
||||
:type objectList: list of FreeCAD primatives
|
||||
:return: filtered list
|
||||
|
||||
The default implementation returns the original list unfiltered
|
||||
|
||||
"""
|
||||
return objectList
|
||||
|
||||
def __and__(self, other):
|
||||
return AndSelector(self, other)
|
||||
|
||||
def __add__(self, other):
|
||||
return SumSelector(self, other)
|
||||
|
||||
def __sub__(self, other):
|
||||
return SubtractSelector(self, other)
|
||||
|
||||
def __neg__(self):
|
||||
return InverseSelector(self)
|
||||
|
||||
class NearestToPointSelector(Selector):
|
||||
"""
|
||||
Selects object nearest the provided point.
|
||||
|
||||
If the object is a vertex or point, the distance
|
||||
is used. For other kinds of shapes, the center of mass
|
||||
is used to to compute which is closest.
|
||||
|
||||
Applicability: All Types of Shapes
|
||||
|
||||
Example::
|
||||
|
||||
CQ(aCube).vertices(NearestToPointSelector((0,1,0))
|
||||
|
||||
returns the vertex of the unit cube closest to the point x=0,y=1,z=0
|
||||
|
||||
"""
|
||||
def __init__(self,pnt ):
|
||||
self.pnt = pnt
|
||||
def filter(self,objectList):
|
||||
|
||||
def dist(tShape):
|
||||
return tShape.Center().sub(Vector(*self.pnt)).Length
|
||||
#if tShape.ShapeType == 'Vertex':
|
||||
# return tShape.Point.sub(toVector(self.pnt)).Length
|
||||
#else:
|
||||
# return tShape.CenterOfMass.sub(toVector(self.pnt)).Length
|
||||
|
||||
return [ min(objectList,key=dist) ]
|
||||
|
||||
class BoxSelector(Selector):
|
||||
"""
|
||||
Selects objects inside the 3D box defined by 2 points.
|
||||
|
||||
If `boundingbox` is True only the objects that have their bounding
|
||||
box inside the given box is selected. Otherwise only center point
|
||||
of the object is tested.
|
||||
|
||||
Applicability: all types of shapes
|
||||
|
||||
Example::
|
||||
|
||||
CQ(aCube).edges(BoxSelector((0,1,0), (1,2,1))
|
||||
"""
|
||||
def __init__(self, point0, point1, boundingbox=False):
|
||||
self.p0 = Vector(*point0)
|
||||
self.p1 = Vector(*point1)
|
||||
self.test_boundingbox = boundingbox
|
||||
|
||||
def filter(self, objectList):
|
||||
|
||||
result = []
|
||||
x0, y0, z0 = self.p0.toTuple()
|
||||
x1, y1, z1 = self.p1.toTuple()
|
||||
|
||||
def isInsideBox(p):
|
||||
# using XOR for checking if x/y/z is in between regardless
|
||||
# of order of x/y/z0 and x/y/z1
|
||||
return ((p.x < x0) ^ (p.x < x1)) and \
|
||||
((p.y < y0) ^ (p.y < y1)) and \
|
||||
((p.z < z0) ^ (p.z < z1))
|
||||
|
||||
for o in objectList:
|
||||
if self.test_boundingbox:
|
||||
bb = o.BoundingBox()
|
||||
if isInsideBox(Vector(bb.xmin, bb.ymin, bb.zmin)) and \
|
||||
isInsideBox(Vector(bb.xmax, bb.ymax, bb.zmax)):
|
||||
result.append(o)
|
||||
else:
|
||||
if isInsideBox(o.Center()):
|
||||
result.append(o)
|
||||
|
||||
return result
|
||||
|
||||
class BaseDirSelector(Selector):
|
||||
"""
|
||||
A selector that handles selection on the basis of a single
|
||||
direction vector
|
||||
"""
|
||||
def __init__(self,vector,tolerance=0.0001 ):
|
||||
self.direction = vector
|
||||
self.TOLERANCE = tolerance
|
||||
|
||||
def test(self,vec):
|
||||
"Test a specified vector. Subclasses override to provide other implementations"
|
||||
return True
|
||||
|
||||
def filter(self,objectList):
|
||||
"""
|
||||
There are lots of kinds of filters, but
|
||||
for planes they are always based on the normal of the plane,
|
||||
and for edges on the tangent vector along the edge
|
||||
"""
|
||||
r = []
|
||||
for o in objectList:
|
||||
#no really good way to avoid a switch here, edges and faces are simply different!
|
||||
|
||||
if type(o) == Face:
|
||||
# a face is only parallell to a direction if it is a plane, and its normal is parallel to the dir
|
||||
normal = o.normalAt(None)
|
||||
|
||||
if self.test(normal):
|
||||
r.append(o)
|
||||
elif type(o) == Edge and o.geomType() == 'LINE':
|
||||
#an edge is parallel to a direction if it is a line, and the line is parallel to the dir
|
||||
tangent = o.tangentAt(None)
|
||||
if self.test(tangent):
|
||||
r.append(o)
|
||||
|
||||
return r
|
||||
|
||||
class ParallelDirSelector(BaseDirSelector):
|
||||
"""
|
||||
Selects objects parallel with the provided direction
|
||||
|
||||
Applicability:
|
||||
Linear Edges
|
||||
Planar Faces
|
||||
|
||||
Use the string syntax shortcut \|(X|Y|Z) if you want to select
|
||||
based on a cardinal direction.
|
||||
|
||||
Example::
|
||||
|
||||
CQ(aCube).faces(ParallelDirSelector((0,0,1))
|
||||
|
||||
selects faces with a normals in the z direction, and is equivalent to::
|
||||
|
||||
CQ(aCube).faces("|Z")
|
||||
"""
|
||||
|
||||
def test(self,vec):
|
||||
return self.direction.cross(vec).Length < self.TOLERANCE
|
||||
|
||||
class DirectionSelector(BaseDirSelector):
|
||||
"""
|
||||
Selects objects aligned with the provided direction
|
||||
|
||||
Applicability:
|
||||
Linear Edges
|
||||
Planar Faces
|
||||
|
||||
Use the string syntax shortcut +/-(X|Y|Z) if you want to select
|
||||
based on a cardinal direction.
|
||||
|
||||
Example::
|
||||
|
||||
CQ(aCube).faces(DirectionSelector((0,0,1))
|
||||
|
||||
selects faces with a normals in the z direction, and is equivalent to::
|
||||
|
||||
CQ(aCube).faces("+Z")
|
||||
"""
|
||||
|
||||
def test(self,vec):
|
||||
return abs(self.direction.getAngle(vec) < self.TOLERANCE)
|
||||
|
||||
class PerpendicularDirSelector(BaseDirSelector):
|
||||
"""
|
||||
Selects objects perpendicular with the provided direction
|
||||
|
||||
Applicability:
|
||||
Linear Edges
|
||||
Planar Faces
|
||||
|
||||
Use the string syntax shortcut #(X|Y|Z) if you want to select
|
||||
based on a cardinal direction.
|
||||
|
||||
Example::
|
||||
|
||||
CQ(aCube).faces(PerpendicularDirSelector((0,0,1))
|
||||
|
||||
selects faces with a normals perpendicular to the z direction, and is equivalent to::
|
||||
|
||||
CQ(aCube).faces("#Z")
|
||||
"""
|
||||
|
||||
def test(self,vec):
|
||||
angle = self.direction.getAngle(vec)
|
||||
r = (abs(angle) < self.TOLERANCE) or (abs(angle - math.pi) < self.TOLERANCE )
|
||||
return not r
|
||||
|
||||
|
||||
class TypeSelector(Selector):
|
||||
"""
|
||||
Selects objects of the prescribed topological type.
|
||||
|
||||
Applicability:
|
||||
Faces: Plane,Cylinder,Sphere
|
||||
Edges: Line,Circle,Arc
|
||||
|
||||
You can use the shortcut selector %(PLANE|SPHERE|CONE) for faces,
|
||||
and %(LINE|ARC|CIRCLE) for edges.
|
||||
|
||||
For example this::
|
||||
|
||||
CQ(aCube).faces ( TypeSelector("PLANE") )
|
||||
|
||||
will select 6 faces, and is equivalent to::
|
||||
|
||||
CQ(aCube).faces( "%PLANE" )
|
||||
|
||||
"""
|
||||
def __init__(self,typeString):
|
||||
self.typeString = typeString.upper()
|
||||
|
||||
def filter(self,objectList):
|
||||
r = []
|
||||
for o in objectList:
|
||||
if o.geomType() == self.typeString:
|
||||
r.append(o)
|
||||
return r
|
||||
|
||||
class DirectionMinMaxSelector(Selector):
|
||||
"""
|
||||
Selects objects closest or farthest in the specified direction
|
||||
Used for faces, points, and edges
|
||||
|
||||
Applicability:
|
||||
All object types. for a vertex, its point is used. for all other kinds
|
||||
of objects, the center of mass of the object is used.
|
||||
|
||||
You can use the string shortcuts >(X|Y|Z) or <(X|Y|Z) if you want to
|
||||
select based on a cardinal direction.
|
||||
|
||||
For example this::
|
||||
|
||||
CQ(aCube).faces ( DirectionMinMaxSelector((0,0,1),True )
|
||||
|
||||
Means to select the face having the center of mass farthest in the positive z direction,
|
||||
and is the same as:
|
||||
|
||||
CQ(aCube).faces( ">Z" )
|
||||
|
||||
"""
|
||||
def __init__(self, vector, directionMax=True, tolerance=0.0001):
|
||||
self.vector = vector
|
||||
self.max = max
|
||||
self.directionMax = directionMax
|
||||
self.TOLERANCE = tolerance
|
||||
def filter(self,objectList):
|
||||
|
||||
def distance(tShape):
|
||||
return tShape.Center().dot(self.vector)
|
||||
|
||||
# import OrderedDict
|
||||
from collections import OrderedDict
|
||||
#make and distance to object dict
|
||||
objectDict = {distance(el) : el for el in objectList}
|
||||
#transform it into an ordered dict
|
||||
objectDict = OrderedDict(sorted(list(objectDict.items()),
|
||||
key=lambda x: x[0]))
|
||||
|
||||
# find out the max/min distance
|
||||
if self.directionMax:
|
||||
d = list(objectDict.keys())[-1]
|
||||
else:
|
||||
d = list(objectDict.keys())[0]
|
||||
|
||||
# return all objects at the max/min distance (within a tolerance)
|
||||
return [o for o in objectList if abs(d - distance(o)) < self.TOLERANCE]
|
||||
|
||||
class DirectionNthSelector(ParallelDirSelector):
|
||||
"""
|
||||
Selects nth object parallel (or normal) to the specified direction
|
||||
Used for faces and edges
|
||||
|
||||
Applicability:
|
||||
Linear Edges
|
||||
Planar Faces
|
||||
"""
|
||||
def __init__(self, vector, n, directionMax=True, tolerance=0.0001):
|
||||
self.direction = vector
|
||||
self.max = max
|
||||
self.directionMax = directionMax
|
||||
self.TOLERANCE = tolerance
|
||||
self.N = n
|
||||
|
||||
def filter(self,objectList):
|
||||
#select first the objects that are normal/parallel to a given dir
|
||||
objectList = super(DirectionNthSelector,self).filter(objectList)
|
||||
|
||||
def distance(tShape):
|
||||
return tShape.Center().dot(self.direction)
|
||||
|
||||
#calculate how many digits of precision do we need
|
||||
digits = int(1/self.TOLERANCE)
|
||||
|
||||
#make a distance to object dict
|
||||
#this is one to many mapping so I am using a default dict with list
|
||||
objectDict = defaultdict(list)
|
||||
for el in objectList:
|
||||
objectDict[round(distance(el),digits)].append(el)
|
||||
|
||||
# choose the Nth unique rounded distance
|
||||
nth_distance = sorted(list(objectDict.keys()),
|
||||
reverse=not self.directionMax)[self.N]
|
||||
|
||||
# map back to original objects and return
|
||||
return objectDict[nth_distance]
|
||||
|
||||
class BinarySelector(Selector):
|
||||
"""
|
||||
Base class for selectors that operates with two other
|
||||
selectors. Subclass must implement the :filterResults(): method.
|
||||
"""
|
||||
def __init__(self, left, right):
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
def filter(self, objectList):
|
||||
return self.filterResults(self.left.filter(objectList),
|
||||
self.right.filter(objectList))
|
||||
|
||||
def filterResults(self, r_left, r_right):
|
||||
raise NotImplementedError
|
||||
|
||||
class AndSelector(BinarySelector):
|
||||
"""
|
||||
Intersection selector. Returns objects that is selected by both selectors.
|
||||
"""
|
||||
def filterResults(self, r_left, r_right):
|
||||
# return intersection of lists
|
||||
return list(set(r_left) & set(r_right))
|
||||
|
||||
class SumSelector(BinarySelector):
|
||||
"""
|
||||
Union selector. Returns the sum of two selectors results.
|
||||
"""
|
||||
def filterResults(self, r_left, r_right):
|
||||
# return the union (no duplicates) of lists
|
||||
return list(set(r_left + r_right))
|
||||
|
||||
class SubtractSelector(BinarySelector):
|
||||
"""
|
||||
Difference selector. Substract results of a selector from another
|
||||
selectors results.
|
||||
"""
|
||||
def filterResults(self, r_left, r_right):
|
||||
return list(set(r_left) - set(r_right))
|
||||
|
||||
class InverseSelector(Selector):
|
||||
"""
|
||||
Inverts the selection of given selector. In other words, selects
|
||||
all objects that is not selected by given selector.
|
||||
"""
|
||||
def __init__(self, selector):
|
||||
self.selector = selector
|
||||
|
||||
def filter(self, objectList):
|
||||
# note that Selector() selects everything
|
||||
return SubtractSelector(Selector(), self.selector).filter(objectList)
|
||||
|
||||
|
||||
def _makeGrammar():
|
||||
"""
|
||||
Define the simple string selector grammar using PyParsing
|
||||
"""
|
||||
|
||||
#float definition
|
||||
point = Literal('.')
|
||||
plusmin = Literal('+') | Literal('-')
|
||||
number = Word(nums)
|
||||
integer = Combine(Optional(plusmin) + number)
|
||||
floatn = Combine(integer + Optional(point + Optional(number)))
|
||||
|
||||
#vector definition
|
||||
lbracket = Literal('(')
|
||||
rbracket = Literal(')')
|
||||
comma = Literal(',')
|
||||
vector = Combine(lbracket + floatn('x') + comma + \
|
||||
floatn('y') + comma + floatn('z') + rbracket)
|
||||
|
||||
#direction definition
|
||||
simple_dir = oneOf(['X','Y','Z','XY','XZ','YZ'])
|
||||
direction = simple_dir('simple_dir') | vector('vector_dir')
|
||||
|
||||
#CQ type definition
|
||||
cqtype = oneOf(['Plane','Cylinder','Sphere','Cone','Line','Circle','Arc'],
|
||||
caseless=True)
|
||||
cqtype = cqtype.setParseAction(upcaseTokens)
|
||||
|
||||
#type operator
|
||||
type_op = Literal('%')
|
||||
|
||||
#direction operator
|
||||
direction_op = oneOf(['>','<'])
|
||||
|
||||
#index definition
|
||||
ix_number = Group(Optional('-')+Word(nums))
|
||||
lsqbracket = Literal('[').suppress()
|
||||
rsqbracket = Literal(']').suppress()
|
||||
|
||||
index = lsqbracket + ix_number('index') + rsqbracket
|
||||
|
||||
#other operators
|
||||
other_op = oneOf(['|','#','+','-'])
|
||||
|
||||
#named view
|
||||
named_view = oneOf(['front','back','left','right','top','bottom'])
|
||||
|
||||
return direction('only_dir') | \
|
||||
(type_op('type_op') + cqtype('cq_type')) | \
|
||||
(direction_op('dir_op') + direction('dir') + Optional(index)) | \
|
||||
(other_op('other_op') + direction('dir')) | \
|
||||
named_view('named_view')
|
||||
|
||||
_grammar = _makeGrammar() #make a grammar instance
|
||||
|
||||
class _SimpleStringSyntaxSelector(Selector):
|
||||
"""
|
||||
This is a private class that converts a parseResults object into a simple
|
||||
selector object
|
||||
"""
|
||||
def __init__(self,parseResults):
|
||||
|
||||
#define all token to object mappings
|
||||
self.axes = {
|
||||
'X': Vector(1,0,0),
|
||||
'Y': Vector(0,1,0),
|
||||
'Z': Vector(0,0,1),
|
||||
'XY': Vector(1,1,0),
|
||||
'YZ': Vector(0,1,1),
|
||||
'XZ': Vector(1,0,1)
|
||||
}
|
||||
|
||||
self.namedViews = {
|
||||
'front' : (Vector(0,0,1),True),
|
||||
'back' : (Vector(0,0,1),False),
|
||||
'left' : (Vector(1,0,0),False),
|
||||
'right' : (Vector(1,0,0),True),
|
||||
'top' : (Vector(0,1,0),True),
|
||||
'bottom': (Vector(0,1,0),False)
|
||||
}
|
||||
|
||||
self.operatorMinMax = {
|
||||
'>' : True,
|
||||
'<' : False,
|
||||
'+' : True,
|
||||
'-' : False
|
||||
}
|
||||
|
||||
self.operator = {
|
||||
'+' : DirectionSelector,
|
||||
'-' : DirectionSelector,
|
||||
'#' : PerpendicularDirSelector,
|
||||
'|' : ParallelDirSelector}
|
||||
|
||||
self.parseResults = parseResults
|
||||
self.mySelector = self._chooseSelector(parseResults)
|
||||
|
||||
def _chooseSelector(self,pr):
|
||||
"""
|
||||
Sets up the underlying filters accordingly
|
||||
"""
|
||||
if 'only_dir' in pr:
|
||||
vec = self._getVector(pr)
|
||||
return DirectionSelector(vec)
|
||||
|
||||
elif 'type_op' in pr:
|
||||
return TypeSelector(pr.cq_type)
|
||||
|
||||
elif 'dir_op' in pr:
|
||||
vec = self._getVector(pr)
|
||||
minmax = self.operatorMinMax[pr.dir_op]
|
||||
|
||||
if 'index' in pr:
|
||||
return DirectionNthSelector(vec,int(''.join(pr.index.asList())),minmax)
|
||||
else:
|
||||
return DirectionMinMaxSelector(vec,minmax)
|
||||
|
||||
elif 'other_op' in pr:
|
||||
vec = self._getVector(pr)
|
||||
return self.operator[pr.other_op](vec)
|
||||
|
||||
else:
|
||||
args = self.namedViews[pr.named_view]
|
||||
return DirectionMinMaxSelector(*args)
|
||||
|
||||
def _getVector(self,pr):
|
||||
"""
|
||||
Translate parsed vector string into a CQ Vector
|
||||
"""
|
||||
if 'vector_dir' in pr:
|
||||
vec = pr.vector_dir
|
||||
return Vector(float(vec.x),float(vec.y),float(vec.z))
|
||||
else:
|
||||
return self.axes[pr.simple_dir]
|
||||
|
||||
def filter(self,objectList):
|
||||
"""
|
||||
selects minimum, maximum, positive or negative values relative to a direction
|
||||
[+\|-\|<\|>\|] \<X\|Y\|Z>
|
||||
"""
|
||||
return self.mySelector.filter(objectList)
|
||||
|
||||
def _makeExpressionGrammar(atom):
|
||||
"""
|
||||
Define the complex string selector grammar using PyParsing (which supports
|
||||
logical operations and nesting)
|
||||
"""
|
||||
|
||||
#define operators
|
||||
and_op = Literal('and')
|
||||
or_op = Literal('or')
|
||||
delta_op = oneOf(['exc','except'])
|
||||
not_op = Literal('not')
|
||||
|
||||
def atom_callback(res):
|
||||
return _SimpleStringSyntaxSelector(res)
|
||||
|
||||
atom.setParseAction(atom_callback) #construct a simple selector from every matched
|
||||
|
||||
#define callback functions for all operations
|
||||
def and_callback(res):
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(AndSelector,items)
|
||||
|
||||
def or_callback(res):
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(SumSelector,items)
|
||||
|
||||
def exc_callback(res):
|
||||
items = res.asList()[0][::2] #take every secend items, i.e. all operands
|
||||
return reduce(SubtractSelector,items)
|
||||
|
||||
def not_callback(res):
|
||||
right = res.asList()[0][1] #take second item, i.e. the operand
|
||||
return InverseSelector(right)
|
||||
|
||||
#construct the final grammar and set all the callbacks
|
||||
expr = infixNotation(atom,
|
||||
[(and_op,2,opAssoc.LEFT,and_callback),
|
||||
(or_op,2,opAssoc.LEFT,or_callback),
|
||||
(delta_op,2,opAssoc.LEFT,exc_callback),
|
||||
(not_op,1,opAssoc.RIGHT,not_callback)])
|
||||
|
||||
return expr
|
||||
|
||||
_expression_grammar = _makeExpressionGrammar(_grammar)
|
||||
|
||||
class StringSyntaxSelector(Selector):
|
||||
"""
|
||||
Filter lists objects using a simple string syntax. All of the filters available in the string syntax
|
||||
are also available ( usually with more functionality ) through the creation of full-fledged
|
||||
selector objects. see :py:class:`Selector` and its subclasses
|
||||
|
||||
Filtering works differently depending on the type of object list being filtered.
|
||||
|
||||
:param selectorString: A two-part selector string, [selector][axis]
|
||||
|
||||
:return: objects that match the specified selector
|
||||
|
||||
***Modfiers*** are ``('|','+','-','<','>','%')``
|
||||
|
||||
:\|:
|
||||
parallel to ( same as :py:class:`ParallelDirSelector` ). Can return multiple objects.
|
||||
:#:
|
||||
perpendicular to (same as :py:class:`PerpendicularDirSelector` )
|
||||
:+:
|
||||
positive direction (same as :py:class:`DirectionSelector` )
|
||||
:-:
|
||||
negative direction (same as :py:class:`DirectionSelector` )
|
||||
:>:
|
||||
maximize (same as :py:class:`DirectionMinMaxSelector` with directionMax=True)
|
||||
:<:
|
||||
minimize (same as :py:class:`DirectionMinMaxSelector` with directionMax=False )
|
||||
:%:
|
||||
curve/surface type (same as :py:class:`TypeSelector`)
|
||||
|
||||
***axisStrings*** are: ``X,Y,Z,XY,YZ,XZ`` or ``(x,y,z)`` which defines an arbitrary direction
|
||||
|
||||
It is possible to combine simple selectors together using logical operations.
|
||||
The following operations are suuported
|
||||
|
||||
:and:
|
||||
Logical AND, e.g. >X and >Y
|
||||
:or:
|
||||
Logical OR, e.g. |X or |Y
|
||||
:not:
|
||||
Logical NOT, e.g. not #XY
|
||||
:exc(ept):
|
||||
Set difference (equivalent to AND NOT): |X exc >Z
|
||||
|
||||
Finally, it is also possible to use even more complex expressions with nesting
|
||||
and arbitrary number of terms, e.g.
|
||||
|
||||
(not >X[0] and #XY) or >XY[0]
|
||||
|
||||
Selectors are a complex topic: see :ref:`selector_reference` for more information
|
||||
"""
|
||||
def __init__(self,selectorString):
|
||||
"""
|
||||
Feed the input string through the parser and construct an relevant complex selector object
|
||||
"""
|
||||
self.selectorString = selectorString
|
||||
parse_result = _expression_grammar.parseString(selectorString,
|
||||
parseAll=True)
|
||||
self.mySelector = parse_result.asList()[0]
|
||||
|
||||
def filter(self,objectList):
|
||||
"""
|
||||
Filter give object list through th already constructed complex selector object
|
||||
"""
|
||||
return self.mySelector.filter(objectList)
|
105
Libs/cadquery/changes.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
Changes
|
||||
=======
|
||||
|
||||
|
||||
v0.1
|
||||
-----
|
||||
* Initial Version
|
||||
|
||||
v0.1.6
|
||||
-----
|
||||
* Added STEP import and supporting tests
|
||||
|
||||
v0.1.7
|
||||
-----
|
||||
* Added revolve operation and supporting tests
|
||||
* Fixed minor documentation errors
|
||||
|
||||
v0.1.8
|
||||
-----
|
||||
* Added toFreecad() function as a convenience for val().wrapped
|
||||
* Converted all examples to use toFreecad()
|
||||
* Updated all version numbers that were missed before
|
||||
* Fixed import issues in Windows caused by fc_import
|
||||
* Added/fixed Mac OS support
|
||||
* Improved STEP import
|
||||
* Fixed bug in rotateAboutCenter that negated its effect on solids
|
||||
* Added Travis config (thanks @krasin)
|
||||
* Removed redundant workplane.py file left over from the PParts.com migration
|
||||
* Fixed toWorldCoordinates bug in moveTo (thanks @xix-xeaon)
|
||||
* Added new tests for 2D drawing functions
|
||||
* Integrated Coveralls.io, with a badge in README.md
|
||||
* Integrated version badge in README.md
|
||||
|
||||
v0.2.0
|
||||
-----
|
||||
* Fixed versioning to match the semantic versioning scheme
|
||||
* Added license badge in changes.md
|
||||
* Fixed Solid.makeSphere implementation
|
||||
* Added CQ.sphere operation that mirrors CQ.box
|
||||
* Updated copyright dates
|
||||
* Cleaned up spelling and misc errors in docstrings
|
||||
* Fixed FreeCAD import error on Arch Linux (thanks @moeb)
|
||||
* Made FreeCAD import report import error instead of silently failing (thanks @moeb)
|
||||
* Added ruled option for the loft operation (thanks @hyOzd)
|
||||
* Fixed close() not working in planes other than XY (thanks @hyOzd)
|
||||
* Added box selector with bounding box option (thanks @hyOzd)
|
||||
* CQ.translate and CQ.rotate documentation fixes (thanks @hyOzd)
|
||||
* Fixed centering of a sphere
|
||||
* Increased test coverage
|
||||
* Added a clean function to keep some operations from failing on solids that need simplified (thanks @hyOzd)
|
||||
* Added a mention of the new Google Group to the readme
|
||||
|
||||
v0.3.0
|
||||
-----
|
||||
* Fixed a bug where clean() could not be called on appropriate objects other than solids (thanks @hyOzd) #108
|
||||
* Implemented new selectors that allow existing selectors to be combined with arithmetic/boolean operations (thanks @hyOzd) #110
|
||||
* Fixed a bug where only 1 random edge was returned with multiple min/max selector matches (thanks @hyOzd) #111
|
||||
* Implemented the creation of a workplane from multiple co-planar faces (thanks @hyOzd) #113
|
||||
* Fixed the operation of Center() when called on a compound with multiple solids
|
||||
* Add the named planes ZX YX ZY to define different normals (thanks @galou) #115
|
||||
* Code cleanup in accordance with PEP 8 (thanks @galou)
|
||||
* Fixed a bug with the close function not resetting the first point of the context correctly (thanks @huskier)
|
||||
* Fixed the findSolid function so that it handles compounds #107
|
||||
* Changed the polyline function so that it adds edges to the stack instead of a wire #102
|
||||
* Add the ability to find the center of the bounding box, rather than the center of mass (thanks @huskier) #122
|
||||
* Changed normalize function to normalized to match OCC/PythonOCC nomenclature #124
|
||||
* Added a label attribute to all freecad_impl.shapes so that they can have IDs attached to them #124
|
||||
|
||||
v0.4.0
|
||||
------
|
||||
* Added Documentation, which is available on dcowden.github.io/cadquery
|
||||
* Added CQGI, an adapter API that standardizes use of cadquery from within structured execution environments
|
||||
* Added ability to import STEP files from a web URL (thanks @huskier ) #128
|
||||
|
||||
v0.4.1
|
||||
------
|
||||
* Minor CQGI updates
|
||||
|
||||
v0.5.0-stable
|
||||
------
|
||||
* Configuring Travis to push to PyPI on version releases.
|
||||
|
||||
v0.5.1
|
||||
------
|
||||
* Mirroring fixes (thanks @huskier)
|
||||
* Added a mirroring example (thanks @huskier)
|
||||
|
||||
v0.5.2
|
||||
------
|
||||
* Added the sweep operation #33
|
||||
|
||||
v1.0.0
|
||||
------
|
||||
* Added an option to do symmetric extrusion about the workplane (thanks @adam-urbanczyk)
|
||||
* Extended selector syntax to include Nth selector and re-implemented selectors using pyparsing (thanks @adam-urbanczyk)
|
||||
* Added logical operations to string selectors (thanks @adam-urbanczyk)
|
||||
* Cleanup of README.md and changes.md (thanks @baoboa)
|
||||
* Fixed bugs with toVector and Face 'Not Defined' errors (thanks @huskier)
|
||||
* Refactor of the initialization code for PEP8 compliance and Python 3 compatibility (thanks @Peque)
|
||||
* Making sure that the new pyparsing library dependency is handled properly (thanks @Peque)
|
||||
|
||||
v1.1.0 (Unreleased)
|
||||
------
|
||||
* Fixes and addition of graphical examples for selectors (thanks @adam-urbanczyk)
|
||||
* Added intersect operation (thanks @fragmuffin)
|
72
Libs/cadquery/conda-py3-freecad.yml
Normal file
|
@ -0,0 +1,72 @@
|
|||
name: cq-freecad
|
||||
channels: !!python/tuple
|
||||
- !!python/unicode
|
||||
'defaults'
|
||||
dependencies:
|
||||
- certifi=2016.2.28=py36_0
|
||||
- conda-forge::boost=1.64.0=py36_4
|
||||
- conda-forge::boost-cpp=1.64.0=1
|
||||
- conda-forge::bzip2=1.0.6=1
|
||||
- conda-forge::curl=7.54.1=0
|
||||
- conda-forge::cycler=0.10.0=py36_0
|
||||
- conda-forge::fontconfig=2.12.1=4
|
||||
- conda-forge::freeimage=3.17.0=0
|
||||
- conda-forge::freetype=2.7=1
|
||||
- conda-forge::future=0.16.0=py36_0
|
||||
- conda-forge::git=2.14.1=1
|
||||
- conda-forge::hdf5=1.8.18=0
|
||||
- conda-forge::icu=58.1=1
|
||||
- conda-forge::jsoncpp=0.10.6=1
|
||||
- conda-forge::krb5=1.14.2=0
|
||||
- conda-forge::libssh2=1.8.0=1
|
||||
- conda-forge::libtiff=4.0.6=7
|
||||
- conda-forge::libxslt=1.1.29=5
|
||||
- conda-forge::matplotlib=1.5.3=np113py36_8
|
||||
- conda-forge::occt=7.1.0=occt7.1.0_1
|
||||
- conda-forge::pyparsing=2.2.0=py36_0
|
||||
- conda-forge::pyqt=4.11.4=py36_2
|
||||
- conda-forge::pyside=1.2.4=py36_8
|
||||
- conda-forge::python-dateutil=2.6.1=py36_0
|
||||
- conda-forge::pytz=2017.2=py36_0
|
||||
- conda-forge::sip=4.18=py36_1
|
||||
- conda-forge::six=1.11.0=py36_1
|
||||
- conda-forge::tbb=2018_20170919=0
|
||||
- conda-forge::tornado=4.5.2=py36_0
|
||||
- conda-forge::vtk=7.1.1=py36_202
|
||||
- conda-forge::xerces-c=3.1.4=3
|
||||
- dbus=1.10.20=0
|
||||
- expat=2.1.0=0
|
||||
- freecad::coin3d=4.0.0=5
|
||||
- freecad::freecad=0.17=py36_occt7.1.0_5
|
||||
- freecad::libmed=3.1.0=2
|
||||
- freecad::netgen=6.2=py36_occt7.1.0_3
|
||||
- freecad::pivy=0.6.2=py36_dev_4
|
||||
- freecad::simage=1.7.0=0
|
||||
- freecad::soqt=1.5.0=0
|
||||
- glib=2.50.2=1
|
||||
- gst-plugins-base=1.8.0=0
|
||||
- gstreamer=1.8.0=0
|
||||
- jpeg=9b=0
|
||||
- libffi=3.2.1=1
|
||||
- libgcc=5.2.0=0
|
||||
- libgfortran=3.0.0=1
|
||||
- libiconv=1.14=0
|
||||
- libpng=1.6.30=1
|
||||
- libxcb=1.12=1
|
||||
- libxml2=2.9.4=0
|
||||
- mkl=2017.0.3=0
|
||||
- mock=2.0.0=py36_0
|
||||
- numpy=1.13.1=py36_0
|
||||
- openssl=1.0.2l=0
|
||||
- pbr=1.10.0=py36_0
|
||||
- pcre=8.39=1
|
||||
- pip=9.0.1=py36_1
|
||||
- python=3.6.2=0
|
||||
- qt=4.8.7=3
|
||||
- readline=6.2=2
|
||||
- setuptools=36.4.0=py36_1
|
||||
- sqlite=3.13.0=0
|
||||
- tk=8.5.18=0
|
||||
- wheel=0.29.0=py36_0
|
||||
- xz=5.2.3=0
|
||||
- zlib=1.2.11=0
|
196
Libs/cadquery/cq_cmd.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
#
|
||||
# cadquery command line interface
|
||||
# usage: cq_cmd [-h] [--param-file inputfile ] [--format STEP|STL ] [--output filename] filename
|
||||
# if input file contains multiple inputs, multiple outputs are created
|
||||
# if no input filename is provided, stdin is read instead
|
||||
# if no output filename is provided, output goes to stdout
|
||||
# default output format is STEP
|
||||
#
|
||||
|
||||
import sys,os
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from cadquery import cqgi,exporters
|
||||
import argparse
|
||||
import os.path
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
class FilepathShapeWriter(object):
|
||||
#a shape writer that writes a new file in a directory for each object
|
||||
def __init__(self,file_pattern, shape_format):
|
||||
self.shape_format=shape_format
|
||||
self.file_pattern=file_pattern
|
||||
self.counter = 1
|
||||
|
||||
def _compute_file_name(self):
|
||||
return self.file_pattern % ({ "counter": self.counter,"format": self.shape_format } )
|
||||
|
||||
def write_shapes(self,shape_list):
|
||||
for result in shape_list:
|
||||
shape = result.shape
|
||||
file_name = self._compute_file_name()
|
||||
info("Writing %s Output to '%s'" % (self.shape_format, file_name))
|
||||
s = open(file_name,'w')
|
||||
exporters.exportShape(shape,self.shape_format,s)
|
||||
s.flush()
|
||||
s.close()
|
||||
|
||||
class StdoutShapeWriter(object):
|
||||
#has extra code to prevent freecad crap from junking up stdout
|
||||
def __init__(self,shape_format ):
|
||||
self.shape_format = shape_format
|
||||
|
||||
def write_shapes(self,shape_list):
|
||||
#f = open('/tmp/cqtemp','w')
|
||||
#with suppress_stdout_stderr():
|
||||
exporters.exportShape(shape_list[0].shape,self.shape_format,sys.stdout)
|
||||
#f.flush()
|
||||
#f.close()
|
||||
#f = open('/tmp/cqtemp')
|
||||
#sys.stdout.write(f.read())
|
||||
|
||||
|
||||
def create_shape_writer(out_spec,shape_format):
|
||||
if out_spec == 'stdout':
|
||||
return StdoutShapeWriter(shape_format)
|
||||
else:
|
||||
return FilepathShapeWriter(out_spec,shape_format)
|
||||
|
||||
class ErrorCodes(object):
|
||||
SCRIPT_ERROR=2
|
||||
UNKNOWN_ERROR=3
|
||||
INVALID_OUTPUT_DESTINATION=4
|
||||
INVALID_OUTPUT_FORMAT=5
|
||||
INVALID_INPUT=6
|
||||
MULTIPLE_RESULTS=7
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
def error(msg,exit_code=1):
|
||||
eprint("ERROR %d: %s" %( exit_code, msg))
|
||||
sys.exit(exit_code)
|
||||
|
||||
def warning(msg):
|
||||
eprint("WARNING: %s" % msg)
|
||||
|
||||
def info(msg):
|
||||
eprint("INFO: %s" % msg)
|
||||
|
||||
def read_file_as_string(file_name):
|
||||
if os.path.isfile(file_name):
|
||||
f = open(file_name)
|
||||
s = f.read()
|
||||
f.close()
|
||||
return s
|
||||
else:
|
||||
return None
|
||||
|
||||
class ParameterHandler(object):
|
||||
def __init__(self):
|
||||
self.params = {}
|
||||
|
||||
def apply_file(self,file_spec):
|
||||
if file_spec is not None:
|
||||
p = read_file_as_string(file_spec)
|
||||
if p is not None:
|
||||
d = json.loads(p)
|
||||
self.params.update(d)
|
||||
|
||||
def apply_string(self,param_string):
|
||||
if param_string is not None:
|
||||
r = json.loads(param_string)
|
||||
self.params.update(r)
|
||||
|
||||
def get(self):
|
||||
return self.params
|
||||
|
||||
def read_input_script(file_name):
|
||||
if file_name != "stdin":
|
||||
s = read_file_as_string(file_name)
|
||||
if s is None:
|
||||
error("%s does not appear to be a readable file." % file_name,ErrorCodes.INVALID_INPUT)
|
||||
else:
|
||||
return s
|
||||
else:
|
||||
s = sys.stdin.read()
|
||||
return s
|
||||
|
||||
def check_input_format(input_format):
|
||||
valid_types = [exporters.ExportTypes.TJS,exporters.ExportTypes.AMF,
|
||||
exporters.ExportTypes.STEP,exporters.ExportTypes.STL,exporters.ExportTypes.TJS ]
|
||||
if input_format not in valid_types:
|
||||
error("Invalid Input format '%s'. Valid values: %s" % ( input_format, str(valid_types)) ,
|
||||
ErrorCodes.INVALID_OUTPUT_FORMAT)
|
||||
|
||||
|
||||
def describe_parameters(user_params, script_params):
|
||||
if len(script_params)> 0:
|
||||
parameter_names = ",".join(list(script_params.keys()))
|
||||
info("This script provides parameters %s, which can be customized at build time." % parameter_names)
|
||||
else:
|
||||
info("This script provides no customizable build parameters.")
|
||||
if len(user_params) > 0:
|
||||
info("User Supplied Parameter Values ( Override Model Defaults):")
|
||||
for k,v in user_params.items():
|
||||
info("\tParameter: %s=%s" % (k,v))
|
||||
else:
|
||||
info("The script will run with default variable values")
|
||||
info("use --param_file to provide a json file that contains values to override the defaults")
|
||||
|
||||
def run(args):
|
||||
|
||||
info("Reading from file '%s'" % args.in_spec)
|
||||
input_script = read_input_script(args.in_spec)
|
||||
script_name = 'stdin' if args.in_spec is None else args.in_spec
|
||||
cq_model = cqgi.parse(input_script)
|
||||
info("Parsed Script '%s'." % script_name)
|
||||
|
||||
param_handler = ParameterHandler()
|
||||
param_handler.apply_file(args.param_file)
|
||||
param_handler.apply_string(args.params)
|
||||
user_params = param_handler.get()
|
||||
describe_parameters(user_params,cq_model.metadata.parameters)
|
||||
|
||||
check_input_format(args.format)
|
||||
|
||||
build_result = cq_model.build(build_parameters=user_params)
|
||||
|
||||
info("Output Format is '%s'. Use --output-format to change it." % args.format)
|
||||
info("Output Path is '%s'. Use --out_spec to change it." % args.out_spec)
|
||||
|
||||
if build_result.success:
|
||||
result_list = build_result.results
|
||||
info("Script Generated %d result Objects" % len(result_list))
|
||||
shape_writer = create_shape_writer(args.out_spec,args.format)
|
||||
shape_writer.write_shapes(result_list)
|
||||
else:
|
||||
error("Script Error: '%s'" % build_result.exception,ErrorCodes.SCRIPT_ERROR)
|
||||
|
||||
if __name__=='__main__':
|
||||
|
||||
desc="""
|
||||
CQ CMD. Runs a cadquery python file, and produces a 3d object.
|
||||
A script can be provided as a file or as standard input.
|
||||
Each object created by the script is written the supplied output directory.
|
||||
"""
|
||||
filename_pattern_help="""
|
||||
Filename pattern to use when creating output files.
|
||||
The sequential file number and the format are available.
|
||||
Default: cqobject-%%(counter)d.%%(format)s
|
||||
Use stdout to write to stdout ( can't be used for multiple results though)
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description=desc)
|
||||
parser.add_argument("--format", action="store",default="STEP",help="Output Object format (TJS|STEP|STL|SVG)")
|
||||
parser.add_argument("--param_file", action="store",help="Parameter Values File, in JSON format")
|
||||
parser.add_argument("--params",action="store", help="JSON encoded parameter values. They override values provided in param_file")
|
||||
parser.add_argument("--in_spec", action="store", required=True, help="Input File path. Use stdin to read standard in")
|
||||
parser.add_argument("--out_spec", action="store",default="./cqobject-%(counter)d.%(format)s",help=filename_pattern_help)
|
||||
args = parser.parse_args()
|
||||
run(args)
|
33
Libs/cadquery/cq_cmd.sh
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
# NOTE
|
||||
# the -u (unbuffered flag) in the below is very important
|
||||
# without it, the FreeCAD libraries somehow manage to get some stdout
|
||||
# junking up output when stdout is used.
|
||||
# this is the script we use
|
||||
# to select between running a build server
|
||||
# and a command line job runner
|
||||
if [ -z "$1" ]; then
|
||||
echo "************************"
|
||||
echo "CadQuery Docker Image"
|
||||
echo "************************"
|
||||
echo "Usage: docker run cadquery build [options]"
|
||||
echo "Examples:"
|
||||
echo " Read a model from stdin, write output to stdout"
|
||||
echo ""
|
||||
echo " cat cadquery_script.py | sudo docker run -i dcowden/cadquery:latest --in_spec stdin --out_spec stdout > my_object.STEP"
|
||||
echo " "
|
||||
echo " Mount a directory, and write results into the local directory"
|
||||
echo ""
|
||||
echo " sudo docker run -i dcowden/cadquery:latest --in_spec my_script.py"
|
||||
echo ""
|
||||
exec python -u /opt/cadquery/cq_cmd.py -h
|
||||
exit 1
|
||||
fi;
|
||||
if [ "$1" == "build" ]; then
|
||||
exec python -u /opt/cadquery/cq_cmd.py "${@:2}"
|
||||
fi;
|
||||
if [ "$1" == "runserver" ]; then
|
||||
echo "Future CadQuery Server"
|
||||
exit 1
|
||||
#exec python -u /opt/cadquery/cq_server.py "${@:2}"
|
||||
fi;
|
2
Libs/cadquery/doc/README
Normal file
|
@ -0,0 +1,2 @@
|
|||
This documentation should be generated with sphinxdoc.
|
||||
see ../build-docs.sh
|
BIN
Libs/cadquery/doc/_static/ParametricPulley.PNG
vendored
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
Libs/cadquery/doc/_static/block.png
vendored
Normal file
After Width: | Height: | Size: 9.2 KiB |
404
Libs/cadquery/doc/_static/cadquery_cheatsheet.html
vendored
Normal file
|
@ -0,0 +1,404 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>CadQuery Cheatsheet</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
|
||||
<style type="text/css">
|
||||
.section {
|
||||
margin: 0.5em;
|
||||
padding: 0px 0.5em 0.5em;
|
||||
background-color: #EBEBEB;
|
||||
}
|
||||
.column {
|
||||
float: left;
|
||||
width: 375px;
|
||||
}
|
||||
tr {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
width: 5em;
|
||||
}
|
||||
h2 {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="column" style="width:475px;">
|
||||
<div class="section">
|
||||
<h2>Documentation</h2>
|
||||
<ul style="background-color:#ffffff;margin-bottom:0px;margin-top:0px;">
|
||||
<li><a href="http://parametricparts.com/docs/#">ParametricParts Documentation</a></li>
|
||||
<li><a href="https://github.com/dcowden/cadquery/blob/master/README.md">CadQuery Readme</a></li>
|
||||
<li><a href="http://parametricparts.com/docs/examples.html#examples">CadQuery Examples</a></li>
|
||||
<li><a href="http://parametricparts.com/docs/classreference.html">CadQuery Class Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>BREP Terminology</h2><br />
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<td style="width:10%;"><strong>vertex</strong></td>
|
||||
<td style="width:90%;">A single point in space</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>edge</strong></td>
|
||||
<td>A connection between two or more vertices along a particular path (called a curve)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>wire</strong></td>
|
||||
<td>A collection of edges that are connected together</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>face</strong></td>
|
||||
<td>A set of edges or wires that enclose a surface</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>shell</strong></td>
|
||||
<td>A collection of faces that are connected together along some of their edges</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>solid</strong></td>
|
||||
<td>A shell that has a closed interior</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>compound</strong></td>
|
||||
<td>A collection of solids</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Named Planes</h2><br />
|
||||
Available named planes are as follows. Direction references refer to the global directions.
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:25%;">Name</th>
|
||||
<th style="width:25%;">xDir</th>
|
||||
<th style="width:25%;">yDir</th>
|
||||
<th style="width:25%;">zDir</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XY</td>
|
||||
<td>+x</td>
|
||||
<td>+y</td>
|
||||
<td>+z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>YZ</td>
|
||||
<td>+y</td>
|
||||
<td>+z</td>
|
||||
<td>+x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XZ</td>
|
||||
<td>+x</td>
|
||||
<td>+z</td>
|
||||
<td>-y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>front</td>
|
||||
<td>+x</td>
|
||||
<td>+y</td>
|
||||
<td>+z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>back</td>
|
||||
<td>-x</td>
|
||||
<td>+y</td>
|
||||
<td>-z</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>left</td>
|
||||
<td>+z</td>
|
||||
<td>+y</td>
|
||||
<td>-x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>right</td>
|
||||
<td>-z</td>
|
||||
<td>+y</td>
|
||||
<td>+x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>top</td>
|
||||
<td>+x</td>
|
||||
<td>-z</td>
|
||||
<td>+y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bottom</td>
|
||||
<td>+x</td>
|
||||
<td>+z</td>
|
||||
<td>-y</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Core Classes</h2><br />
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:40%;">Class</th>
|
||||
<th style="width:60%;">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CQ(obj)</td>
|
||||
<td>Provides enhanced functionality for a wrapped CAD primitive.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Plane(origin, xDir, normal)</td>
|
||||
<td>A 2d coordinate system in space, with the x-y axes on the a plane, and a particular point as the origin.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Workplane(inPlane[origin, obj])</td>
|
||||
<td>Defines a coordinate system in space, in which 2-d coordinates can be used.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column" style="width:600px;">
|
||||
<div class="section">
|
||||
<h2>Selector Methods</h2><br />
|
||||
CadQuery selector strings allow filtering various types of object lists.
|
||||
Most commonly, Edges, Faces, and Vertices are used, but all objects types can be filtered.<br />
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:40%;">Selector Method</th>
|
||||
<th style="width:60%;">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="http://parametricparts.com/docs/classreference.html#cadfile.cadutils.cadquery.CQ.faces">CQ.faces(selector=None)</a></td>
|
||||
<td>Select the faces of objects on the stack, optionally filtering the selection.</td>
|
||||
<tr>
|
||||
<td><a href="http://parametricparts.com/docs/classreference.html#cadfile.cadutils.cadquery.CQ.edges">CQ.edges(selector=None)</a></td>
|
||||
<td>Select the edges of objects on the stack, optionally filtering the selection.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="http://parametricparts.com/docs/classreference.html#cadfile.cadutils.cadquery.CQ.vertices">CQ.vertices(selector=None)</a></td>
|
||||
<td>Select the vertices of objects on the stack, optionally filtering the selection.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="http://parametricparts.com/docs/classreference.html#cadfile.cadutils.cadquery.CQ.solids">CQ.solids(selector=None)</a></td>
|
||||
<td>Select the solids of objects on the stack, optionally filtering the selection.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="http://parametricparts.com/docs/classreference.html#cadfile.cadutils.cadquery.CQ.shells">CQ.shells(selector=None)</a></td>
|
||||
<td>Select the shells of objects on the stack, optionally filtering the selection.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Selector Classes</h2><br />
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:40%;">Class</th>
|
||||
<th style="width:60%;">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NearestToPointSelector(pnt)</td>
|
||||
<td>Selects object nearest the provided point.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ParallelDirSelector(vector[tolerance])</td>
|
||||
<td>Selects objects parallel with the provided direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DirectionSelector(vector[tolerance])</td>
|
||||
<td>Selects objects aligned with the provided direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PerpendicularDirSelector(vector[tolerance])</td>
|
||||
<td>Selects objects perpendicular with the provided direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TypeSelector(typeString)</td>
|
||||
<td>Selects objects of the prescribed topological type.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DirectionMinMaxSelector(vector[directionMax])</td>
|
||||
<td>Selects objects closest or farthest in the specified direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>StringSyntaxSelector(selectorString)</td>
|
||||
<td>Filter lists objects using a simple string syntax.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Selector String Modifiers</h2><br />
|
||||
Selectors are a complex topic: see <a href="http://parametricparts.com/docs/selectors.html">CadQuery String Selectors</a> for more information.<br />
|
||||
Axis Strings are: X, Y, Z, XY, YZ, XZ
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:10%;">Modifier</th>
|
||||
<th style="width:90%;">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>|</td>
|
||||
<td>Parallel to (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=paralleldirselector#cadfile.cadutils.cadquery.ParallelDirSelector">ParallelDirSelector</a>). Can return multiple objects.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td>Perpendicular to (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=perpendiculardirselector#cadfile.cadutils.cadquery.PerpendicularDirSelector">PerpendicularDirSelector</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>+</td>
|
||||
<td>Positive direction (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=directionselector#cadfile.cadutils.cadquery.DirectionSelector">DirectionSelector</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>Negative direction (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=directionselector#cadfile.cadutils.cadquery.DirectionSelector">DirectionSelector</a>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>></td>
|
||||
<td>Maximize (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=directionminmaxselector#cadfile.cadutils.cadquery.DirectionMinMaxSelector">DirectionMinMaxSelector</a> with directionMax=True)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><</td>
|
||||
<td>Minimize (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=directionminmaxselector#cadfile.cadutils.cadquery.DirectionMinMaxSelector">DirectionMinMaxSelector</a> with directionMax=False)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>%</td>
|
||||
<td>Curve/surface type (same as <a href="http://parametricparts.com/docs/classreference.html?highlight=typeselector#cadfile.cadutils.cadquery.TypeSelector">TypeSelector</a>)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Examples of Filtering Faces</h2><br />
|
||||
All types of filters work on faces. In most cases, the selector refers to the direction of the normal vector of the face.
|
||||
If a face is not planar, selectors are evaluated at the center of mass of the face. This can lead to results that are quite unexpected.
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:10%;">Selector</th>
|
||||
<th style="width:40%;">Selector Class</th>
|
||||
<th style="width:40%;">Selects</th>
|
||||
<th style="width:10%;"># Objects Returned</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>+Z</td>
|
||||
<td>DirectionSelector</td>
|
||||
<td>Faces with normal in +z direction</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>|Z</td>
|
||||
<td>ParallelDirSelector</td>
|
||||
<td>Faces parallel to xy plane</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-X</td>
|
||||
<td>DirectionSelector</td>
|
||||
<td>Faces with normal in neg x direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#Z</td>
|
||||
<td>PerpendicularDirSelector</td>
|
||||
<td>Faces perpendicular to z direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>%Plane</td>
|
||||
<td>TypeSelector</td>
|
||||
<td>Faces of type plane</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>>Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Face farthest in the positive y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Face farthest in the negative y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Examples of Filtering Edges</h2><br />
|
||||
Some filter types are not supported for edges. The selector usually refers to the direction of the edge.
|
||||
Non-linear edges are not selected for any selectors except type (%). Non-linear edges are never returned when these filters are applied.
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:10%;">Selector</th>
|
||||
<th style="width:40%;">Selector Class</th>
|
||||
<th style="width:40%;">Selects</th>
|
||||
<th style="width:10%;"># Objects Returned</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>+Z</td>
|
||||
<td>DirectionSelector</td>
|
||||
<td>Edges aligned in the Z direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>|Z</td>
|
||||
<td>ParallelDirSelector</td>
|
||||
<td>Edges parallel to z direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>-X</td>
|
||||
<td>DirectionSelector</td>
|
||||
<td>Edges aligned in neg x direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#Z</td>
|
||||
<td>PerpendicularDirSelector</td>
|
||||
<td>Edges perpendicular to z direction</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>%Plane</td>
|
||||
<td>TypeSelector</td>
|
||||
<td>Edges type line</td>
|
||||
<td>0..many</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>>Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Edges farthest in the positive y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Edges farthest in the negative y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Examples of Filtering Vertices</h2><br />
|
||||
Only a few of the filter types apply to vertices. The location of the vertex is the subject of the filter.
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th style="width:10%;">Selector</th>
|
||||
<th style="width:40%;">Selector Class</th>
|
||||
<th style="width:40%;">Selects</th>
|
||||
<th style="width:10%;"># Objects Returned</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>>Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Vertices farthest in the positive y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><Y</td>
|
||||
<td>DirectionMinMaxSelector</td>
|
||||
<td>Vertices farthest in the negative y dir</td>
|
||||
<td>0 or 1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
Libs/cadquery/doc/_static/cqlogo.png
vendored
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
Libs/cadquery/doc/_static/hyOzd-cablefix.png
vendored
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
Libs/cadquery/doc/_static/hyOzd-finished.jpg
vendored
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
Libs/cadquery/doc/_static/new_badge.png
vendored
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
Libs/cadquery/doc/_static/parametric-cup-screencap.PNG
vendored
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
Libs/cadquery/doc/_static/parametric-pillowblock-screencap.png
vendored
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
Libs/cadquery/doc/_static/pillowblock.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Libs/cadquery/doc/_static/quickstart-1.png
vendored
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
Libs/cadquery/doc/_static/quickstart-2.png
vendored
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
Libs/cadquery/doc/_static/quickstart-3.png
vendored
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
Libs/cadquery/doc/_static/quickstart-4.png
vendored
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
Libs/cadquery/doc/_static/quickstart-5.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Libs/cadquery/doc/_static/quickstart.png
vendored
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/000.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/001.png
vendored
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/002.png
vendored
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/003.png
vendored
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/004.png
vendored
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
Libs/cadquery/doc/_static/quickstart/005.png
vendored
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
Libs/cadquery/doc/_static/simpleblock.png
vendored
Normal file
After Width: | Height: | Size: 8.5 KiB |
169
Libs/cadquery/doc/apireference.rst
Normal file
|
@ -0,0 +1,169 @@
|
|||
.. _apireference:
|
||||
|
||||
***********************
|
||||
CadQuery API Reference
|
||||
***********************
|
||||
|
||||
The CadQuery API is made up of 3 main objects:
|
||||
|
||||
* **CQ** - An object that wraps a topological entity.
|
||||
* **Workplane** -- A subclass of CQ, that applies in a 2-D modelling context.
|
||||
* **Selector** -- Filter and select things
|
||||
|
||||
This page lists methods of these objects grouped by **functional area**
|
||||
|
||||
.. seealso::
|
||||
This page lists api methods grouped by functional area.
|
||||
Use :ref:`classreference` to see methods alphabetically by class.
|
||||
|
||||
|
||||
Initialization
|
||||
----------------
|
||||
|
||||
.. currentmodule:: cadquery
|
||||
|
||||
Creating new workplanes and object chains
|
||||
|
||||
.. autosummary::
|
||||
CQ
|
||||
Workplane
|
||||
|
||||
|
||||
.. _2dOperations:
|
||||
|
||||
2-d Operations
|
||||
-----------------
|
||||
|
||||
Creating 2-d constructs that can be used to create 3 d features.
|
||||
|
||||
All 2-d operations require a **Workplane** object to be created.
|
||||
|
||||
.. currentmodule:: cadquery
|
||||
|
||||
.. autosummary::
|
||||
Workplane.center
|
||||
Workplane.lineTo
|
||||
Workplane.line
|
||||
Workplane.vLine
|
||||
Workplane.vLineTo
|
||||
Workplane.hLine
|
||||
Workplane.moveTo
|
||||
Workplane.move
|
||||
Workplane.spline
|
||||
Workplane.threePointArc
|
||||
Workplane.rotateAndCopy
|
||||
Workplane.mirrorY
|
||||
Workplane.mirrorX
|
||||
Workplane.wire
|
||||
Workplane.rect
|
||||
Workplane.circle
|
||||
Workplane.polyline
|
||||
Workplane.close
|
||||
Workplane.rarray
|
||||
|
||||
.. _3doperations:
|
||||
|
||||
3-d Operations
|
||||
-----------------
|
||||
|
||||
Some 3-d operations also require an active 2-d workplane, but some do not.
|
||||
|
||||
3-d operations that require a 2-d workplane to be active:
|
||||
|
||||
.. autosummary::
|
||||
Workplane.cboreHole
|
||||
Workplane.cskHole
|
||||
Workplane.hole
|
||||
Workplane.extrude
|
||||
Workplane.cut
|
||||
Workplane.cutBlind
|
||||
Workplane.cutThruAll
|
||||
Workplane.box
|
||||
Workplane.union
|
||||
Workplane.combine
|
||||
|
||||
3-d operations that do NOT require a 2-d workplane to be active:
|
||||
|
||||
.. autosummary::
|
||||
CQ.shell
|
||||
CQ.fillet
|
||||
CQ.split
|
||||
CQ.rotate
|
||||
CQ.rotateAboutCenter
|
||||
CQ.translate
|
||||
|
||||
File Management and Export
|
||||
---------------------------------
|
||||
|
||||
.. autosummary::
|
||||
CQ.toSvg
|
||||
CQ.exportSvg
|
||||
|
||||
|
||||
.. autosummary::
|
||||
importers.importStep
|
||||
exporters.exportShape
|
||||
|
||||
|
||||
Iteration Methods
|
||||
------------------
|
||||
|
||||
Methods that allow iteration over the stack or objects
|
||||
|
||||
.. autosummary::
|
||||
Workplane.each
|
||||
Workplane.eachpoint
|
||||
|
||||
|
||||
.. _stackMethods:
|
||||
|
||||
Stack and Selector Methods
|
||||
------------------------------
|
||||
|
||||
CadQuery methods that operate on the stack
|
||||
|
||||
.. autosummary::
|
||||
CQ.all
|
||||
CQ.size
|
||||
CQ.vals
|
||||
CQ.add
|
||||
CQ.val
|
||||
CQ.first
|
||||
CQ.item
|
||||
CQ.last
|
||||
CQ.end
|
||||
CQ.vertices
|
||||
CQ.faces
|
||||
CQ.edges
|
||||
CQ.wires
|
||||
CQ.solids
|
||||
CQ.shells
|
||||
CQ.compounds
|
||||
|
||||
.. _selectors:
|
||||
|
||||
Selectors
|
||||
------------------------
|
||||
|
||||
Objects that filter and select CAD objects. Selectors are used to select existing geometry
|
||||
as a basis for futher operations.
|
||||
|
||||
.. currentmodule:: cadquery
|
||||
|
||||
.. autosummary::
|
||||
|
||||
NearestToPointSelector
|
||||
BoxSelector
|
||||
BaseDirSelector
|
||||
ParallelDirSelector
|
||||
DirectionSelector
|
||||
DirectionNthSelector
|
||||
PerpendicularDirSelector
|
||||
TypeSelector
|
||||
DirectionMinMaxSelector
|
||||
BinarySelector
|
||||
AndSelector
|
||||
SumSelector
|
||||
SubtractSelector
|
||||
InverseSelector
|
||||
StringSyntaxSelector
|
71
Libs/cadquery/doc/classreference.rst
Normal file
|
@ -0,0 +1,71 @@
|
|||
.. _classreference:
|
||||
|
||||
*************************
|
||||
CadQuery Class Summary
|
||||
*************************
|
||||
|
||||
This page documents all of the methods and functions of the CadQuery classes, organized alphabatically.
|
||||
|
||||
.. seealso::
|
||||
|
||||
For a listing organized by functional area, see the :ref:`apireference`
|
||||
|
||||
.. currentmodule:: cadquery
|
||||
|
||||
Core Classes
|
||||
---------------------
|
||||
|
||||
.. autosummary::
|
||||
CQ
|
||||
Workplane
|
||||
|
||||
Topological Classes
|
||||
----------------------
|
||||
|
||||
.. autosummary::
|
||||
Shape
|
||||
Vertex
|
||||
Edge
|
||||
Wire
|
||||
Face
|
||||
Shell
|
||||
Solid
|
||||
Compound
|
||||
|
||||
Geometry Classes
|
||||
------------------
|
||||
|
||||
.. autosummary::
|
||||
Vector
|
||||
Matrix
|
||||
Plane
|
||||
|
||||
Selector Classes
|
||||
---------------------
|
||||
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Selector
|
||||
NearestToPointSelector
|
||||
BoxSelector
|
||||
BaseDirSelector
|
||||
ParallelDirSelector
|
||||
DirectionSelector
|
||||
DirectionNthSelector
|
||||
PerpendicularDirSelector
|
||||
TypeSelector
|
||||
DirectionMinMaxSelector
|
||||
BinarySelector
|
||||
AndSelector
|
||||
SumSelector
|
||||
SubtractSelector
|
||||
InverseSelector
|
||||
StringSyntaxSelector
|
||||
|
||||
|
||||
Class Details
|
||||
---------------
|
||||
|
||||
.. automodule:: cadquery
|
||||
:members:
|
276
Libs/cadquery/doc/conf.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# CadQuery documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Aug 25 21:10:53 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import os.path
|
||||
#print "working path is %s" % os.getcwd()
|
||||
#sys.path.append("../cadquery")
|
||||
import cadquery
|
||||
|
||||
#settings._target = None
|
||||
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary','cadquery.cq_directive']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'CadQuery'
|
||||
copyright = 'Parametric Products Intellectual Holdings LLC, All Rights Reserved'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#html_theme = 'timlinux-linfiniti-sphinx'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {
|
||||
# "headerfont": "'Open Sans',Arial,sans-serif",
|
||||
# #"bodyfont:": "'Open Sans',Arial,sans-serif",
|
||||
# #"headerbg" : "{image: url('/img/bg/body.jpg');color:#000000;}",
|
||||
# "headerbg" : "color:black;",
|
||||
# "footerbg" : "{color:#13171A;}",
|
||||
# "linkcolor": "#84B51E;",
|
||||
## "headercolor1": "#13171A;",
|
||||
# "headercolor2": "#444;",
|
||||
# "headerlinkcolor" : "#13171A;",
|
||||
#}
|
||||
|
||||
#agogo options
|
||||
"""
|
||||
bodyfont (CSS font family): Font for normal text.
|
||||
headerfont (CSS font family): Font for headings.
|
||||
pagewidth (CSS length): Width of the page content, default 70em.
|
||||
documentwidth (CSS length): Width of the document (without sidebar), default 50em.
|
||||
sidebarwidth (CSS length): Width of the sidebar, default 20em.
|
||||
bgcolor (CSS color): Background color.
|
||||
headerbg (CSS value for “background”): background for the header area, default a grayish gradient.
|
||||
footerbg (CSS value for “background”): background for the footer area, default a light gray gradient.
|
||||
linkcolor (CSS color): Body link color.
|
||||
headercolor1, headercolor2 (CSS color): colors for <h1> and <h2> headings.
|
||||
headerlinkcolor (CSS color): Color for the backreference link in headings.
|
||||
textalign (CSS text-align value): Text alignment for the body, default is justify.
|
||||
"""
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = "CadQuery Documentation"
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
html_logo = "_static/cqlogo.png"
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'CadQuerydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'CadQuery.tex', 'CadQuery Documentation',
|
||||
'David Cowden', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'cadquery', 'CadQuery Documentation',
|
||||
['David Cowden'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'CadQuery', 'CadQuery Documentation',
|
||||
'David Cowden', 'CadQuery', 'A Fluent CAD api',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
167
Libs/cadquery/doc/cqgi.rst
Normal file
|
@ -0,0 +1,167 @@
|
|||
.. _cqgi:
|
||||
|
||||
The CadQuery Gateway Interface
|
||||
====================================
|
||||
|
||||
|
||||
CadQuery is first and foremost designed as a library, which can be used as a part of any project.
|
||||
In this context, there is no need for a standard script format or gateway api.
|
||||
|
||||
Though the embedded use case is the most common, several tools have been created which run
|
||||
cadquery scripts on behalf of the user, and then render the result of the script visually.
|
||||
|
||||
These execution environments (EE) generally accept a script and user input values for
|
||||
script parameters, and then display the resulting objects visually to the user.
|
||||
|
||||
Today, three execution environments exist:
|
||||
|
||||
* `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_, which runs scripts
|
||||
inside of the FreeCAD IDE, and displays objects in the display window
|
||||
* the cq-directive, which is used to execute scripts inside of sphinx-doc,
|
||||
producing documented examples that include both a script and an SVG representation of the object that results
|
||||
* `ParametricParts.com <https://www.parametricparts.com>`_, which provides a web-based way to prompt user input for
|
||||
variables, and then display the result output in a web page.
|
||||
|
||||
The CQGI is distributed with cadquery, and standardizes the interface between execution environments and cadquery scripts.
|
||||
|
||||
|
||||
The Script Side
|
||||
-----------------
|
||||
|
||||
CQGI compliant containers provide an execution environment for scripts. The environment includes:
|
||||
|
||||
* the cadquery library is automatically imported as 'cq'.
|
||||
* the :py:meth:`cadquery.cqgi.ScriptCallback.show_object()` method is defined that should be used to export a shape to the execution environment
|
||||
* the :py:meth:`cadquery.cqgi.ScriptCallBack.debug()` method is defined, which can be used by scripts to debug model output during execution.
|
||||
|
||||
Scripts must call build_output at least once. Invoking show_object more than once will send multiple objects to
|
||||
the container. An error will occur if the script does not return an object using the show_object() method.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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::
|
||||
|
||||
base_cube = cq.Workplane('XY').rect(1.0,1.0).extrude(1.0)
|
||||
top_of_cube_plane = base_cube.faces(">Z").workplane()
|
||||
debug(top_of_cube_plane, { 'color': 'yellow', } )
|
||||
debug(top_of_cube_plane.center, { 'color' : 'blue' } )
|
||||
|
||||
circle=top_of_cube_plane.circle(0.5)
|
||||
debug(circle, { 'color': 'red' } )
|
||||
|
||||
show_object( circle.extrude(1.0),{"color": "#aaaaaa" )
|
||||
|
||||
Note that importing cadquery is not required.
|
||||
At the end of this script, one object will be displayed, in addition to a workplane, a point, and a circle
|
||||
|
||||
Future enhancements will include several other methods, used to provide more metadata for the execution environment:
|
||||
* :py:meth:`cadquery.cqgi.ScriptCallback.add_error()`, indicates an error with an input parameter
|
||||
* :py:meth:`cadquery.cqgi.ScriptCallback.describe_parameter()`, provides extra information about a parameter in the script,
|
||||
|
||||
|
||||
The execution environment side
|
||||
-------------------------------
|
||||
|
||||
CQGI makes it easy to run cadquery scripts in a standard way. To run a script from an execution environment,
|
||||
run code like this::
|
||||
|
||||
from cadquery import cqgi
|
||||
|
||||
user_script = ...
|
||||
build_result = cqgi.parse(user_script).build()
|
||||
|
||||
The :py:meth:`cadquery.cqgi.parse()` method returns a :py:class:`cadquery.cqgi.CQModel` object.
|
||||
|
||||
The `metadata`p property of the object contains a `cadquery.cqgi.ScriptMetaData` object, which can be used to discover the
|
||||
user parameters available. This is useful if the execution environment would like to present a GUI to allow the user to change the
|
||||
model parameters. Typically, after collecting new values, the environment will supply them in the build() method.
|
||||
|
||||
This code will return a dictionary of parameter values in the model text SCRIPT::
|
||||
|
||||
parameters = cqgi.parse(SCRIPT).metadata.parameters
|
||||
|
||||
The dictionary you get back is a map where key is the parameter name, and value is an InputParameter object,
|
||||
which has a name, type, and default value.
|
||||
|
||||
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 ).
|
||||
|
||||
The parameter object also has a description, valid values, minimum, and maximum values, if the user has provided them using the
|
||||
describe_parameter() method.
|
||||
|
||||
|
||||
|
||||
Calling :py:meth:`cadquery.cqgi.CQModel.build()` returns a :py:class:`cadquery.cqgi.BuildResult` object,
|
||||
,which includes the script execution time, and a success flag.
|
||||
|
||||
If the script was successful, the results property will include a list of results returned by the script,
|
||||
as well as any debug the script produced
|
||||
|
||||
If the script failed, the exception property contains the exception object.
|
||||
|
||||
If you have a way to get inputs from a user, you can override any of the constants defined in the user script
|
||||
with new values::
|
||||
|
||||
from cadquery import cqgi
|
||||
|
||||
user_script = ...
|
||||
build_result = cqgi.parse(user_script).build(build_parameters={ 'param': 2 }, build_options={} )
|
||||
|
||||
If a parameter called 'param' is defined in the model, it will be assigned the value 2 before the script runs.
|
||||
An error will occur if a value is provided that is not defined in the model, or if the value provided cannot
|
||||
be assigned to a variable with the given name.
|
||||
|
||||
build_options is used to set server-side settings like timeouts, tesselation tolerances, and other details about
|
||||
how the model should be built.
|
||||
|
||||
|
||||
More about script variables
|
||||
-----------------------------
|
||||
|
||||
CQGI uses the following rules to find input variables for a script:
|
||||
|
||||
* only top-level statements are considered
|
||||
* only assignments of constant values to a local name are considered.
|
||||
|
||||
For example, in the following script::
|
||||
|
||||
h = 1.0
|
||||
w = 2.0
|
||||
foo = 'bar'
|
||||
|
||||
def some_function():
|
||||
x = 1
|
||||
|
||||
h, w, and foo will be overridable script variables, but x is not.
|
||||
|
||||
You can list the variables defined in the model by using the return value of the parse method::
|
||||
|
||||
model = cqgi.parse(user_script)
|
||||
|
||||
//a dictionary of InputParameter objects
|
||||
parameters = model.metadata.parameters
|
||||
|
||||
The key of the dictionary is a string , and the value is a :py:class:`cadquery.cqgi.InputParameter` object
|
||||
See the CQGI API docs for more details.
|
||||
|
||||
Future enhancments will include a safer sandbox to prevent malicious scripts.
|
||||
|
||||
Important CQGI Methods
|
||||
-------------------------
|
||||
|
||||
These are the most important Methods and classes of the CQGI
|
||||
|
||||
.. currentmodule:: cadquery.cqgi
|
||||
|
||||
.. autosummary::
|
||||
parse
|
||||
CQModel.build
|
||||
BuildResult
|
||||
ScriptCallback.show_object
|
||||
|
||||
Complete CQGI api
|
||||
-----------------
|
||||
|
||||
.. automodule:: cadquery.cqgi
|
||||
:members:
|
||||
|
74
Libs/cadquery/doc/designprinciples.rst
Normal file
|
@ -0,0 +1,74 @@
|
|||
.. _designprinciples:
|
||||
|
||||
|
||||
===========================
|
||||
CadQuery Design Principles
|
||||
===========================
|
||||
|
||||
|
||||
Principle 1: Intuitive Construction
|
||||
====================================
|
||||
|
||||
CadQuery aims to make building models using python scripting easy and intuitive.
|
||||
CadQuery strives to allow scripts to read roughly as a human would describe an object verbally.
|
||||
|
||||
For example, consider this object:
|
||||
|
||||
.. image:: _static/quickstart.png
|
||||
|
||||
A human would describe this as:
|
||||
|
||||
"A block 80mm square x 30mm thick , with countersunk holes for M2 socket head cap screws
|
||||
at the corners, and a circular pocket 22mm in diameter in the middle for a bearing"
|
||||
|
||||
The goal is to have the CadQuery script that produces this object be as close as possible to the english phrase
|
||||
a human would use.
|
||||
|
||||
|
||||
Principle 2: Capture Design Intent
|
||||
====================================
|
||||
|
||||
The features that are **not** part of the part description above are just as important as those that are. For example, most
|
||||
humans will assume that:
|
||||
|
||||
* The countersunk holes are spaced a uniform distance from the edges
|
||||
* The circular pocket is in the center of the block, no matter how big the block is
|
||||
|
||||
If you have experience with 3D CAD systems, you also know that there is a key design intent built into this object.
|
||||
After the base block is created, how the hole is located is key. If it is located from one edge, changing the block
|
||||
size will have a different affect than if the hole is located from the center.
|
||||
|
||||
Many scripting langauges do not provide a way to capture design intent-- because they require that you always work in
|
||||
global coordinates. CadQuery is different-- you can locate features relative to others in a relative way-- preserving
|
||||
the design intent just like a human would when creating a drawing or building an object.
|
||||
|
||||
In fact, though many people know how to use 3D CAD systems, few understand how important the way that an object is built
|
||||
impact its maintainability and resiliency to design changes.
|
||||
|
||||
|
||||
Principle 3: Plugins as first class citizens
|
||||
============================================
|
||||
|
||||
Any system for building 3D models will evolve to contain an immense number of libraries and feature builders. It is
|
||||
important that these can be seamlessly included into the core and used alongside the built in libraries. Plugins
|
||||
should be easy to install and familiar to use.
|
||||
|
||||
|
||||
Principle 4: CAD models as source code makes sense
|
||||
==================================================================
|
||||
|
||||
It is surprising that the world of 3D CAD is primarily dominated by systems that create opaque binary files.
|
||||
Just like the world of software, CAD models are very complex.
|
||||
|
||||
CAD models have many things in common with software, and would benefit greatly from the use of tools that are standard
|
||||
in the software industry, such as:
|
||||
|
||||
1. Easily re-using features between objects
|
||||
2. Storing objects using version control systems
|
||||
3. Computing the differences between objects by using source control tools
|
||||
4. Share objects on the internet
|
||||
5. Automate testing and generation by allowing objects to be built from within libraries
|
||||
|
||||
CadQuery is designed to make 3D content creation easy enough that the above benefits can be attained without more work
|
||||
than using existing 'opaque', 'point and click' solutions.
|
||||
|
1096
Libs/cadquery/doc/examples.rst
Normal file
180
Libs/cadquery/doc/extending.rst
Normal file
|
@ -0,0 +1,180 @@
|
|||
.. _extending:
|
||||
|
||||
Extending CadQuery
|
||||
======================
|
||||
|
||||
|
||||
If you find that CadQuery doesnt suit your needs, you can easily extend it. CadQuery provides several extension
|
||||
methods:
|
||||
|
||||
* You can load plugins others have developed. This is by far the easiest way to access other code
|
||||
* you can define your own plugins.
|
||||
* you can use FreeCAD script directly
|
||||
|
||||
|
||||
Using FreeCAD Script
|
||||
-----------------------
|
||||
|
||||
The easiest way to extend CadQuery is to simply use FreeCAD script inside of your build method. Just about
|
||||
any valid FreeCAD script will execute just fine. For example, this simple CadQuery script::
|
||||
|
||||
return cq.Workplane("XY").box(1.0,2.0,3.0).val()
|
||||
|
||||
is actually equivalent to::
|
||||
|
||||
return Part.makeBox(1.0,2.0,3.0)
|
||||
|
||||
As long as you return a valid FreeCAD Shape, you can use any FreeCAD methods you like. You can even mix and match the
|
||||
two. For example, consider this script, which creates a FreeCAD box, but then uses cadquery to select its faces::
|
||||
|
||||
box = Part.makeBox(1.0,2.0,3.0)
|
||||
cq = CQ(box).faces(">Z").size() # returns 6
|
||||
|
||||
|
||||
Extending CadQuery: Plugins
|
||||
----------------------------
|
||||
|
||||
Though you can get a lot done with FreeCAD, the code gets pretty nasty in a hurry. CadQuery shields you from
|
||||
a lot of the complexity of the FreeCAD api.
|
||||
|
||||
You can get the best of both worlds by wrapping your freecad script into a CadQuery plugin.
|
||||
|
||||
A CadQuery plugin is simply a function that is attached to the CadQuery :py:meth:`cadquery.CQ` or :py:meth:`cadquery.Workplane` class.
|
||||
When connected, your plugin can be used in the chain just like the built-in functions.
|
||||
|
||||
There are a few key concepts important to understand when building a plugin
|
||||
|
||||
|
||||
The Stack
|
||||
-------------------
|
||||
|
||||
Every CadQuery object has a local stack, which contains a list of items. The items on the stack will be
|
||||
one of these types:
|
||||
|
||||
* **A CadQuery SolidReference object**, which holds a reference to a FreeCAD solid
|
||||
* **A FreeCAD object**, a Vertex, Edge, Wire, Face, Shell, Solid, or Compound
|
||||
|
||||
The stack is available by using self.objects, and will always contain at least one object.
|
||||
|
||||
.. note::
|
||||
|
||||
Objects and points on the stack are **always** in global coordinates. Similarly, any objects you
|
||||
create must be created in terms of global coordinates as well!
|
||||
|
||||
|
||||
Preserving the Chain
|
||||
-----------------------
|
||||
|
||||
CadQuery's fluent api relies on the ability to chain calls together one after another. For this to work,
|
||||
you must return a valid CadQuery object as a return value. If you choose not to return a CadQuery object,
|
||||
then your plugin will end the chain. Sometimes this is desired for example :py:meth:`cadquery.CQ.size`
|
||||
|
||||
There are two ways you can safely continue the chain:
|
||||
|
||||
1. **return self** If you simply wish to modify the stack contents, you can simply return a reference to
|
||||
self. This approach is destructive, because the contents of the stack are modified, but it is also the
|
||||
simplest.
|
||||
2. :py:meth:`cadquery.CQ.newObject` Most of the time, you will want to return a new object. Using newObject will
|
||||
return a new CQ or Workplane object having the stack you specify, and will link this object to the
|
||||
previous one. This preserves the original object and its stack.
|
||||
|
||||
|
||||
Helper Methods
|
||||
-----------------------
|
||||
|
||||
When you implement a CadQuery plugin, you are extending CadQuery's base objects. As a result, you can call any
|
||||
CadQuery or Workplane methods from inside of your extension. You can also call a number of internal methods that
|
||||
are designed to aid in plugin creation:
|
||||
|
||||
|
||||
* :py:meth:`cadquery.Workplane._makeWireAtPoints` will invoke a factory function you supply for all points on the stack,
|
||||
and return a properly constructed cadquery object. This function takes care of registering wires for you
|
||||
and everything like that
|
||||
|
||||
* :py:meth:`cadquery.Workplane.newObject` returns a new Workplane object with the provided stack, and with its parent set
|
||||
to the current object. The preferred way to continue the chain
|
||||
|
||||
* :py:meth:`cadquery.CQ.findSolid` returns the first Solid found in the chain, working from the current object upwards
|
||||
in the chain. commonly used when your plugin will modify an existing solid, or needs to create objects and
|
||||
then combine them onto the 'main' part that is in progress
|
||||
|
||||
* :py:meth:`cadquery.Workplane._addPendingWire` must be called if you add a wire. This allows the base class to track all the wires
|
||||
that are created, so that they can be managed when extrusion occurs.
|
||||
|
||||
* :py:meth:`cadquery.Workplane.wire` gathers up all of the edges that have been drawn ( eg, by line, vline, etc ), and
|
||||
attempts to combine them into a single wire, which is returned. This should be used when your plugin creates
|
||||
2-d edges, and you know it is time to collect them into a single wire.
|
||||
|
||||
* :py:meth:`cadquery.Workplane.plane` provides a reference to the workplane, which allows you to convert between workplane
|
||||
coordinates and global coordinates:
|
||||
* :py:meth:`cadquery.freecad_impl.geom.Plane.toWorldCoords` will convert local coordinates to global ones
|
||||
* :py:meth:`cadquery.freecad_impl.geom.Plane.toLocalCoords` will convet from global coordinates to local coordinates
|
||||
|
||||
Coordinate Systems
|
||||
-----------------------
|
||||
|
||||
Keep in mind that the user may be using a work plane that has created a local coordinate system. Consequently,
|
||||
the orientation of shapes that you create are often implicitly defined by the user's workplane.
|
||||
|
||||
Any objects that you create must be fully defined in *global coordinates*, even though some or all of the users'
|
||||
inputs may be defined in terms of local coordinates.
|
||||
|
||||
|
||||
Linking in your plugin
|
||||
-----------------------
|
||||
|
||||
Your plugin is a single method, which is attached to the main Workplane or CadQuery object.
|
||||
|
||||
Your plugin method's first parameter should be 'self', which will provide a reference to base class functionality.
|
||||
You can also accept other arguments.
|
||||
|
||||
To install it, simply attach it to the CadQuery or Workplane object, like this::
|
||||
|
||||
def _yourFunction(self,arg1,arg):
|
||||
do stuff
|
||||
return whatever_you_want
|
||||
|
||||
cq.Workplane.yourPlugin = _yourFunction
|
||||
|
||||
That's it!
|
||||
|
||||
CadQueryExample Plugins
|
||||
-----------------------
|
||||
Some core cadquery code is intentionally written exactly like a plugin.
|
||||
If you are writing your own plugins, have a look at these methods for inspiration:
|
||||
|
||||
* :py:meth:`cadquery.Workplane.polygon`
|
||||
* :py:meth:`cadquery.Workplane.cboreHole`
|
||||
|
||||
|
||||
Plugin Example
|
||||
-----------------------
|
||||
|
||||
This ultra simple plugin makes cubes of the specified size for each stack point.
|
||||
|
||||
(The cubes are off-center because the boxes have their lower left corner at the reference points.)
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
def makeCubes(self,length):
|
||||
#self refers to the CQ or Workplane object
|
||||
|
||||
#inner method that creates a cube
|
||||
def _singleCube(pnt):
|
||||
#pnt is a location in local coordinates
|
||||
#since we're using eachpoint with useLocalCoordinates=True
|
||||
return cq.Solid.makeBox(length,length,length,pnt)
|
||||
|
||||
#use CQ utility method to iterate over the stack, call our
|
||||
#method, and convert to/from local coordinates.
|
||||
return self.eachpoint(_singleCube,True)
|
||||
|
||||
#link the plugin into cadQuery
|
||||
cq.Workplane.makeCubes = makeCubes
|
||||
|
||||
#use the plugin
|
||||
result = cq.Workplane("XY").box(6.0,8.0,0.5).faces(">Z")\
|
||||
.rect(4.0,4.0,forConstruction=True).vertices() \
|
||||
.makeCubes(1.0).combineSolids()
|
||||
show_object(result)
|
||||
|
24
Libs/cadquery/doc/fileformat.rst
Normal file
|
@ -0,0 +1,24 @@
|
|||
.. _cadquery_reference:
|
||||
|
||||
CadQuery Scripts and Object Output
|
||||
======================================
|
||||
|
||||
CadQuery scripts are pure python scripts, that may follow a few conventions.
|
||||
|
||||
If you are using cadquery as a library, there are no constraints.
|
||||
|
||||
If you are using cadquery scripts inside of a cadquery execution environment,
|
||||
like `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_ or
|
||||
`parametricParts.com <https://www.parametricparts.com>`_, there are a few conventions you need to be aware of:
|
||||
|
||||
* cadquery is already imported as 'cq'
|
||||
* to return an object to the container, you need to call the show_object() method.
|
||||
|
||||
Each script generally has three sections:
|
||||
|
||||
* Variable Assignments and metadata definitions
|
||||
* cadquery and other python code
|
||||
* object exports, via the export_object() function
|
||||
|
||||
|
||||
see the :ref:`cqgi` section for more details.
|
58
Libs/cadquery/doc/index.rst
Normal file
|
@ -0,0 +1,58 @@
|
|||
|
||||
|
||||
CadQuery Documentation
|
||||
===================================
|
||||
|
||||
CadQuery is an intuitive, easy-to-use python library for building parametric 3D CAD models. It has several goals:
|
||||
|
||||
* Build models with scripts that are as close as possible to how you'd describe the object to a human,
|
||||
using a standard, already established programming language
|
||||
|
||||
* Create parametric models that can be very easily customized by end users
|
||||
|
||||
* Output high quality CAD formats like STEP and AMF in addition to traditional STL
|
||||
|
||||
* Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
|
||||
|
||||
See CadQuery in Action
|
||||
-------------------------
|
||||
|
||||
This `Getting Started Video <https://youtu.be/lxhBNOE7GVs>`_ will show you what CadQuery can do.
|
||||
|
||||
|
||||
Quick Links
|
||||
------------------
|
||||
|
||||
* :ref:`quickstart`
|
||||
* `CadQuery CheatSheet <_static/cadquery_cheatsheet.html>`_
|
||||
* :ref:`apireference`
|
||||
|
||||
Table Of Contents
|
||||
-------------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
intro.rst
|
||||
installation.rst
|
||||
quickstart.rst
|
||||
designprinciples.rst
|
||||
primer.rst
|
||||
fileformat.rst
|
||||
examples.rst
|
||||
apireference.rst
|
||||
selectors.rst
|
||||
classreference.rst
|
||||
cqgi.rst
|
||||
extending.rst
|
||||
roadmap.rst
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
-------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
58
Libs/cadquery/doc/installation.rst
Normal file
|
@ -0,0 +1,58 @@
|
|||
.. _installation:
|
||||
|
||||
Installing CadQuery
|
||||
===================================
|
||||
|
||||
CadQuery is based on `FreeCAD <http://sourceforge.net/apps/mediawiki/free-cad/index.php?title=Main_Page>`_,
|
||||
which is turn based on the open-source `OpenCascade <http://www.opencascade.com/>`_ modelling kernel.
|
||||
|
||||
Prerequisites--FreeCAD and Python 2.6 or 2.7
|
||||
----------------------------------------------
|
||||
CadQuery requires FreeCAD and Python version 2.6.x or 2.7.x *Python 3.x is NOT supported*
|
||||
|
||||
Ubuntu Command Line Installation
|
||||
------------------------------------------
|
||||
|
||||
On Ubuntu, you can type::
|
||||
|
||||
sudo apt-get install -y freecad freecad-doc
|
||||
pip install cadquery
|
||||
|
||||
This `Unix Installation Video <http://youtu.be/InZu8jgaYCA>`_ will walk you through the installation
|
||||
|
||||
|
||||
Installation: Other Platforms
|
||||
------------------------------------------
|
||||
|
||||
1. Install FreeCAD using the appropriate installer for your platform, on `www.freecadweb.org <http://www.freecadweb.org/wiki/?title=Download>`_
|
||||
2. pip install cadquery
|
||||
|
||||
This `Windows Installation video <https://www.youtube.com/watch?v=dWw4Y_ah-8k>`_ will walk you through the installation on Windows
|
||||
|
||||
Test Your Installation
|
||||
------------------------
|
||||
|
||||
If all has gone well, you can open a command line/prompt, and type::
|
||||
|
||||
$python
|
||||
$import cadquery
|
||||
$cadquery.Workplane('XY').box(1,2,3).toSvg()
|
||||
|
||||
Adding a Nicer GUI via the cadquery-freecad-module
|
||||
--------------------------------------------------------
|
||||
|
||||
If you prefer to have a GUI available, your best option is to use
|
||||
`The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_.
|
||||
|
||||
Simply extract cadquery-freecad-module into your FreeCAD installation. You'll end up
|
||||
with a cadquery workbench that allows you to interactively run scripts, and then see the results in the FreeCAD GUI
|
||||
|
||||
Zero Step Install
|
||||
-------------------------------------------------
|
||||
|
||||
If you would like to use cadquery with no installation all, you can
|
||||
use `ParametricParts.com <https://www.parametricparts.com>`_, a web-based platform that runs cadquery scripts
|
||||
|
||||
It is free, and allows running and viewing cadquery scripts in your web browser or mobile phone
|
||||
|
||||
|
95
Libs/cadquery/doc/intro.rst
Normal file
|
@ -0,0 +1,95 @@
|
|||
.. _what_is_cadquery:
|
||||
|
||||
*********************
|
||||
Introduction
|
||||
*********************
|
||||
|
||||
What is CadQuery
|
||||
========================================
|
||||
|
||||
CadQuery is an intuitive, easy-to-use python library for building parametric 3D CAD models. It has several goals:
|
||||
|
||||
* Build models with scripts that are as close as possible to how you'd describe the object to a human,
|
||||
using a standard, already established programming language
|
||||
|
||||
* Create parametric models that can be very easily customized by end users
|
||||
|
||||
* Output high quality CAD formats like STEP and AMF in addition to traditional STL
|
||||
|
||||
* Provide a non-proprietary, plain text model format that can be edited and executed with only a web browser
|
||||
|
||||
CadQuery is based on
|
||||
`FreeCAD <http://sourceforge.net/apps/mediawiki/free-cad/index.php?title=Main_Page>`_,
|
||||
which is turn based on the open-source `OpenCascade <http://www.opencascade.com/>`_ modelling kernel.
|
||||
|
||||
Using CadQuery, you can build fully parametric models with a very small amount of code. For example, this simple script
|
||||
produces a flat plate with a hole in the middle::
|
||||
|
||||
thickness = 0.5
|
||||
width=2.0
|
||||
result = Workplane("front").box(width,width,thickness).faces(">Z").hole(thickness)
|
||||
|
||||
.. image:: _static/simpleblock.png
|
||||
|
||||
That's a bit of a dixie-cup example. But it is pretty similar to a more useful part: a parametric pillow block for a
|
||||
standard 608-size ball bearing::
|
||||
|
||||
(length,height,diam, thickness,padding) = ( 30.0,40.0,22.0,10.0,8.0)
|
||||
|
||||
result = Workplane("XY").box(length,height,thickness).faces(">Z").workplane().hole(diam)\
|
||||
.faces(">Z").workplane() \
|
||||
.rect(length-padding,height-padding,forConstruction=True) \
|
||||
.vertices().cboreHole(2.4,4.4,2.1)
|
||||
|
||||
.. image:: _static/pillowblock.png
|
||||
|
||||
Lots more examples are available in the :ref:`examples`
|
||||
|
||||
CadQuery is a library, GUIs are separate
|
||||
==============================================
|
||||
|
||||
CadQuery is a library, that's intentionally designed to be usable as a GUI-less library. This enables
|
||||
its use in a variety of engineering and scientific applications that create 3d models programmatically.
|
||||
|
||||
If you'd like a GUI, you have a couple of options:
|
||||
|
||||
* Install cadquery as a part of `The CadQuery Freecad Module <https://github.com/jmwright/cadquery-freecad-module>`_
|
||||
* Use `ParametricParts.com <https://www.parametricparts.com>`_, a web-based platform that runs cadQuery scripts
|
||||
|
||||
|
||||
Why CadQuery instead of OpenSCAD?
|
||||
============================================
|
||||
|
||||
Like OpenSCAD, CadQuery is an open-source, script based, parametric model generator. But CadQuery has several key advantages:
|
||||
|
||||
1. **The scripts use a standard programming language**, python, and thus can benefit from the associated infrastructure.
|
||||
This includes many standard libraries and IDEs
|
||||
|
||||
2. **More powerful CAD kernel** OpenCascade is much more powerful than CGAL. Features supported natively
|
||||
by OCC include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations,
|
||||
in addition to the standard CSG operations supported by CGAL
|
||||
|
||||
3. **Ability to import/export STEP** We think the ability to begin with a STEP model, created in a CAD package,
|
||||
and then add parametric features is key. This is possible in OpenSCAD using STL, but STL is a lossy format
|
||||
|
||||
4. **Less Code and easier scripting** CadQuery scripts require less code to create most objects, because it is possible to locate
|
||||
features based on the position of other features, workplanes, vertices, etc.
|
||||
|
||||
5. **Better Performance** CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
|
||||
|
||||
Where does the name CadQuery come from?
|
||||
========================================
|
||||
|
||||
CadQuery is inspired by `jQuery <http://www.jquery.com>`_ , a popular framework that
|
||||
revolutionized web development involving javascript.
|
||||
|
||||
CadQuery is for 3D CAD what jQuery is for javascript.
|
||||
If you are familiar with how jQuery works, you will probably recognize several jQuery features that CadQuery uses:
|
||||
|
||||
* A fluent api to create clean, easy to read code
|
||||
|
||||
* Ability to use the library along side other python libraries
|
||||
|
||||
* Clear and complete documentation, with plenty of samples.
|
||||
|
||||
|
153
Libs/cadquery/doc/primer.rst
Normal file
|
@ -0,0 +1,153 @@
|
|||
.. _3d_cad_primer:
|
||||
|
||||
|
||||
CadQuery Concepts
|
||||
===================================
|
||||
|
||||
|
||||
3D BREP Topology Concepts
|
||||
---------------------------
|
||||
Before talking about CadQuery, it makes sense to talk a little about 3D CAD Topology. CadQuery is based upon the
|
||||
OpenCascade kernel, which is uses Boundary Representations ( BREP ) for objects. This just means that objects
|
||||
are defined by their enclosing surfaces.
|
||||
|
||||
When working in a BREP system, these fundamental constructs exist to define a shape ( working up the food chain):
|
||||
|
||||
:vertex: a single point in space
|
||||
:edge: a connection between two or more vertices along a particular path ( called a curve )
|
||||
:wire: a collection of edges that are connected together.
|
||||
:face: a set of edges or wires that enclose a surface
|
||||
:shell: a collection of faces that are connected together along some of their edges
|
||||
:solid: a shell that has a closed interior
|
||||
:compound: a collection of solids
|
||||
|
||||
When using CadQuery, all of these objects are created, hopefully with the least possible work. In the actual CAD
|
||||
kernel, there are another set of Geometrical constructs involved as well. For example, an arc-shaped edge will
|
||||
hold a reference to an underlying curve that is a full cricle, and each linear edge holds underneath it the equation
|
||||
for a line. CadQuery shields you from these constructs.
|
||||
|
||||
|
||||
CQ, the CadQuery Object
|
||||
---------------------------
|
||||
|
||||
The CadQuery object wraps a BREP feature, and provides functionality around it. Typical examples include rotating,
|
||||
transforming, combining objects, and creating workplanes.
|
||||
|
||||
See :ref:`apireference` to learn more.
|
||||
|
||||
|
||||
Workplanes
|
||||
---------------------------
|
||||
|
||||
Workplanes represent a plane in space, from which other features can be located. They have a center point and a local
|
||||
coordinate system.
|
||||
|
||||
The most common way to create a workplane is to locate one on the face of a solid. You can also create new workplanes
|
||||
in space, or relative to other planes using offsets or rotations.
|
||||
|
||||
The most powerful feature of workplanes is that they allow you to work in 2D space in the coordinate system of the
|
||||
workplane, and then build 3D features based on local coordinates. This makes scripts much easier to create and maintain.
|
||||
|
||||
See :py:class:`cadquery.Workplane` to learn more
|
||||
|
||||
|
||||
2D Construction
|
||||
---------------------------
|
||||
|
||||
Once you create a workplane, you can work in 2D, and then later use the features you create to make 3D objects.
|
||||
You'll find all of the 2D constructs you expect-- circles, lines, arcs, mirroring, points, etc.
|
||||
|
||||
See :ref:`2dOperations` to learn more.
|
||||
|
||||
|
||||
3D Construction
|
||||
---------------------------
|
||||
|
||||
You can construct 3D primatives such as boxes, spheres, wedges, and cylinders directly. You can also sweep, extrude,
|
||||
and loft 2D geometry to form 3D features. Of course the basic primitive operations are also available.
|
||||
|
||||
See :ref:`3doperations` to learn more.
|
||||
|
||||
|
||||
|
||||
Selectors
|
||||
---------------------------
|
||||
|
||||
Selectors allow you to select one or more features, for use to define new features. As an example, you might
|
||||
extrude a box, and then select the top face as the location for a new feture. Or, you might extrude a box, and
|
||||
then select all of the vertical edges so that you can apply a fillet to them.
|
||||
|
||||
You can select Vertices, Edges, Faces, Solids, and Wires using selectors.
|
||||
|
||||
Think of selectors as the equivalent of your hand and mouse, were you to build an object using a conventional CAD system.
|
||||
|
||||
You can learn more about selectors :ref:`selectors`
|
||||
|
||||
|
||||
Construction Geometry
|
||||
---------------------------
|
||||
Construction geometry are features that are not part of the object, but are only defined to aid in building the object.
|
||||
A common example might be to define a rectangle, and then use the corners to define a the location of a set of holes.
|
||||
|
||||
Most CadQuery construction methods provide a forConstruction keyword, which creates a feature that will only be used
|
||||
to locate other features
|
||||
|
||||
|
||||
The Stack
|
||||
---------------------------
|
||||
|
||||
As you work in CadQuery, each operation returns a new CadQuery object with the result of that operations. Each CadQuery
|
||||
object has a list of objects, and a reference to its parent.
|
||||
|
||||
You can always go backwards to older operations by removing the current object from the stack. For example::
|
||||
|
||||
CQ(someObject).faces(">Z").first().vertices()
|
||||
|
||||
returns a CadQuery object that contains all of the vertices on highest face of someObject. But you can always move
|
||||
backwards in the stack to get the face as well::
|
||||
|
||||
CQ(someObject).faces(">Z").first().vertices().end() #returns the same as CQ(someObject).faces(">Z").first()
|
||||
|
||||
You can browse stack access methods here :ref:`stackMethods`
|
||||
|
||||
|
||||
Chaining
|
||||
---------------------------
|
||||
|
||||
All CadQuery methods return another CadQuery object, so that you can chain the methods together fluently. Use
|
||||
the core CQ methods to get at the objects that were created.
|
||||
|
||||
|
||||
The Context Solid
|
||||
---------------------------
|
||||
|
||||
Most of the time, you are building a single object, and adding features to that single object. CadQuery watches
|
||||
your operations, and defines the first solid object created as the 'context solid'. After that, any features
|
||||
you create are automatically combined ( unless you specify otherwise) with that solid. This happens even if the
|
||||
solid was created a long way up in the stack. For example::
|
||||
|
||||
Workplane('XY').box(1,2,3).faces(">Z").circle(0.25).extrude()
|
||||
|
||||
Will create a 1x2x3 box, with a cylindrical boss extending from the top face. It was not necessary to manually
|
||||
combine the cylinder created by extruding the circle with the box, because the default behavior for extrude is
|
||||
to combine the result with the context solid. The hole() method works similarly-- CadQuery presumes that you want
|
||||
to subtract the hole from the context solid.
|
||||
|
||||
If you want to avoid this, you can specified combine=False, and CadQuery will create the solid separately.
|
||||
|
||||
|
||||
Iteration
|
||||
---------------------------
|
||||
|
||||
CAD models often have repeated geometry, and its really annoying to resort to for loops to construct features.
|
||||
Many CadQuery methods operate automatically on each element on the stack, so that you don't have to write loops.
|
||||
For example, this::
|
||||
|
||||
Workplane('XY').box(1,2,3).faces(">Z").vertices().circle(0.5)
|
||||
|
||||
Will actually create 4 circles, because vertices() selects 4 vertices of a rectangular face, and the circle() method
|
||||
iterates on each member of the stack.
|
||||
|
||||
This is really useful to remember when you author your own plugins. :py:meth:`cadquery.CQ.Workplane.each` is useful for this purpose.
|
||||
|
||||
|
242
Libs/cadquery/doc/quickstart.rst
Normal file
|
@ -0,0 +1,242 @@
|
|||
.. _quickstart:
|
||||
|
||||
***********************
|
||||
CadQuery QuickStart
|
||||
***********************
|
||||
|
||||
.. module:: cadquery
|
||||
|
||||
Want a quick glimpse of what CadQuery can do? This quickstart will demonstrate the basics of cadQuery using a simple example
|
||||
|
||||
Prerequisites: FreeCAD + cadQuery-freeCAD-module in FreeCAD
|
||||
==============================================================
|
||||
|
||||
If you have not already done so, follow the :ref:`installation`, and to install cadquery, FreeCAD,
|
||||
and the cadquery-freecad-module
|
||||
|
||||
After installation, open the CadQuery workbench:
|
||||
|
||||
.. image:: _static/quickstart/001.png
|
||||
|
||||
You'll see that we start out with a single block. Find the cadquery Code Window, at the bottom left.
|
||||
|
||||
If you want check out a couple of the examples in the CadQuery->Examples menu.
|
||||
|
||||
What we'll accomplish
|
||||
========================
|
||||
|
||||
We will build a fully parametric bearing pillow block in this quickstart. Our finished object will look like this:
|
||||
|
||||
.. image:: _static/quickstart/000.png
|
||||
|
||||
**We would like our block to have these features:**
|
||||
|
||||
1. It should be sized to hold a single 608 ( 'skate' ) bearing, in the center of the block.
|
||||
2. It should have counter sunk holes for M2 socket head cap screws at the corners
|
||||
3. The length and width of the block should be configurable by the user to any reasonable size.
|
||||
|
||||
A human would describe this as:
|
||||
|
||||
"A rectangular block 80mm x 60mm x 30mm , with countersunk holes for M2 socket head cap screws
|
||||
at the corners, and a circular pocket 22mm in diameter in the middle for a bearing"
|
||||
|
||||
Human descriptions are very elegant, right?
|
||||
Hopefully our finished script will not be too much more complex than this human-oriented description.
|
||||
|
||||
Let's see how we do.
|
||||
|
||||
Start With A single, simple Plate
|
||||
======================================
|
||||
|
||||
Lets start with a simple model that makes nothing but a rectangular block, but
|
||||
with place-holders for the dimensions. Paste this into the CodeWindow:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
thickness = 10.0
|
||||
|
||||
# make the base
|
||||
result = cq.Workplane("XY").box(height, width, thickness)
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|
||||
|
||||
Press F2 to run the script. You should see Our basic base.
|
||||
|
||||
.. image:: _static/quickstart/002.png
|
||||
|
||||
Nothing special, but its a start!
|
||||
|
||||
Add the Holes
|
||||
================
|
||||
|
||||
Our pillow block needs to have a 22mm diameter hole in the center of this block to hold the bearing.
|
||||
|
||||
This modification will do the trick:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 4,8
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
thickness = 10.0
|
||||
diameter = 22.0
|
||||
|
||||
# make the base
|
||||
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||
.faces(">Z").workplane().hole(diameter)
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|
||||
|
||||
Rebuild your model by pressing F2. Your block should look like this:
|
||||
|
||||
.. image:: _static/quickstart/003.png
|
||||
|
||||
|
||||
The code is pretty compact, lets step through it.
|
||||
|
||||
**Line 4** adds a new parameter, diameter, for the diamter of the hole
|
||||
|
||||
**Line 8**, we're adding the hole.
|
||||
:py:meth:`cadquery.CQ.faces` selects the top-most face in the Z direction, and then
|
||||
:py:meth:`cadquery.CQ.workplane` begins a new workplane located on this face. The center of this workplane
|
||||
is located at the geometric center of the shape, which in this case is the center of the plate.
|
||||
Finally, :py:meth:`cadquery.Workplane.hole` drills a hole through the part 22mm in diamter
|
||||
|
||||
.. note::
|
||||
|
||||
Don't worry about the CadQuery syntax now.. you can learn all about it in the :ref:`apireference` later.
|
||||
|
||||
More Holes
|
||||
============
|
||||
|
||||
Ok, that hole was not too hard, but what about the counter-bored holes in the corners?
|
||||
|
||||
An M2 Socket head cap screw has these dimensions:
|
||||
|
||||
* **Head Diameter** : 3.8 mm
|
||||
* **Head height** : 2.0 mm
|
||||
* **Clearance Hole** : 2.4 mm
|
||||
* **CounterBore diameter** : 4.4 mm
|
||||
|
||||
The centers of these holes should be 4mm from the edges of the block. And,
|
||||
we want the block to work correctly even when the block is re-sized by the user.
|
||||
|
||||
**Don't tell me** we'll have to repeat the steps above 8 times to get counter-bored holes?
|
||||
Good news!-- we can get the job done with just two lines of code. Here's the code we need:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 5,10-13
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
thickness = 10.0
|
||||
diameter = 22.0
|
||||
padding = 12.0
|
||||
|
||||
# make the base
|
||||
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||
.faces(">Z").workplane().hole(diameter)\
|
||||
.faces(">Z").workplane() \
|
||||
.rect(height - padding,width - padding,forConstruction=True)\
|
||||
.vertices()\
|
||||
.cboreHole(2.4, 4.4, 2.1)
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|
||||
|
||||
|
||||
After pressing F2 to re-execute the model, you should see something like this:
|
||||
|
||||
.. image:: _static/quickstart/004.png
|
||||
|
||||
|
||||
There is quite a bit going on here, so lets break it down a bit.
|
||||
|
||||
**Line 5** creates a new padding parameter that decides how far the holes are from the edges of the plate.
|
||||
|
||||
**Line 10** selects the top-most face of the block, and creates a workplane on the top of that face, which we'll use to
|
||||
define the centers of the holes in the corners.
|
||||
|
||||
There are a couple of things to note about this line:
|
||||
|
||||
1. The :py:meth:`cadquery.Workplane.rect` function draws a rectangle. **forConstruction=True**
|
||||
tells CadQuery that this rectangle will not form a part of the solid,
|
||||
but we are just using it to help define some other geometry.
|
||||
2. The center point of a workplane on a face is always at the center of the face, which works well here
|
||||
3. Unless you specifiy otherwise, a rectangle is drawn with its center on the current workplane center-- in
|
||||
this case, the center of the top face of the block. So this rectangle will be centered on the face
|
||||
|
||||
|
||||
**Line 11** draws a rectangle 8mm smaller than the overall length and width of the block,which we will use to
|
||||
locate the corner holes. We'll use the vertices ( corners ) of this rectangle to locate the holes. The rectangle's
|
||||
center is at the center of the workplane, which in this case co-incides with the center of the bearing hole.
|
||||
|
||||
**Line 12** selects the vertices of the rectangle, which we will use for the centers of the holes.
|
||||
The :py:meth:`cadquery.CQ.vertices` function selects the corners of the rectangle
|
||||
|
||||
**Line 13** uses the cboreHole function to draw the holes.
|
||||
The :py:meth:`cadquery.Workplane.cboreHole` function is a handy CadQuery function that makes a counterbored hole,
|
||||
like most other CadQuery functions, operate on the values on the stack. In this case, since we
|
||||
selected the four vertices before calling the function, the function operates on each of the four points--
|
||||
which results in a counterbore hole at the corners.
|
||||
|
||||
|
||||
Filleting
|
||||
===========
|
||||
|
||||
Almost done. Let's just round the corners of the block a bit. That's easy, we just need to select the edges
|
||||
and then fillet them:
|
||||
|
||||
We can do that using the preset dictionaries in the parameter definition:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:emphasize-lines: 13
|
||||
|
||||
height = 60.0
|
||||
width = 80.0
|
||||
thickness = 10.0
|
||||
diameter = 22.0
|
||||
padding = 12.0
|
||||
|
||||
# make the base
|
||||
result = cq.Workplane("XY").box(height, width, thickness)\
|
||||
.faces(">Z").workplane().hole(diameter)\
|
||||
.faces(">Z").workplane() \
|
||||
.rect(height - padding, width - padding, forConstruction=True)\
|
||||
.vertices().cboreHole(2.4, 4.4, 2.1)\
|
||||
.edges("|Z").fillet(2.0)
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|
||||
|
||||
**Line 13** fillets the edges using the :py:meth:`cadquery.CQ.fillet` method.
|
||||
|
||||
To grab the right edges, the :py:meth:`cadquery.CQ.edges` selects all of the
|
||||
edges that are parallel to the Z axis ("\|Z"),
|
||||
|
||||
The finished product looks like this:
|
||||
|
||||
.. image:: _static/quickstart/005.png
|
||||
|
||||
|
||||
Done!
|
||||
============
|
||||
|
||||
You just made a parametric, model that can generate pretty much any bearing pillow block
|
||||
with < 20 lines of code.
|
||||
|
||||
Want to learn more?
|
||||
====================
|
||||
|
||||
* Use the CadQuery->Examples menu of the cadquery workbench to explore a lot of other examples.
|
||||
* The :ref:`examples` contains lots of examples demonstrating cadquery features
|
||||
* The :ref:`apireference` is a good overview of language features grouped by function
|
||||
* The :ref:`classreference` is the hard-core listing of all functions available.
|
154
Libs/cadquery/doc/roadmap.rst
Normal file
|
@ -0,0 +1,154 @@
|
|||
.. _roadmap:
|
||||
|
||||
|
||||
RoadMap: Planned Features
|
||||
==============================
|
||||
|
||||
**CadQuery is not even close to finished!!!**
|
||||
|
||||
Many features are planned for later versions. This page tracks them. If you find that you need features
|
||||
not listed here, let us know!
|
||||
|
||||
Core
|
||||
--------------------
|
||||
|
||||
end(n)
|
||||
allows moving backwards a fixed number of parents in the chain, eg end(3) is same as end().end().end()
|
||||
|
||||
FreeCAD object wrappers
|
||||
return CQ wrappers for FreeCAD shapes instead of the native FreeCAD objects.
|
||||
|
||||
Improved iteration tools for plugin developers
|
||||
make it easier to iterate over points and wires for plugins
|
||||
|
||||
More parameter types (String? )
|
||||
|
||||
face.outerWire
|
||||
allow selecting the outerWire of a face, so that it can be used for reference geometry or offsets
|
||||
|
||||
Selectors
|
||||
--------------------
|
||||
|
||||
tagged entities
|
||||
support tagging entities when they are created, so they can be selected later on using that tag.
|
||||
ideally, tags are propagated to features that are created from these features ( ie, an edge tagged with 'foo'
|
||||
that is later extruded into a face means that face would be tagged with 'foo' as well )
|
||||
|
||||
|
||||
Workplanes
|
||||
--------------------
|
||||
|
||||
rotated workplanes
|
||||
support creation of workplanes at an angle to another plane or face
|
||||
|
||||
workplane local rotations
|
||||
rotate the coordinate system of a workplane by an angle.
|
||||
|
||||
make a workplane from a wire
|
||||
useful to select outer wire and then operate from there, to allow offsets
|
||||
|
||||
2-d operations
|
||||
-------------------
|
||||
|
||||
offsets
|
||||
offset profiles, including circles, rects, and other profiles.
|
||||
|
||||
ellipses
|
||||
create elipses and portions of elipses
|
||||
|
||||
regular polygons
|
||||
several construction methods:
|
||||
* number of sides and side length
|
||||
* number of sides inscribed in circle
|
||||
* number of sides circumscribed by circle
|
||||
|
||||
arc construction using relative measures
|
||||
instead of forcing use of absolute workplane coordinates
|
||||
|
||||
tangent arcs
|
||||
after a line
|
||||
|
||||
centerpoint arcs
|
||||
including portions of arcs as well as with end points specified
|
||||
|
||||
trimming
|
||||
ability to use construction geometry to trim other entities
|
||||
|
||||
construction lines
|
||||
especially centerlines
|
||||
|
||||
2-d fillets
|
||||
for a rectangle, or for consecutive selected lines
|
||||
|
||||
2-d chamfers
|
||||
based on rectangles, polygons, polylines, or adjacent selected lines
|
||||
|
||||
mirror around centerline
|
||||
using centerline construction geometry
|
||||
|
||||
rectangular array
|
||||
automate creation of equally spread points
|
||||
|
||||
polar array
|
||||
create equally spaced copies of a feature around a circle
|
||||
perhaps based on a construction circle?
|
||||
|
||||
midpoint selection
|
||||
select midpoints of lines, arcs
|
||||
|
||||
face center
|
||||
explicit selection of face center
|
||||
|
||||
manipulate spline control points
|
||||
so that the shape of a spline can be more accurately controlled
|
||||
|
||||
feature snap
|
||||
project geometry in the rest of the part into the work plane, so that
|
||||
they can be selected and used as references for other features.
|
||||
|
||||
polyline edges
|
||||
allow polyline to be combined with other edges/curves
|
||||
|
||||
create text
|
||||
ideally, in various fonts.
|
||||
|
||||
3-d operations
|
||||
---------------------
|
||||
|
||||
rotation/transform that return a copy
|
||||
The current rotateAboutCenter and translate method modify the object, rather than returning a copy
|
||||
|
||||
primitive creation
|
||||
Need primitive creation for:
|
||||
* cone
|
||||
* sphere
|
||||
* cylinder
|
||||
* torus
|
||||
* wedge
|
||||
|
||||
extrude/cut up to surface
|
||||
allow a cut or extrude to terminate at another surface, rather than either through all or a fixed distance
|
||||
|
||||
extrude along a path
|
||||
rather than just normal to the plane. This would include
|
||||
|
||||
STEP import
|
||||
allow embedding and importing step solids created in other tools, which
|
||||
can then be further manipulated parametrically
|
||||
|
||||
Dome
|
||||
very difficult to do otherwise
|
||||
|
||||
primitive boolean operations
|
||||
* intersect
|
||||
* union
|
||||
* subtract
|
||||
|
||||
|
||||
Algorithms
|
||||
---------------------
|
||||
|
||||
Wire Discretization
|
||||
Sample wires at point interval to improve closet wire computations
|
||||
|
||||
|
140
Libs/cadquery/doc/selectors.rst
Normal file
|
@ -0,0 +1,140 @@
|
|||
.. _selector_reference:
|
||||
|
||||
String Selectors Reference
|
||||
=============================
|
||||
|
||||
|
||||
CadQuery selector strings allow filtering various types of object lists. Most commonly, Edges, Faces, and Vertices are
|
||||
used, but all objects types can be filtered.
|
||||
|
||||
String selectors are simply shortcuts for using the full object equivalents. If you pass one of the
|
||||
string patterns in, CadQuery will automatically use the associated selector object.
|
||||
|
||||
* :py:meth:`cadquery.CQ.faces`
|
||||
* :py:meth:`cadquery.CQ.edges`
|
||||
* :py:meth:`cadquery.CQ.vertices`
|
||||
* :py:meth:`cadquery.CQ.solids`
|
||||
* :py:meth:`cadquery.CQ.shells`
|
||||
|
||||
.. note::
|
||||
|
||||
String selectors are shortcuts to concrete selector classes, which you can use or extend. See
|
||||
:ref:`classreference` for more details
|
||||
|
||||
If you find that the built-in selectors are not sufficient, you can easily plug in your own.
|
||||
See :ref:`extending` to see how.
|
||||
|
||||
|
||||
Combining Selectors
|
||||
==========================
|
||||
|
||||
Selectors can be combined logically, currently defined operators include **and**, **or**, **not** and **exc[ept]** (set difference). For example:
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("XY").box(2, 2, 2) \
|
||||
.edges("|Z and >Y") \
|
||||
.chamfer(0.2)
|
||||
|
||||
show_object(result)
|
||||
|
||||
Much more complex expressions are possible as well:
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("XY").box(2, 2, 2) \
|
||||
.faces(">Z") \
|
||||
.shell(-0.2) \
|
||||
.faces(">Z") \
|
||||
.edges("not(<X or >X or <Y or >Y)") \
|
||||
.chamfer(0.1)
|
||||
|
||||
show_object(result)
|
||||
|
||||
.. _filteringfaces:
|
||||
|
||||
Filtering Faces
|
||||
----------------
|
||||
|
||||
All types of filters work on faces. In most cases, the selector refers to the direction of the **normal vector**
|
||||
of the face.
|
||||
|
||||
.. warning::
|
||||
|
||||
If a face is not planar, selectors are evaluated at the center of mass of the face. This can lead
|
||||
to results that are quite unexpected.
|
||||
|
||||
The axis used in the listing below are for illustration: any axis would work similarly in each case.
|
||||
|
||||
========= ======================================= ======================================================= ==========================
|
||||
Selector Selects Selector Class # objects returned
|
||||
========= ======================================= ======================================================= ==========================
|
||||
+Z Faces with normal in +z direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
\|Z Faces parallel to xy plane :py:class:`cadquery.ParallelDirSelector` 0..many
|
||||
-X Faces with normal in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
#Z Faces perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
|
||||
%Plane Faces of type plane :py:class:`cadquery.TypeSelector` 0..many
|
||||
>Y Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
<Y Face farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
>Y[-2] 2nd Face farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
<Y[0] 1st closest Fase in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
========= ======================================= ======================================================= ==========================
|
||||
|
||||
|
||||
.. _filteringedges:
|
||||
|
||||
Filtering Edges
|
||||
----------------
|
||||
|
||||
Some filter types are not supported for edges. The selector usually refers to the **direction** of the edge.
|
||||
|
||||
.. warning::
|
||||
|
||||
Non-linear edges are not selected for any selectors except type (%). Non-linear edges are never returned
|
||||
when these filters are applied.
|
||||
|
||||
The axis used in the listing below are for illustration: any axis would work similarly in each case.
|
||||
|
||||
|
||||
========= ======================================= ======================================================= ==========================
|
||||
Selector Selects Selector Class # objects returned
|
||||
========= ======================================= ======================================================= ==========================
|
||||
+Z Edges aligned in the Z direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
\|Z Edges parallel to z direction :py:class:`cadquery.ParallelDirSelector` 0..many
|
||||
-X Edges aligned in neg x direction :py:class:`cadquery.DirectionSelector` 0..many
|
||||
#Z Edges perpendicular to z direction :py:class:`cadquery.PerpendicularDirSelector` 0..many
|
||||
%Line Edges of type line :py:class:`cadquery.TypeSelector` 0..many
|
||||
>Y Edges farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
<Y Edges farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
>Y[1] 2nd closest edge in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
<Y[-2] 2nd farthest edge in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
========= ======================================= ======================================================= ==========================
|
||||
|
||||
|
||||
.. _filteringvertices:
|
||||
|
||||
Filtering Vertices
|
||||
-------------------
|
||||
|
||||
Only a few of the filter types apply to vertices. The location of the vertex is the subject of the filter
|
||||
|
||||
========= ======================================= ======================================================= ==========================
|
||||
Selector Selects Selector Class # objects returned
|
||||
========= ======================================= ======================================================= ==========================
|
||||
>Y Vertices farthest in the positive y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
<Y Vertices farthest in the negative y dir :py:class:`cadquery.DirectionMinMaxSelector` 0..many
|
||||
========= ======================================= ======================================================= ==========================
|
||||
|
||||
User-defined Directions
|
||||
-----------------------
|
||||
|
||||
It is possible to use user defined vectors as a basis for the selectors. For example:
|
||||
|
||||
.. cq_plot::
|
||||
|
||||
result = cq.Workplane("XY").box(10,10,10)
|
||||
|
||||
# chamfer only one edge
|
||||
result = result.edges('>(-1,1,0)').chamfer(1)
|
||||
|
||||
show_object(result)
|
19
Libs/cadquery/examples/FreeCAD/Ex001_Simple_Block.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
|
||||
# Create a 3D block based on the dimension variables above.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
result = cq.Workplane("XY").box(length, height, thickness)
|
||||
|
||||
# The following method is now outdated, but can still be used to display the
|
||||
# results of the script if you want
|
||||
# from Helpers import show
|
||||
# show(result) # Render the result of this script
|
||||
|
||||
show_object(result)
|
|
@ -0,0 +1,20 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
|
||||
# Create a block based on the dimensions above and add a 22mm center hole.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
# 2. The highest (max) Z face is selected and a new workplane is created on it.
|
||||
# 3. The new workplane is used to drill a hole through the block.
|
||||
# 3a. The hole is automatically centered in the workplane.
|
||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||
.faces(">Z").workplane().hole(center_hole_dia)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,34 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
length = 80.0 # Length of the block
|
||||
height = 60.0 # Height of the block
|
||||
thickness = 10.0 # Thickness of the block
|
||||
center_hole_dia = 22.0 # Diameter of center hole in block
|
||||
cbore_hole_diameter = 2.4 # Bolt shank/threads clearance hole diameter
|
||||
cbore_diameter = 4.4 # Bolt head pocket hole diameter
|
||||
cbore_depth = 2.1 # Bolt head pocket hole depth
|
||||
|
||||
# Create a 3D block based on the dimensions above and add a 22mm center hold
|
||||
# and 4 counterbored holes for bolts
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
# 2. The highest(max) Z face is selected and a new workplane is created on it.
|
||||
# 3. The new workplane is used to drill a hole through the block.
|
||||
# 3a. The hole is automatically centered in the workplane.
|
||||
# 4. The highest(max) Z face is selected and a new workplane is created on it.
|
||||
# 5. A for-construction rectangle is created on the workplane based on the
|
||||
# block's overall dimensions.
|
||||
# 5a. For-construction objects are used only to place other geometry, they
|
||||
# do not show up in the final displayed geometry.
|
||||
# 6. The vertices of the rectangle (corners) are selected, and a counter-bored
|
||||
# hole is placed at each of the vertices (all 4 of them at once).
|
||||
result = cq.Workplane("XY").box(length, height, thickness) \
|
||||
.faces(">Z").workplane().hole(center_hole_dia) \
|
||||
.faces(">Z").workplane() \
|
||||
.rect(length - 8.0, height - 8.0, forConstruction=True) \
|
||||
.vertices().cboreHole(cbore_hole_diameter, cbore_diameter, cbore_depth)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,29 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
circle_radius = 50.0 # Radius of the plate
|
||||
thickness = 13.0 # Thickness of the plate
|
||||
rectangle_width = 13.0 # Width of rectangular hole in cylindrical plate
|
||||
rectangle_length = 19.0 # Length of rectangular hole in cylindrical plate
|
||||
|
||||
# Extrude a cylindrical plate with a rectangular hole in the middle of it.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. The 2D geometry for the outer circle is created at the same time as the
|
||||
# rectangle that will create the hole in the center.
|
||||
# 2a. The circle and the rectangle will be automatically centered on the
|
||||
# workplane.
|
||||
# 2b. Unlike some other functions like the hole(), circle() takes
|
||||
# a radius and not a diameter.
|
||||
# 3. The circle and rectangle are extruded together, creating a cylindrical
|
||||
# plate with a rectangular hole in the center.
|
||||
# 3a. circle() and rect() could be changed to any other shape to completely
|
||||
# change the resulting plate and/or the hole in it.
|
||||
result = cq.Workplane("front").circle(circle_radius) \
|
||||
.rect(rectangle_width, rectangle_length) \
|
||||
.extrude(thickness)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,32 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
width = 2.0 # Overall width of the plate
|
||||
thickness = 0.25 # Thickness of the plate
|
||||
|
||||
# Extrude a plate outline made of lines and an arc
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Draws a line from the origin to an X position of the plate's width.
|
||||
# 2a. The starting point of a 2D drawing like this will be at the center of the
|
||||
# workplane (0, 0) unless the moveTo() function moves the starting point.
|
||||
# 3. A line is drawn from the last position straight up in the Y direction
|
||||
# 1.0 millimeters.
|
||||
# 4. An arc is drawn from the last point, through point (1.0, 1.5) which is
|
||||
# half-way back to the origin in the X direction and 0.5 mm above where
|
||||
# the last line ended at. The arc then ends at (0.0, 1.0), which is 1.0 mm
|
||||
# above (in the Y direction) where our first line started from.
|
||||
# 5. close() is called to automatically draw the last line for us and close
|
||||
# the sketch so that it can be extruded.
|
||||
# 5a. Without the close(), the 2D sketch will be left open and the extrude
|
||||
# operation will provide unpredictable results.
|
||||
# 6. The 2D sketch is extruded into a solid object of the specified thickness.
|
||||
result = cq.Workplane("front").lineTo(width, 0) \
|
||||
.lineTo(width, 1.0) \
|
||||
.threePointArc((1.0, 1.5), (0.0, 1.0)) \
|
||||
.close().extrude(thickness)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,35 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
circle_radius = 3.0 # The outside radius of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
|
||||
# Make a plate with two cutouts in it by moving the workplane center point
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 1b. The initial workplane center point is the center of the circle, at (0,0).
|
||||
# 2. A circle is created at the center of the workplane
|
||||
# 2a. Notice that circle() takes a radius and not a diameter
|
||||
result = cq.Workplane("front").circle(circle_radius)
|
||||
|
||||
# 3. The work center is movide to (1.5, 0.0) by calling center().
|
||||
# 3a. The new center is specified relative to the previous center,not
|
||||
# relative to global coordinates.
|
||||
# 4. A 0.5mm x 0.5mm 2D square is drawn inside the circle.
|
||||
# 4a. The plate has not been extruded yet, only 2D geometry is being created.
|
||||
result = result.center(1.5, 0.0).rect(0.5, 0.5)
|
||||
|
||||
# 5. The work center is moved again, this time to (-1.5, 1.5).
|
||||
# 6. A 2D circle is created at that new center with a radius of 0.25mm.
|
||||
result = result.center(-1.5, 1.5).circle(0.25)
|
||||
|
||||
# 7. All 2D geometry is extruded to the specified thickness of the plate.
|
||||
# 7a. The small circle and the square are enclosed in the outer circle of the
|
||||
# plate and so it is assumed that we want them to be cut out of the plate.
|
||||
# A separate cut operation is not needed.
|
||||
result = result.extrude(thickness)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
32
Libs/cadquery/examples/FreeCAD/Ex007_Using_Point_Lists.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
plate_radius = 2.0 # The radius of the plate that will be extruded
|
||||
hole_pattern_radius = 0.25 # Radius of circle where the holes will be placed
|
||||
thickness = 0.125 # The thickness of the plate that will be extruded
|
||||
|
||||
# Make a plate with 4 holes in it at various points in a polar arrangement from
|
||||
# the center of the workplane.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. A 2D circle is drawn that will become though outer profile of the plate.
|
||||
r = cq.Workplane("front").circle(plate_radius)
|
||||
|
||||
# 3. Push 4 points on the stack that will be used as the center points of the
|
||||
# holes.
|
||||
r = r.pushPoints([(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)])
|
||||
|
||||
# 4. This circle() call will operate on all four points, putting a circle at
|
||||
# each one.
|
||||
r = r.circle(hole_pattern_radius)
|
||||
|
||||
# 5. All 2D geometry is extruded to the specified thickness of the plate.
|
||||
# 5a. The small hole circles are enclosed in the outer circle of the plate and
|
||||
# so it is assumed that we want them to be cut out of the plate. A
|
||||
# separate cut operation is not needed.
|
||||
result = r.extrude(thickness)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
39
Libs/cadquery/examples/FreeCAD/Ex008_Polygon_Creation.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
width = 3.0 # The width of the plate
|
||||
height = 4.0 # The height of the plate
|
||||
thickness = 0.25 # The thickness of the plate
|
||||
polygon_sides = 6 # The number of sides that the polygonal holes should have
|
||||
polygon_dia = 1.0 # The diameter of the circle enclosing the polygon points
|
||||
|
||||
# Create a plate with two polygons cut through it
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. A 3D box is created in one box() operation to represent the plate.
|
||||
# 2a. The box is centered around the origin, which creates a result that may
|
||||
# be unituitive when the polygon cuts are made.
|
||||
# 3. 2 points are pushed onto the stack and will be used as centers for the
|
||||
# polygonal holes.
|
||||
# 4. The two polygons are created, on for each point, with one call to
|
||||
# polygon() using the number of sides and the circle that bounds the
|
||||
# polygon.
|
||||
# 5. The polygons are cut thru all objects that are in the line of extrusion.
|
||||
# 5a. A face was not selected, and so the polygons are created on the
|
||||
# workplane. Since the box was centered around the origin, the polygons end
|
||||
# up being in the center of the box. This makes them cut from the center to
|
||||
# the outside along the normal (positive direction).
|
||||
# 6. The polygons are cut through all objects, starting at the center of the
|
||||
# box/plate and going "downward" (opposite of normal) direction. Functions
|
||||
# like cutBlind() assume a positive cut direction, but cutThruAll() assumes
|
||||
# instead that the cut is made from a max direction and cuts downward from
|
||||
# that max through all objects.
|
||||
result = cq.Workplane("front").box(width, height, thickness) \
|
||||
.pushPoints([(0, 0.75), (0, -0.75)]) \
|
||||
.polygon(polygon_sides, polygon_dia) \
|
||||
.cutThruAll()
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
39
Libs/cadquery/examples/FreeCAD/Ex009_Polylines.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import cadquery as cq
|
||||
|
||||
# These can be modified rather than hardcoding values for each dimension.
|
||||
# Define up our Length, Height, Width, and thickness of the beam
|
||||
(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
|
||||
|
||||
# Define the points that the polyline will be drawn to/thru
|
||||
pts = [
|
||||
(W/2.0, H/2.0),
|
||||
(W/2.0, (H/2.0 - t)),
|
||||
(t/2.0, (H/2.0-t)),
|
||||
(t/2.0, (t - H/2.0)),
|
||||
(W/2.0, (t - H/2.0)),
|
||||
(W/2.0, H/-2.0),
|
||||
(0, H/-2.0)
|
||||
]
|
||||
|
||||
# We generate half of the I-beam outline and then mirror it to create the full
|
||||
# I-beam.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. moveTo() is used to move the first point from the origin (0, 0) to
|
||||
# (0, 10.0), with 10.0 being half the height (H/2.0). If this is not done
|
||||
# the first line will start from the origin, creating an extra segment that
|
||||
# will cause the extrude to have an invalid shape.
|
||||
# 3. The polyline function takes a list of points and generates the lines
|
||||
# through all the points at once.
|
||||
# 3. Only half of the I-beam profile has been drawn so far. That half is
|
||||
# mirrored around the Y-axis to create the complete I-beam profile.
|
||||
# 4. The I-beam profile is extruded to the final length of the beam.
|
||||
result = cq.Workplane("front").moveTo(0, H/2.0) \
|
||||
.polyline(pts) \
|
||||
.mirrorY() \
|
||||
.extrude(L)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,27 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane to create the spline on to extrude.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
s = cq.Workplane("XY")
|
||||
|
||||
# The points that the spline will pass through
|
||||
sPnts = [
|
||||
(2.75, 1.5),
|
||||
(2.5, 1.75),
|
||||
(2.0, 1.5),
|
||||
(1.5, 1.0),
|
||||
(1.0, 1.25),
|
||||
(0.5, 1.0),
|
||||
(0, 1.0)
|
||||
]
|
||||
|
||||
# 2. Generate our plate with the spline feature and make sure it is a
|
||||
# closed entity
|
||||
r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts).close()
|
||||
|
||||
# 3. Extrude to turn the wire into a plate
|
||||
result = r.extrude(0.5)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,20 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. A horizontal line is drawn on the workplane with the hLine function.
|
||||
# 2a. 1.0 is the distance, not coordinate. hLineTo allows using xCoordinate
|
||||
# not distance.
|
||||
r = cq.Workplane("front").hLine(1.0)
|
||||
|
||||
# 3. Draw a series of vertical and horizontal lines with the vLine and hLine
|
||||
# functions.
|
||||
r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0)
|
||||
|
||||
# 4. Mirror the geometry about the Y axis and extrude it into a 3D object.
|
||||
result = r.mirrorY().extrude(0.25)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,16 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a 3D box that will have a hole placed in it later.
|
||||
result = cq.Workplane("front").box(2, 3, 0.5)
|
||||
|
||||
# 3. Find the top-most face with the >Z max selector.
|
||||
# 3a. Establish a new workplane to build geometry on.
|
||||
# 3b. Create a hole down into the box.
|
||||
result = result.faces(">Z").workplane().hole(0.5)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,21 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a 3D box that will have a hole placed in it later.
|
||||
result = cq.Workplane("front").box(3, 2, 0.5)
|
||||
|
||||
# 3. Select the lower left vertex and make a workplane.
|
||||
# 3a. The top-most Z face is selected using the >Z selector.
|
||||
# 3b. The lower-left vertex of the faces is selected with the <XY selector.
|
||||
# 3c. A new workplane is created on the vertex to build future geometry on.
|
||||
result = result.faces(">Z").vertices("<XY").workplane()
|
||||
|
||||
# 4. A circle is drawn with the selected vertex as its center.
|
||||
# 4a. The circle is cut down through the box to cut the corner out.
|
||||
result = result.circle(1.0).cutThruAll()
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
20
Libs/cadquery/examples/FreeCAD/Ex014_Offset_Workplanes.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a 3D box that will have geometry based off it later.
|
||||
result = cq.Workplane("front").box(3, 2, 0.5)
|
||||
|
||||
# 3. The lowest face in the X direction is selected with the <X selector.
|
||||
# 4. A new wokrplane is created
|
||||
# 4a.The workplane is offset from the object surface so that it is not touching
|
||||
# the original box.
|
||||
result = result.faces("<X").workplane(offset=0.75)
|
||||
|
||||
# 5. Creates a thin disc on the offset workplane that is floating near the box.
|
||||
result = result.circle(1.0).extrude(0.5)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
22
Libs/cadquery/examples/FreeCAD/Ex015_Rotated_Workplanes.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import cadquery as cq
|
||||
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects the top-most Z face of the box.
|
||||
# 4. Creates a new workplane and then moves and rotates it with the
|
||||
# transformed function.
|
||||
# 5. Creates a for-construction rectangle that only exists to use for placing
|
||||
# other geometry.
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
# 7a. Since the workplane is rotated, this results in angled holes in the face.
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
||||
.workplane() \
|
||||
.transformed(offset=(0, -1.5, 1.0), rotate=(60, 0, 0)) \
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,21 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a block with holes in each corner of a rectangle on that workplane.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects the top-most Z face of the box.
|
||||
# 4. Creates a new workplane to build new geometry on.
|
||||
# 5. Creates a for-construction rectangle that only exists to use for placing
|
||||
# other geometry.
|
||||
# 6. Selects the vertices of the for-construction rectangle.
|
||||
# 7. Places holes at the center of each selected vertex.
|
||||
result = cq.Workplane("front").box(2, 2, 0.5)\
|
||||
.faces(">Z").workplane() \
|
||||
.rect(1.5, 1.5, forConstruction=True).vertices() \
|
||||
.hole(0.125)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,14 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a hollow box that's open on both ends with a thin wall.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects faces with normal in +z direction.
|
||||
# 4. Create a shell by cutting out the top-most Z face.
|
||||
result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.05)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
20
Libs/cadquery/examples/FreeCAD/Ex018_Making_Lofts.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a lofted section between a rectangle and a circular section.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the named plane orientation "front" to define the workplane, meaning
|
||||
# that the positive Z direction is "up", and the negative Z direction
|
||||
# is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects the top-most Z face of the box.
|
||||
# 4. Draws a 2D circle at the center of the the top-most face of the box.
|
||||
# 5. Creates a workplane 3 mm above the face the circle was drawn on.
|
||||
# 6. Draws a 2D circle on the new, offset workplane.
|
||||
# 7. Creates a loft between the circle and the rectangle.
|
||||
result = cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z") \
|
||||
.circle(1.5).workplane(offset=3.0) \
|
||||
.rect(0.75, 0.5) \
|
||||
.loft(combine=True)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
19
Libs/cadquery/examples/FreeCAD/Ex019_Counter_Sunk_Holes.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a plate with 4 counter-sunk holes in it.
|
||||
# 1. Establishes a workplane using an XY object instead of a named plane.
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects the top-most face of the box and established a workplane on that.
|
||||
# 4. Draws a for-construction rectangle on the workplane which only exists for
|
||||
# placing other geometry.
|
||||
# 5. Selects the corner vertices of the rectangle and places a counter-sink
|
||||
# hole, using each vertex as the center of a hole using the cskHole()
|
||||
# function.
|
||||
# 5a. When the depth of the counter-sink hole is set to None, the hole will be
|
||||
# cut through.
|
||||
result = cq.Workplane(cq.Plane.XY()).box(4, 2, 0.5).faces(">Z") \
|
||||
.workplane().rect(3.5, 1.5, forConstruction=True) \
|
||||
.vertices().cskHole(0.125, 0.25, 82.0, depth=None)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,13 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a plate with 4 rounded corners in the Z-axis.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects all edges that are parallel to the Z axis.
|
||||
# 4. Creates fillets on each of the selected edges with the specified radius.
|
||||
result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
25
Libs/cadquery/examples/FreeCAD/Ex021_Splitting_an_Object.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Create a simple block with a hole through it that we can split.
|
||||
# 1. Establishes a workplane that an object can be built on.
|
||||
# 1a. Uses the X and Y origins to define the workplane, meaning that the
|
||||
# positive Z direction is "up", and the negative Z direction is "down".
|
||||
# 2. Creates a plain box to base future geometry on with the box() function.
|
||||
# 3. Selects the top-most face of the box and establishes a workplane on it
|
||||
# that new geometry can be built on.
|
||||
# 4. Draws a 2D circle on the new workplane and then uses it to cut a hole
|
||||
# all the way through the box.
|
||||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane() \
|
||||
.circle(0.25).cutThruAll()
|
||||
|
||||
# 5. Selects the face furthest away from the origin in the +Y axis direction.
|
||||
# 6. Creates an offset workplane that is set in the center of the object.
|
||||
# 6a. One possible improvement to this script would be to make the dimensions
|
||||
# of the box variables, and then divide the Y-axis dimension by 2.0 and
|
||||
# use that to create the offset workplane.
|
||||
# 7. Uses the embedded workplane to split the object, keeping only the "top"
|
||||
# portion.
|
||||
result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
19
Libs/cadquery/examples/FreeCAD/Ex022_Classic_OCC_Bottle.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Set up the length, width, and thickness
|
||||
(L, w, t) = (20.0, 6.0, 3.0)
|
||||
s = cq.Workplane("XY")
|
||||
|
||||
# Draw half the profile of the bottle and extrude it
|
||||
p = s.center(-L / 2.0, 0).vLine(w / 2.0) \
|
||||
.threePointArc((L / 2.0, w / 2.0 + t), (L, w / 2.0)).vLine(-w / 2.0) \
|
||||
.mirrorX().extrude(30.0, True)
|
||||
|
||||
# Make the neck
|
||||
p.faces(">Z").workplane().circle(3.0).extrude(2.0, True)
|
||||
|
||||
# Make a shell
|
||||
result = p.faces(">Z").shell(0.3)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
77
Libs/cadquery/examples/FreeCAD/Ex023_Parametric_Enclosure.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import cadquery as cq
|
||||
|
||||
# Parameter definitions
|
||||
p_outerWidth = 100.0 # Outer width of box enclosure
|
||||
p_outerLength = 150.0 # Outer length of box enclosure
|
||||
p_outerHeight = 50.0 # Outer height of box enclosure
|
||||
|
||||
p_thickness = 3.0 # Thickness of the box walls
|
||||
p_sideRadius = 10.0 # Radius for the curves around the sides of the bo
|
||||
p_topAndBottomRadius = 2.0 # Radius for the curves on the top and bottom edges
|
||||
|
||||
p_screwpostInset = 12.0 # How far in from the edges the screwposts should be
|
||||
p_screwpostID = 4.0 # Inner diameter of the screwpost holes, should be roughly screw diameter not including threads
|
||||
p_screwpostOD = 10.0 # Outer diameter of the screwposts. Determines overall thickness of the posts
|
||||
|
||||
p_boreDiameter = 8.0 # Diameter of the counterbore hole, if any
|
||||
p_boreDepth = 1.0 # Depth of the counterbore hole, if
|
||||
p_countersinkDiameter = 0.0 # Outer diameter of countersink. Should roughly match the outer diameter of the screw head
|
||||
p_countersinkAngle = 90.0 # Countersink angle (complete angle between opposite sides, not from center to one side)
|
||||
p_lipHeight = 1.0 # Height of lip on the underside of the lid. Sits inside the box body for a snug fit.
|
||||
|
||||
# Outer shell
|
||||
oshell = cq.Workplane("XY").rect(p_outerWidth, p_outerLength) \
|
||||
.extrude(p_outerHeight + p_lipHeight)
|
||||
|
||||
# Weird geometry happens if we make the fillets in the wrong order
|
||||
if p_sideRadius > p_topAndBottomRadius:
|
||||
oshell.edges("|Z").fillet(p_sideRadius)
|
||||
oshell.edges("#Z").fillet(p_topAndBottomRadius)
|
||||
else:
|
||||
oshell.edges("#Z").fillet(p_topAndBottomRadius)
|
||||
oshell.edges("|Z").fillet(p_sideRadius)
|
||||
|
||||
# Inner shell
|
||||
ishell = oshell.faces("<Z").workplane(p_thickness, True)\
|
||||
.rect((p_outerWidth - 2.0 * p_thickness), (p_outerLength - 2.0 * p_thickness))\
|
||||
.extrude((p_outerHeight - 2.0 * p_thickness), False) # Set combine false to produce just the new boss
|
||||
ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
|
||||
|
||||
# Make the box outer box
|
||||
box = oshell.cut(ishell)
|
||||
|
||||
# Make the screwposts
|
||||
POSTWIDTH = (p_outerWidth - 2.0 * p_screwpostInset)
|
||||
POSTLENGTH = (p_outerLength - 2.0 * p_screwpostInset)
|
||||
|
||||
postCenters = box.faces(">Z").workplane(-p_thickness)\
|
||||
.rect(POSTWIDTH, POSTLENGTH, forConstruction=True)\
|
||||
.vertices()
|
||||
|
||||
for v in postCenters.all():
|
||||
v.circle(p_screwpostOD / 2.0).circle(p_screwpostID / 2.0)\
|
||||
.extrude((-1.0) * ((p_outerHeight + p_lipHeight) - (2.0 * p_thickness)), True)
|
||||
|
||||
# Split lid into top and bottom parts
|
||||
(lid, bottom) = box.faces(">Z").workplane(-p_thickness - p_lipHeight).split(keepTop=True, keepBottom=True).all()
|
||||
|
||||
# Translate the lid, and subtract the bottom from it to produce the lid inset
|
||||
lowerLid = lid.translate((0, 0, -p_lipHeight))
|
||||
cutlip = lowerLid.cut(bottom).translate((p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight))
|
||||
|
||||
# Compute centers for counterbore/countersink or counterbore
|
||||
topOfLidCenters = cutlip.faces(">Z").workplane().rect(POSTWIDTH, POSTLENGTH, forConstruction=True).vertices()
|
||||
|
||||
# Add holes of the desired type
|
||||
if p_boreDiameter > 0 and p_boreDepth > 0:
|
||||
topOfLid = topOfLidCenters.cboreHole(p_screwpostID, p_boreDiameter, p_boreDepth, (2.0) * p_thickness)
|
||||
elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
|
||||
topOfLid = topOfLidCenters.cskHole(p_screwpostID, p_countersinkDiameter, p_countersinkAngle, (2.0) * p_thickness)
|
||||
else:
|
||||
topOfLid= topOfLidCenters.hole(p_screwpostID, 2.0 * p_thickness)
|
||||
|
||||
# Return the combined result
|
||||
result = topOfLid.combineSolids(bottom)
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
|
@ -0,0 +1,23 @@
|
|||
# This example is meant to be used from within the CadQuery module of FreeCAD.
|
||||
import cadquery
|
||||
import FreeCAD
|
||||
|
||||
# Create a new document that we can draw our model on
|
||||
newDoc = FreeCAD.newDocument()
|
||||
|
||||
# Shows a 1x1x1 FreeCAD cube in the display
|
||||
initialBox = newDoc.addObject("Part::Box", "initialBox")
|
||||
newDoc.recompute()
|
||||
|
||||
# Make a CQ object
|
||||
cqBox = cadquery.CQ(cadquery.Solid(initialBox.Shape))
|
||||
|
||||
# Extrude a peg
|
||||
newThing = cqBox.faces(">Z").workplane().circle(0.5).extrude(0.25)
|
||||
|
||||
# Add a FreeCAD object to the tree and then store a CQ object in it
|
||||
nextShape = newDoc.addObject("Part::Feature", "nextShape")
|
||||
nextShape.Shape = newThing.val().wrapped
|
||||
|
||||
# Rerender the doc to see what the new solid looks like
|
||||
newDoc.recompute()
|
21
Libs/cadquery/examples/FreeCAD/Ex025_Revolution.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import cadquery as cq
|
||||
|
||||
# The dimensions of the model. These can be modified rather than changing the
|
||||
# shape's code directly.
|
||||
rectangle_width = 10.0
|
||||
rectangle_length = 10.0
|
||||
angle_degrees = 360.0
|
||||
|
||||
# Revolve a cylinder from a rectangle
|
||||
# Switch comments around in this section to try the revolve operation with different parameters
|
||||
result = cq.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve()
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, False).revolve(angle_degrees)
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5))
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5, -5),(-5, 5))
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length).revolve(angle_degrees,(-5,-5),(-5,5), False)
|
||||
|
||||
# Revolve a donut with square walls
|
||||
#result = cadquery.Workplane("XY").rect(rectangle_width, rectangle_length, True).revolve(angle_degrees, (20, 0), (20, 10))
|
||||
|
||||
# Displays the result of this script
|
||||
show_object(result)
|
56
Libs/cadquery/examples/FreeCAD/Ex026_Lego_Brick.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# This script can create any regular rectangular Lego(TM) Brick
|
||||
import cadquery as cq
|
||||
|
||||
#####
|
||||
# Inputs
|
||||
######
|
||||
lbumps = 1 # number of bumps long
|
||||
wbumps = 1 # number of bumps wide
|
||||
thin = True # True for thin, False for thick
|
||||
|
||||
#
|
||||
# Lego Brick Constants-- these make a lego brick a lego :)
|
||||
#
|
||||
pitch = 8.0
|
||||
clearance = 0.1
|
||||
bumpDiam = 4.8
|
||||
bumpHeight = 1.8
|
||||
if thin:
|
||||
height = 3.2
|
||||
else:
|
||||
height = 9.6
|
||||
|
||||
t = (pitch - (2 * clearance) - bumpDiam) / 2.0
|
||||
postDiam = pitch - t # works out to 6.5
|
||||
total_length = lbumps*pitch - 2.0*clearance
|
||||
total_width = wbumps*pitch - 2.0*clearance
|
||||
|
||||
# make the base
|
||||
s = cq.Workplane("XY").box(total_length, total_width, height)
|
||||
|
||||
# shell inwards not outwards
|
||||
s = s.faces("<Z").shell(-1.0 * t)
|
||||
|
||||
# make the bumps on the top
|
||||
s = s.faces(">Z").workplane(). \
|
||||
rarray(pitch, pitch, lbumps, wbumps, True).circle(bumpDiam / 2.0) \
|
||||
.extrude(bumpHeight)
|
||||
|
||||
# add posts on the bottom. posts are different diameter depending on geometry
|
||||
# solid studs for 1 bump, tubes for multiple, none for 1x1
|
||||
tmp = s.faces("<Z").workplane(invert=True)
|
||||
|
||||
if lbumps > 1 and wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, wbumps - 1, center=True). \
|
||||
circle(postDiam / 2.0).circle(bumpDiam / 2.0).extrude(height - t)
|
||||
elif lbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, lbumps - 1, 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
elif wbumps > 1:
|
||||
tmp = tmp.rarray(pitch, pitch, 1, wbumps - 1, center=True). \
|
||||
circle(t).extrude(height - t)
|
||||
else:
|
||||
tmp = s
|
||||
|
||||
# Render the solid
|
||||
show_object(tmp)
|
85
Libs/cadquery/examples/FreeCAD/Ex027_Remote_Enclosure.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
import cadquery as cq
|
||||
|
||||
exploded = False # when true, moves the base away from the top so we see
|
||||
showTop = True # When true, the top is rendered.
|
||||
showCover = True # When true, the cover is rendered
|
||||
|
||||
width = 2.2 # Nominal x dimension of the part
|
||||
height = 0.5 # Height from bottom top to the top of the top :P
|
||||
length = 1.5 # Nominal y dimension of the part
|
||||
trapezoidFudge = 0.7 # ratio of trapezoid bases. set to 1.0 for cube
|
||||
xHoleOffset = 0.500 # Holes are distributed symetrically about each axis
|
||||
yHoleOffset = 0.500
|
||||
zFilletRadius = 0.50 # Fillet radius of corners perp. to Z axis.
|
||||
yFilletRadius = 0.250 # Fillet readius of the top edge of the case
|
||||
lipHeight = 0.1 # The height of the lip on the inside of the cover
|
||||
wallThickness = 0.06 # Wall thickness for the case
|
||||
coverThickness = 0.2 # Thickness of the cover plate
|
||||
holeRadius = 0.30 # Button hole radius
|
||||
counterSyncAngle = 100 # Countersink angle.
|
||||
|
||||
xyplane = cq.Workplane("XY")
|
||||
yzplane = cq.Workplane("YZ")
|
||||
|
||||
|
||||
def trapezoid(b1, b2, h):
|
||||
"Defines a symetrical trapezoid in the XY plane."
|
||||
|
||||
y = h / 2
|
||||
x1 = b1 / 2
|
||||
x2 = b2 / 2
|
||||
return (xyplane.moveTo(-x1, y)
|
||||
.polyline([(x1, y),
|
||||
(x2, -y),
|
||||
(-x2, -y)]).close())
|
||||
|
||||
|
||||
# Defines our base shape: a box with fillets around the vertical edges.
|
||||
# This has to be a function because we need to create multiple copies of
|
||||
# the shape.
|
||||
def base(h):
|
||||
return (trapezoid(width, width * trapezoidFudge, length)
|
||||
.extrude(h)
|
||||
.translate((0, 0, height / 2))
|
||||
.edges("Z")
|
||||
.fillet(zFilletRadius))
|
||||
|
||||
# start with the base shape
|
||||
top = (base(height)
|
||||
# then fillet the top edge
|
||||
.edges(">Z")
|
||||
.fillet(yFilletRadius)
|
||||
# shell the solid from the bottom face, with a .060" wall thickness
|
||||
.faces("<Z")
|
||||
.shell(-wallThickness)
|
||||
# cut five button holes into the top face in a cross pattern.
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.pushPoints([(0, 0),
|
||||
(-xHoleOffset, 0),
|
||||
(0, -yHoleOffset),
|
||||
(xHoleOffset, 0),
|
||||
(0, yHoleOffset)])
|
||||
.cskHole(diameter=holeRadius,
|
||||
cskDiameter=holeRadius * 1.5,
|
||||
cskAngle=counterSyncAngle))
|
||||
|
||||
# the bottom cover begins with the same basic shape as the top
|
||||
cover = (base(coverThickness)
|
||||
# we need to move it upwards into the parent solid slightly.
|
||||
.translate((0, 0, -coverThickness + lipHeight))
|
||||
# now we subtract the top from the cover. This produces a lip on the
|
||||
# solid NOTE: that this does not account for mechanical tolerances.
|
||||
# But it looks cool.
|
||||
.cut(top)
|
||||
# try to fillet the inner edge of the cover lip. Technically this
|
||||
# fillets every edge perpendicular to the Z axis.
|
||||
.edges("#Z")
|
||||
.fillet(.020)
|
||||
.translate((0, 0, -0.5 if exploded else 0)))
|
||||
|
||||
# Conditionally render the parts
|
||||
if showTop:
|
||||
show_object(top)
|
||||
if showCover:
|
||||
show_object(cover)
|
25
Libs/cadquery/examples/FreeCAD/Ex028_Numpy.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# NOTE: This example may not run correctly in some revisions of FreeCAD 0.16
|
||||
import numpy as np
|
||||
import cadquery as cq
|
||||
|
||||
# Square side and offset in x and y.
|
||||
side = 10
|
||||
offset = 5
|
||||
|
||||
# Define the locations that the polyline will be drawn to/thru.
|
||||
# The polyline is defined as numpy.array so that operations like translation
|
||||
# of all points are simplified.
|
||||
pts = np.array([
|
||||
(0, 0),
|
||||
(side, 0),
|
||||
(side, side),
|
||||
(0, side),
|
||||
(0, 0)
|
||||
]) + [offset, offset]
|
||||
|
||||
result = cq.Workplane('XY') \
|
||||
.polyline(pts).close().extrude(2) \
|
||||
.faces('+Z').workplane().circle(side / 2).extrude(1)
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|
182
Libs/cadquery/examples/FreeCAD/Ex029_Braille.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import cadquery as cq
|
||||
|
||||
# text_lines is a list of text lines.
|
||||
# FreeCAD in braille (converted with braille-converter:
|
||||
# https://github.com/jpaugh/braille-converter.git).
|
||||
text_lines = ['⠠ ⠋ ⠗ ⠑ ⠑ ⠠ ⠉ ⠠ ⠁ ⠠ ⠙']
|
||||
# See http://www.tiresias.org/research/reports/braille_cell.htm for examples
|
||||
# of braille cell geometry.
|
||||
horizontal_interdot = 2.5
|
||||
vertical_interdot = 2.5
|
||||
horizontal_intercell = 6
|
||||
vertical_interline = 10
|
||||
dot_height = 0.5
|
||||
dot_diameter = 1.3
|
||||
|
||||
base_thickness = 1.5
|
||||
|
||||
# End of configuration.
|
||||
BrailleCellGeometry = namedtuple('BrailleCellGeometry',
|
||||
('horizontal_interdot',
|
||||
'vertical_interdot',
|
||||
'intercell',
|
||||
'interline',
|
||||
'dot_height',
|
||||
'dot_diameter'))
|
||||
|
||||
|
||||
class Point(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __add__(self, other):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.x, self.y)[index]
|
||||
|
||||
def __str__(self):
|
||||
return '({}, {})'.format(self.x, self.y)
|
||||
|
||||
|
||||
def brailleToPoints(text, cell_geometry):
|
||||
# Unicode bit pattern (cf. https://en.wikipedia.org/wiki/Braille_Patterns).
|
||||
mask1 = 0b00000001
|
||||
mask2 = 0b00000010
|
||||
mask3 = 0b00000100
|
||||
mask4 = 0b00001000
|
||||
mask5 = 0b00010000
|
||||
mask6 = 0b00100000
|
||||
mask7 = 0b01000000
|
||||
mask8 = 0b10000000
|
||||
masks = (mask1, mask2, mask3, mask4, mask5, mask6, mask7, mask8)
|
||||
|
||||
# Corresponding dot position
|
||||
w = cell_geometry.horizontal_interdot
|
||||
h = cell_geometry.vertical_interdot
|
||||
pos1 = Point(0, 2 * h)
|
||||
pos2 = Point(0, h)
|
||||
pos3 = Point(0, 0)
|
||||
pos4 = Point(w, 2 * h)
|
||||
pos5 = Point(w, h)
|
||||
pos6 = Point(w, 0)
|
||||
pos7 = Point(0, -h)
|
||||
pos8 = Point(w, -h)
|
||||
pos = (pos1, pos2, pos3, pos4, pos5, pos6, pos7, pos8)
|
||||
|
||||
# Braille blank pattern (u'\u2800').
|
||||
blank = '⠀'
|
||||
points = []
|
||||
# Position of dot1 along the x-axis (horizontal).
|
||||
character_origin = 0
|
||||
for c in text:
|
||||
for m, p in zip(masks, pos):
|
||||
delta_to_blank = ord(c) - ord(blank)
|
||||
if (m & delta_to_blank):
|
||||
points.append(p + Point(character_origin, 0))
|
||||
character_origin += cell_geometry.intercell
|
||||
return points
|
||||
|
||||
|
||||
def get_plate_height(text_lines, cell_geometry):
|
||||
# cell_geometry.vertical_interdot is also used as space between base
|
||||
# borders and characters.
|
||||
return (2 * cell_geometry.vertical_interdot +
|
||||
2 * cell_geometry.vertical_interdot +
|
||||
(len(text_lines) - 1) * cell_geometry.interline)
|
||||
|
||||
|
||||
def get_plate_width(text_lines, cell_geometry):
|
||||
# cell_geometry.horizontal_interdot is also used as space between base
|
||||
# borders and characters.
|
||||
max_len = max([len(t) for t in text_lines])
|
||||
return (2 * cell_geometry.horizontal_interdot +
|
||||
cell_geometry.horizontal_interdot +
|
||||
(max_len - 1) * cell_geometry.intercell)
|
||||
|
||||
|
||||
def get_cylinder_radius(cell_geometry):
|
||||
"""Return the radius the cylinder should have
|
||||
|
||||
The cylinder have the same radius as the half-sphere make the dots (the
|
||||
hidden and the shown part of the dots).
|
||||
The radius is such that the spherical cap with diameter
|
||||
cell_geometry.dot_diameter has a height of cell_geometry.dot_height.
|
||||
"""
|
||||
h = cell_geometry.dot_height
|
||||
r = cell_geometry.dot_diameter / 2
|
||||
return (r ** 2 + h ** 2) / 2 / h
|
||||
|
||||
|
||||
def get_base_plate_thickness(plate_thickness, cell_geometry):
|
||||
"""Return the height on which the half spheres will sit"""
|
||||
return (plate_thickness +
|
||||
get_cylinder_radius(cell_geometry) -
|
||||
cell_geometry.dot_height)
|
||||
|
||||
|
||||
def make_base(text_lines, cell_geometry, plate_thickness):
|
||||
base_width = get_plate_width(text_lines, cell_geometry)
|
||||
base_height = get_plate_height(text_lines, cell_geometry)
|
||||
base_thickness = get_base_plate_thickness(plate_thickness, cell_geometry)
|
||||
base = cq.Workplane('XY').box(base_width, base_height, base_thickness,
|
||||
centered=(False, False, False))
|
||||
return base
|
||||
|
||||
|
||||
def make_embossed_plate(text_lines, cell_geometry):
|
||||
"""Make an embossed plate with dots as spherical caps
|
||||
|
||||
Method:
|
||||
- make a thin plate on which sit cylinders
|
||||
- fillet the upper edge of the cylinders so to get pseudo half-spheres
|
||||
- make the union with a thicker plate so that only the sphere caps stay
|
||||
"visible".
|
||||
"""
|
||||
base = make_base(text_lines, cell_geometry, base_thickness)
|
||||
|
||||
dot_pos = []
|
||||
base_width = get_plate_width(text_lines, cell_geometry)
|
||||
base_height = get_plate_height(text_lines, cell_geometry)
|
||||
y = base_height - 3 * cell_geometry.vertical_interdot
|
||||
line_start_pos = Point(cell_geometry.horizontal_interdot, y)
|
||||
for text in text_lines:
|
||||
dots = brailleToPoints(text, cell_geometry)
|
||||
dots = [p + line_start_pos for p in dots]
|
||||
dot_pos += dots
|
||||
line_start_pos += Point(0, -cell_geometry.interline)
|
||||
|
||||
r = get_cylinder_radius(cell_geometry)
|
||||
base = base.faces('>Z').vertices('<XY').workplane() \
|
||||
.pushPoints(dot_pos).circle(r) \
|
||||
.extrude(r)
|
||||
# Make a fillet almost the same radius to get a pseudo spherical cap.
|
||||
base = base.faces('>Z').edges() \
|
||||
.fillet(r - 0.001)
|
||||
hidding_box = cq.Workplane('XY').box(
|
||||
base_width, base_height, base_thickness, centered=(False, False, False))
|
||||
result = hidding_box.union(base)
|
||||
return result
|
||||
|
||||
_cell_geometry = BrailleCellGeometry(
|
||||
horizontal_interdot,
|
||||
vertical_interdot,
|
||||
horizontal_intercell,
|
||||
vertical_interline,
|
||||
dot_height,
|
||||
dot_diameter)
|
||||
|
||||
if base_thickness < get_cylinder_radius(_cell_geometry):
|
||||
raise ValueError('Base thickness should be at least {}'.format(dot_height))
|
||||
|
||||
show_object(make_embossed_plate(text_lines, _cell_geometry))
|
|
@ -0,0 +1,45 @@
|
|||
import cadquery as cq
|
||||
|
||||
# The dimensions of the model. These can be modified rather than changing the
|
||||
# object's code directly.
|
||||
width = 400
|
||||
height = 500
|
||||
thickness = 2
|
||||
|
||||
# Create a plate with two polygons cut through it
|
||||
result = cq.Workplane("front").box(width, height, thickness)
|
||||
|
||||
h_sep = 60
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
h_sep4DB9 = 30
|
||||
for idx in range(8):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
for idx in range(8):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
for idx in range(8):
|
||||
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()
|
||||
|
||||
for idx in range(4):
|
||||
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()
|
||||
|
||||
# Render the solid
|
||||
show_object(result)
|