* Added docs for new extrude capabilities Added examples for extrude and cutblind until extrusion parameter Updated weird leftover text in Example page * added note on relative axis coords for Workplane.revolve * added mixed extrude cutblind example
1411 lines
48 KiB
ReStructuredText
1411 lines
48 KiB
ReStructuredText
.. _examples:
|
||
|
||
*********************************
|
||
CadQuery Examples
|
||
*********************************
|
||
|
||
|
||
|
||
The examples on this page can help you learn how to build objects with CadQuery.
|
||
|
||
They are organized from simple to complex, so working through them in order is the best way to absorb them.
|
||
|
||
Each example lists the API elements used in the example for easy reference.
|
||
Items introduced in the example are marked with a **!**
|
||
|
||
|
||
|
||
.. note::
|
||
|
||
We strongly recommend installing `CQ-editor <https://github.com/CadQuery/CQ-editor>`_,
|
||
so that you can work along with these examples interactively. See :ref:`installation` for more info.
|
||
|
||
If you do, make sure to take these steps so that they work:
|
||
|
||
1. import cadquery as cq
|
||
2. add the line ``show_object(result)`` at the end. The samples below are autogenerated, but they use a different
|
||
syntax than the models on the website need to be.
|
||
|
||
.. warning::
|
||
|
||
* You have to have an svg capable browser to view these!
|
||
|
||
.. contents:: List of Examples
|
||
:backlinks: entry
|
||
|
||
|
||
Simple Rectangular Plate
|
||
------------------------
|
||
|
||
Just about the simplest possible example, a rectangular box
|
||
|
||
.. cadquery::
|
||
|
||
result = cadquery.Workplane("front").box(2.0, 2.0, 0.5)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane` **!**
|
||
* :py:meth:`Workplane.box` **!**
|
||
|
||
Plate with Hole
|
||
------------------------
|
||
|
||
A rectangular box, but with a hole added.
|
||
|
||
"\>Z" selects the top most face of the resulting box. The hole is located in the center because the default origin
|
||
of a working plane is the projected origin of the last Workplane, the last Workplane having origin at (0,0,0) the
|
||
projection is at the center of the face. The default hole depth is through the entire part.
|
||
|
||
|
||
.. cadquery::
|
||
|
||
# The dimensions of the box. These can be modified rather than changing the
|
||
# object's code directly.
|
||
length = 80.0
|
||
height = 60.0
|
||
thickness = 10.0
|
||
center_hole_dia = 22.0
|
||
|
||
# Create a box based on the dimensions above and add a 22mm center hole
|
||
result = (cq.Workplane("XY").box(length, height, thickness)
|
||
.faces(">Z").workplane().hole(center_hole_dia))
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.hole` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.box`
|
||
|
||
An extruded prismatic solid
|
||
-------------------------------
|
||
|
||
Build a prismatic solid using extrusion. After a drawing operation, the center of the previous object
|
||
is placed on the stack, and is the reference for the next operation. So in this case, the rect() is drawn
|
||
centered on the previously draw circle.
|
||
|
||
By default, rectangles and circles are centered around the previous working point.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").circle(2.0).rect(0.5, 0.75).extrude(0.5)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.circle` **!**
|
||
* :py:meth:`Workplane.rect` **!**
|
||
* :py:meth:`Workplane.extrude` **!**
|
||
* :py:meth:`Workplane`
|
||
|
||
Building Profiles using lines and arcs
|
||
--------------------------------------
|
||
|
||
Sometimes you need to build complex profiles using lines and arcs. This example builds a prismatic
|
||
solid from 2D operations.
|
||
|
||
2D operations maintain a current point, which is initially at the origin. Use close() to finish a
|
||
closed curve.
|
||
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("front").lineTo(2.0, 0).lineTo(2.0, 1.0).threePointArc((1.0, 1.5), (0.0, 1.0))
|
||
.close().extrude(0.25))
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.threePointArc` **!**
|
||
* :py:meth:`Workplane.lineTo` **!**
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane`
|
||
|
||
Moving The Current working point
|
||
---------------------------------
|
||
|
||
In this example, a closed profile is required, with some interior features as well.
|
||
|
||
This example also demonstrates using multiple lines of code instead of longer chained commands,
|
||
though of course in this case it was possible to do it in one long line as well.
|
||
|
||
A new work plane center can be established at any point.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").circle(3.0) # current point is the center of the circle, at (0, 0)
|
||
result = result.center(1.5, 0.0).rect(0.5, 0.5) # new work center is (1.5, 0.0)
|
||
|
||
result = result.center(-1.5, 1.5).circle(0.25) # new work center is (0.0, 1.5).
|
||
# The new center is specified relative to the previous center, not global coordinates!
|
||
|
||
result = result.extrude(0.25)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.center` **!**
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.rect`
|
||
* :py:meth:`Workplane.extrude`
|
||
|
||
Using Point Lists
|
||
---------------------------
|
||
|
||
Sometimes you need to create a number of features at various locations, and using :py:meth:`Workplane.center`
|
||
is too cumbersome.
|
||
|
||
You can use a list of points to construct multiple objects at once. Most construction methods,
|
||
like :py:meth:`Workplane.circle` and :py:meth:`Workplane.rect`, will operate on multiple points if they are on the stack
|
||
|
||
.. cadquery::
|
||
|
||
r = cq.Workplane("front").circle(2.0) # make base
|
||
r = r.pushPoints([(1.5, 0), (0, 1.5), (-1.5, 0), (0, -1.5)]) # now four points are on the stack
|
||
r = r.circle(0.25) # circle will operate on all four points
|
||
result = r.extrude(0.125) # make prism
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.points` **!**
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.extrude`
|
||
|
||
Polygons
|
||
-------------------------
|
||
|
||
You can create polygons for each stack point if you would like. Useful in 3d printers whose firmware does not
|
||
correct for small hole sizes.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("front").box(3.0, 4.0, 0.25).pushPoints([(0, 0.75), (0, -0.75)])
|
||
.polygon(6, 1.0).cutThruAll())
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.polygon` **!**
|
||
* :py:meth:`Workplane.pushPoints`
|
||
* :py:meth:`Workplane.box`
|
||
|
||
Polylines
|
||
-------------------------
|
||
|
||
:py:meth:`Workplane.polyline` allows creating a shape from a large number of chained points connected by lines.
|
||
|
||
This example uses a polyline to create one half of an i-beam shape, which is mirrored to create the final profile.
|
||
|
||
.. cadquery::
|
||
|
||
(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)
|
||
pts = [
|
||
(0, H/2.0),
|
||
(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)
|
||
]
|
||
result = cq.Workplane("front").polyline(pts).mirrorY().extrude(L)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.polyline` **!**
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`Workplane.mirrorY`
|
||
* :py:meth:`Workplane.extrude`
|
||
|
||
|
||
|
||
Defining an Edge with a Spline
|
||
------------------------------
|
||
|
||
This example defines a side using a spline curve through a collection of points. Useful when you have an edge that
|
||
needs a complex profile
|
||
|
||
.. cadquery::
|
||
|
||
s = cq.Workplane("XY")
|
||
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)
|
||
]
|
||
r = s.lineTo(3.0, 0).lineTo(3.0, 1.0).spline(sPnts, includeCurrent=True).close()
|
||
result = r.extrude(0.5)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.spline` **!**
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`Workplane.close`
|
||
* :py:meth:`Workplane.lineTo`
|
||
* :py:meth:`Workplane.extrude`
|
||
|
||
Mirroring Symmetric Geometry
|
||
-----------------------------
|
||
|
||
You can mirror 2D geometry when your shape is symmetric. In this example we also
|
||
introduce horizontal and vertical lines, which make for slightly easier coding.
|
||
|
||
|
||
.. cadquery::
|
||
|
||
r = cq.Workplane("front").hLine(1.0) # 1.0 is the distance, not coordinate
|
||
r = r.vLine(0.5).hLine(-0.25).vLine(-0.25).hLineTo(0.0) # hLineTo allows using xCoordinate not distance
|
||
result = r.mirrorY().extrude(0.25) # mirror the geometry and extrude
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.hLine` **!**
|
||
* :py:meth:`Workplane.vLine` **!**
|
||
* :py:meth:`Workplane.hLineTo` **!**
|
||
* :py:meth:`Workplane.mirrorY` **!**
|
||
* :py:meth:`Workplane.mirrorX` **!**
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`Workplane.extrude`
|
||
|
||
Mirroring 3D Objects
|
||
-----------------------------
|
||
|
||
.. cadquery::
|
||
|
||
result0 = (cadquery.Workplane("XY")
|
||
.moveTo(10, 0)
|
||
.lineTo(5, 0)
|
||
.threePointArc((3.9393, 0.4393), (3.5, 1.5))
|
||
.threePointArc((3.0607, 2.5607), (2, 3))
|
||
.lineTo(1.5, 3)
|
||
.threePointArc((0.4393, 3.4393), (0, 4.5))
|
||
.lineTo(0, 13.5)
|
||
.threePointArc((0.4393, 14.5607), (1.5, 15))
|
||
.lineTo(28, 15)
|
||
.lineTo(28, 13.5)
|
||
.lineTo(24, 13.5)
|
||
.lineTo(24, 11.5)
|
||
.lineTo(27, 11.5)
|
||
.lineTo(27, 10)
|
||
.lineTo(22, 10)
|
||
.lineTo(22, 13.2)
|
||
.lineTo(14.5, 13.2)
|
||
.lineTo(14.5, 10)
|
||
.lineTo(12.5, 10)
|
||
.lineTo(12.5, 13.2)
|
||
.lineTo(5.5, 13.2)
|
||
.lineTo(5.5, 2)
|
||
.threePointArc((5.793, 1.293), (6.5, 1))
|
||
.lineTo(10, 1)
|
||
.close())
|
||
result = result0.extrude(100)
|
||
|
||
result = result.rotate((0, 0, 0), (1, 0, 0), 90)
|
||
|
||
result = result.translate(result.val().BoundingBox().center.multiply(-1))
|
||
|
||
mirXY_neg = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, -30))
|
||
mirXY_pos = result.mirror(mirrorPlane="XY", basePointVector=(0, 0, 30))
|
||
mirZY_neg = result.mirror(mirrorPlane="ZY", basePointVector=(-30, 0, 0))
|
||
mirZY_pos = result.mirror(mirrorPlane="ZY", basePointVector=(30, 0, 0))
|
||
|
||
result = result.union(mirXY_neg).union(mirXY_pos).union(mirZY_neg).union(mirZY_pos)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.moveTo`
|
||
* :py:meth:`Workplane.lineTo`
|
||
* :py:meth:`Workplane.threePointArc`
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane.mirror`
|
||
* :py:meth:`Workplane.union`
|
||
* :py:meth:`Workplane.rotate`
|
||
|
||
|
||
Mirroring From Faces
|
||
-----------------------------
|
||
|
||
This example shows how you can mirror about a selected face. It also shows how the resulting mirrored object can be unioned immediately with the referenced mirror geometry.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("XY")
|
||
.line(0, 1)
|
||
.line(1, 0)
|
||
.line(0, -.5)
|
||
.close()
|
||
.extrude(1))
|
||
|
||
result = result.mirror(result.faces(">X"), union=True)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.line`
|
||
* :py:meth:`Workplane.close`
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane.mirror`
|
||
* :py:meth:`Workplane.union`
|
||
|
||
Creating Workplanes on Faces
|
||
-----------------------------
|
||
|
||
This example shows how to locate a new workplane on the face of a previously created feature.
|
||
|
||
.. note::
|
||
Using workplanes in this way are a key feature of CadQuery. Unlike a typical 3d scripting
|
||
language, using work planes frees you from tracking the position of various features in
|
||
variables, and allows the model to adjust itself with removing redundant dimensions
|
||
|
||
The :py:meth:`Workplane.faces()` method allows you to select the faces of a resulting solid. It
|
||
accepts a selector string or object, that allows you to target a single face, and make a workplane
|
||
oriented on that face.
|
||
|
||
Keep in mind that by default the origin of a new workplane is calculated by forming a plane from the
|
||
selected face and projecting the previous origin onto that plane. This behaviour can be changed
|
||
through the centerOption argument of :py:meth:`Workplane.workplane`.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(2, 3, 0.5) # make a basic prism
|
||
result = result.faces(">Z").workplane().hole(0.5) # find the top-most face and make a hole
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.faces` **!**
|
||
* :py:meth:`StringSyntaxSelector` **!**
|
||
* :ref:`selector_reference` **!**
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane`
|
||
|
||
Locating a Workplane on a vertex
|
||
---------------------------------
|
||
|
||
Normally, the :py:meth:`Workplane.workplane` method requires a face to be selected. But if a vertex
|
||
is selected **immediately after a face**, :py:meth:`Workplane.workplane` with the centerOption
|
||
argument set to CenterOfMass will locate the workplane on the face, with the origin at the vertex
|
||
instead of at the center of the face
|
||
|
||
The example also introduces :py:meth:`Workplane.cutThruAll`, which makes a cut through the entire
|
||
part, no matter how deep the part is.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(3,2, 0.5) # make a basic prism
|
||
result = result.faces(">Z").vertices("<XY").workplane(centerOption="CenterOfMass") # select the lower left vertex and make a workplane
|
||
result = result.circle(1.0).cutThruAll() # cut the corner out
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.cutThruAll` **!**
|
||
|
||
* :ref:`selector_reference` **!**
|
||
* :py:meth:`Workplane.vertices` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane`
|
||
* :py:meth:`StringSyntaxSelector` **!**
|
||
|
||
Offset Workplanes
|
||
--------------------------
|
||
|
||
Workplanes do not have to lie exactly on a face. When you make a workplane, you can define it at an offset
|
||
from an existing face.
|
||
|
||
This example uses an offset workplane to make a compound object, which is perfectly valid!
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(3, 2, 0.5) # make a basic prism
|
||
result = result.faces("<X").workplane(offset=0.75) # workplane is offset from the object surface
|
||
result = result.circle(1.0).extrude(0.5) # disc
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.extrude`
|
||
* :ref:`selector_reference` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane`
|
||
|
||
Copying Workplanes
|
||
--------------------------
|
||
|
||
An existing CQ object can copy a workplane from another CQ object.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("front").circle(1).extrude(10) # make a cylinder
|
||
# We want to make a second cylinder perpendicular to the first,
|
||
# but we have no face to base the workplane off
|
||
.copyWorkplane(
|
||
# create a temporary object with the required workplane
|
||
cq.Workplane("right", origin=(-5, 0, 0))
|
||
).circle(1).extrude(10))
|
||
|
||
.. topic:: API References
|
||
|
||
.. hlist:
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.copyWorkplane` **!**
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane`
|
||
|
||
Rotated Workplanes
|
||
--------------------------
|
||
|
||
You can create a rotated work plane by specifying angles of rotation relative to another workplane
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("front").box(4.0, 4.0, 0.25).faces(">Z").workplane()
|
||
.transformed(offset=cq.Vector(0, -1.5, 1.0),rotate=cq.Vector(60, 0, 0))
|
||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.25))
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.transformed` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.rect`
|
||
* :py:meth:`Workplane.faces`
|
||
|
||
Using construction Geometry
|
||
---------------------------
|
||
|
||
You can draw shapes to use the vertices as points to locate other features. Features that are used to
|
||
locate other features, rather than to create them, are called ``Construction Geometry``
|
||
|
||
In the example below, a rectangle is drawn, and its vertices are used to locate a set of holes.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("front").box(2, 2, 0.5).faces(">Z").workplane()
|
||
.rect(1.5, 1.5, forConstruction=True).vertices().hole(0.125))
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.rect` (forConstruction=True)
|
||
* :ref:`selector_reference`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.hole`
|
||
* :py:meth:`Workplane`
|
||
|
||
Shelling To Create Thin features
|
||
--------------------------------
|
||
|
||
Shelling converts a solid object into a shell of uniform thickness.
|
||
|
||
To shell an object and 'hollow out' the inside pass a negative thickness parameter
|
||
to the :py:meth:`Workplane.shell()` method of a shape.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(2, 2, 2).shell(-0.1)
|
||
|
||
A positive thickness parameter wraps an object with filleted outside edges
|
||
and the original object will be the 'hollowed out' portion.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(2, 2, 2).shell(0.1)
|
||
|
||
Use face selectors to select a face to be removed from the resulting hollow shape.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("front").box(2, 2, 2).faces("+Z").shell(0.1)
|
||
|
||
Multiple faces can be removed using more complex selectors.
|
||
|
||
.. cadquery::
|
||
|
||
result = (
|
||
cq.Workplane("front")
|
||
.box(2, 2, 2)
|
||
.faces("+Z or -X or +X")
|
||
.shell(0.1)
|
||
)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.shell` **!**
|
||
* :ref:`selector_reference`
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane`
|
||
|
||
Making Lofts
|
||
--------------------------------------------
|
||
|
||
A loft is a solid swept through a set of wires. This example creates lofted section between a rectangle
|
||
and a circular section.
|
||
|
||
.. cadquery::
|
||
|
||
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))
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.loft` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.rect`
|
||
|
||
Extruding until a given face
|
||
--------------------------------------------
|
||
|
||
Sometimes you will want to extrude a wire until a given face that can be not planar or where you
|
||
might not know easily the distance you have to extrude to. In such cases you can use `next`, `last`
|
||
or even give a :class:`~cadquery.Face` object for the `until` argument of
|
||
:meth:`~cadquery.Workplane.extrude`.
|
||
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane(origin = (20,0,0))
|
||
.circle(2)
|
||
.revolve(180, (-20,0,0),(-20,-1,0))
|
||
.center(-20,0)
|
||
.workplane()
|
||
.rect(20,4)
|
||
.extrude("next")
|
||
)
|
||
|
||
The same behaviour is available with :meth:`~cadquery.Workplane.cutBlind` and as you can see it is
|
||
also possible to work on several :class:`~cadquery.Wire` objects at a time (the
|
||
same is true for :meth:`~cadquery.Workplane.extrude`).
|
||
|
||
.. cadquery::
|
||
|
||
skycrappers_locations = [(-16,1),(-8,0),(7,0.2),(17,-1.2)]
|
||
angles = iter([15,0,-8,10])
|
||
skycrappers = (cq.Workplane()
|
||
.pushPoints(skycrappers_locations)
|
||
.eachpoint(lambda loc: (cq.Workplane()
|
||
.rect(5,16)
|
||
.workplane(offset=10)
|
||
.ellipse(3,8)
|
||
.workplane(offset=10)
|
||
.slot2D(20,5, 90)
|
||
.loft()
|
||
.rotateAboutCenter((0,0,1),next(angles))
|
||
.val().located(loc)
|
||
)
|
||
)
|
||
)
|
||
|
||
result = (skycrappers
|
||
.transformed((0,-90,0))
|
||
.moveTo(15,0)
|
||
.rect(3,3, forConstruction=True)
|
||
.vertices()
|
||
.circle(1)
|
||
.cutBlind("last")
|
||
)
|
||
|
||
Here is a typical situation where extruding and cuting until a given surface is very handy. It allows us to extrude or cut until a curved surface without overlapping issues.
|
||
|
||
.. cadquery::
|
||
|
||
import cadquery as cq
|
||
|
||
sphere = cq.Workplane().sphere(5)
|
||
base = (cq.Workplane(origin=(0,0,-2))
|
||
.box(12,12,10)
|
||
.cut(sphere)
|
||
.edges("|Z")
|
||
.fillet(2)
|
||
)
|
||
sphere_face = base.faces(">>X[2] and (not |Z) and (not |Y)").val()
|
||
base = (base
|
||
.faces("<Z")
|
||
.workplane()
|
||
.circle(2)
|
||
.extrude(10)
|
||
)
|
||
|
||
shaft = (cq.Workplane()
|
||
.sphere(4.5)
|
||
.circle(1.5)
|
||
.extrude(20)
|
||
)
|
||
|
||
spherical_joint = (base.union(shaft)
|
||
.faces(">X")
|
||
.workplane(centerOption="CenterOfMass")
|
||
.move(0,4)
|
||
.slot2D(10,2,90)
|
||
.cutBlind(sphere_face)
|
||
.workplane(offset=10)
|
||
.move(0,2)
|
||
.circle(0.9)
|
||
.extrude("next")
|
||
)
|
||
|
||
result = spherical_joint
|
||
|
||
.. warning::
|
||
|
||
If the wire you want to extrude cannot be fully projected on the target surface, the result will
|
||
be unpredictable. Furthermore the algorithm in charge of finding the candidates faces do it's
|
||
search by counting all the faces intersected by a line created from your wire center along your
|
||
extrusion direction. So make sure your wire can be projected on your target face to avoid
|
||
unexpected behaviour.
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 3
|
||
|
||
* :py:meth:`Workplane.cutBlind` **!**
|
||
* :py:meth:`Workplane.rect`
|
||
* :py:meth:`Workplane.ellipse`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.slot2D`
|
||
* :py:meth:`Workplane.loft`
|
||
* :py:meth:`Workplane.rotateAboutCenter`
|
||
* :py:meth:`Workplane.transformed`
|
||
* :py:meth:`Workplane.moveTo`
|
||
* :py:meth:`Workplane.circle`
|
||
|
||
|
||
Making Counter-bored and Counter-sunk Holes
|
||
----------------------------------------------
|
||
|
||
Counterbored and countersunk holes are so common that CadQuery creates macros to create them in a single step.
|
||
|
||
Similar to :py:meth:`Workplane.hole`, these functions operate on a list of points as well as a single point.
|
||
|
||
.. cadquery::
|
||
|
||
result = (
|
||
cq.Workplane(cq.Plane.XY())
|
||
.box(4, 2, 0.5)
|
||
.faces(">Z")
|
||
.workplane()
|
||
.rect(3.5, 1.5, forConstruction=True)
|
||
.vertices()
|
||
.cboreHole(0.125, 0.25, 0.125, depth=None)
|
||
)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.cboreHole` **!**
|
||
* :py:meth:`Workplane.cskHole` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.rect`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.vertices`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane`
|
||
|
||
Offsetting wires in 2D
|
||
----------------------
|
||
|
||
Two dimensional wires can be transformed with :py:meth:`Workplane.offset2D`. They can be offset
|
||
inwards or outwards, and with different techniques for extending the corners.
|
||
|
||
.. cadquery::
|
||
|
||
original = cq.Workplane().polygon(5, 10).extrude(0.1).translate((0, 0, 2))
|
||
arc = (
|
||
cq.Workplane()
|
||
.polygon(5, 10)
|
||
.offset2D(1, "arc")
|
||
.extrude(0.1)
|
||
.translate((0, 0, 1))
|
||
)
|
||
intersection = cq.Workplane().polygon(5, 10).offset2D(1, "intersection").extrude(0.1)
|
||
result = original.add(arc).add(intersection)
|
||
|
||
|
||
Using the forConstruction argument you can do the common task of offsetting a series of bolt holes
|
||
from the outline of an object. Here is the counterbore example from above but with the bolt holes
|
||
offset from the edges.
|
||
|
||
.. cadquery::
|
||
|
||
result = (
|
||
cq.Workplane()
|
||
.box(4, 2, 0.5)
|
||
.faces(">Z")
|
||
.edges()
|
||
.toPending()
|
||
.offset2D(-0.25, forConstruction=True)
|
||
.vertices()
|
||
.cboreHole(0.125, 0.25, 0.125, depth=None)
|
||
)
|
||
|
||
|
||
Note that :py:meth:`Workplane.edges` is for selecting objects. It does not add the selected edges to
|
||
pending edges in the modelling context, because this would result in your next extrusion including
|
||
everything you had only selected in addition to the lines you had drawn. To specify you want these
|
||
edges to be used in :py:meth:`Workplane.offset2D`, you call :py:meth:`Workplane.toPending` to
|
||
explicitly put them in the list of pending edges.
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.offset2D` **!**
|
||
* :py:meth:`Workplane.cboreHole`
|
||
* :py:meth:`Workplane.cskHole`
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.polygon`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.vertices`
|
||
* :py:meth:`Workplane.edges`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane`
|
||
|
||
|
||
Rounding Corners with Fillet
|
||
-----------------------------
|
||
|
||
Filleting is done by selecting the edges of a solid, and using the fillet function.
|
||
|
||
Here we fillet all of the edges of a simple plate.
|
||
|
||
.. cadquery::
|
||
|
||
result = cq.Workplane("XY").box(3, 3, 0.5).edges("|Z").fillet(0.125)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.fillet` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.edges`
|
||
* :py:meth:`Workplane`
|
||
|
||
Tagging objects
|
||
----------------
|
||
|
||
The :py:meth:`Workplane.tag` method can be used to tag a particular object in the chain with a string, so that it can be referred to later in the chain.
|
||
|
||
The :py:meth:`Workplane.workplaneFromTagged` method applies :py:meth:`Workplane.copyWorkplane` to a tagged object. For example, when extruding two different solids from a surface, after the first solid is extruded it can become difficult to reselect the original surface with CadQuery's other selectors.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("XY")
|
||
# create and tag the base workplane
|
||
.box(10, 10, 10).faces(">Z").workplane().tag("baseplane")
|
||
# extrude a cylinder
|
||
.center(-3, 0).circle(1).extrude(3)
|
||
# to reselect the base workplane, simply
|
||
.workplaneFromTagged("baseplane")
|
||
# extrude a second cylinder
|
||
.center(3, 0).circle(1).extrude(2))
|
||
|
||
|
||
Tags can also be used with most selectors, including :py:meth:`Workplane.vertices`, :py:meth:`Workplane.faces`, :py:meth:`Workplane.edges`, :py:meth:`Workplane.wires`, :py:meth:`Workplane.shells`, :py:meth:`Workplane.solids` and :py:meth:`Workplane.compounds`.
|
||
|
||
.. cadquery::
|
||
|
||
result = (cq.Workplane("XY")
|
||
# create a triangular prism and tag it
|
||
.polygon(3, 5).extrude(4).tag("prism")
|
||
# create a sphere that obscures the prism
|
||
.sphere(10)
|
||
# create features based on the prism's faces
|
||
.faces("<X", tag="prism").workplane().circle(1).cutThruAll()
|
||
.faces(">X", tag="prism").faces(">Y").workplane().circle(1).cutThruAll())
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.tag` **!**
|
||
* :py:meth:`Workplane.getTagged` **!**
|
||
* :py:meth:`Workplane.workplaneFromTagged` **!**
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane.cutThruAll`
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane`
|
||
|
||
A Parametric Bearing Pillow Block
|
||
------------------------------------
|
||
|
||
Combining a few basic functions, its possible to make a very good parametric bearing pillow block,
|
||
with just a few lines of code.
|
||
|
||
.. cadquery::
|
||
|
||
(length, height, bearing_diam, thickness, padding) = (30.0, 40.0, 22.0, 10.0, 8.0)
|
||
|
||
result = (cq.Workplane("XY").box(length, height, thickness).faces(">Z").workplane().hole(bearing_diam)
|
||
.faces(">Z").workplane()
|
||
.rect(length-padding, height-padding, forConstruction=True)
|
||
.vertices().cboreHole(2.4, 4.4, 2.1))
|
||
|
||
|
||
Splitting an Object
|
||
---------------------
|
||
|
||
You can split an object using a workplane, and retain either or both halves
|
||
|
||
.. cadquery::
|
||
|
||
c = cq.Workplane("XY").box(1, 1, 1).faces(">Z").workplane().circle(0.25).cutThruAll()
|
||
|
||
# now cut it in half sideways
|
||
result = c.faces(">Y").workplane(-0.5).split(keepTop=True)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.split` **!**
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.cutThruAll`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane`
|
||
|
||
The Classic OCC Bottle
|
||
----------------------
|
||
|
||
CadQuery is based on the OpenCascade.org (OCC) modeling Kernel. Those who are familiar with OCC know about the
|
||
famous 'bottle' example. `The bottle example in the OCCT online documentation <https://old.opencascade.com/doc/occt-7.5.0/overview/html/occt__tutorial.html>`_.
|
||
|
||
A pythonOCC version is listed `here <https://github.com/tpaviot/pythonocc-demos/blob/f3ea9b4f65a9dff482be04b153d4ce5ec2430e13/examples/core_classic_occ_bottle.py>`_.
|
||
|
||
Of course one difference between this sample and the OCC version is the length. This sample is one of the longer
|
||
ones at 13 lines, but that's very short compared to the pythonOCC version, which is 10x longer!
|
||
|
||
|
||
.. cadquery::
|
||
|
||
(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 = p.faces(">Z").workplane(centerOption="CenterOfMass").circle(3.0).extrude(2.0, True)
|
||
|
||
# Make a shell
|
||
result = p.faces(">Z").shell(0.3)
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 2
|
||
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane.mirrorX`
|
||
* :py:meth:`Workplane.threePointArc`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.vertices`
|
||
* :py:meth:`Workplane.vLine`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane`
|
||
|
||
A Parametric Enclosure
|
||
-----------------------
|
||
|
||
.. cadquery::
|
||
:height: 400px
|
||
|
||
# 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 box
|
||
p_topAndBottomRadius = 2.0 # Radius for the curves on the top and bottom edges of the box
|
||
|
||
p_screwpostInset = 12.0 # How far in from the edges the screw posts should be place.
|
||
p_screwpostID = 4.0 # Inner Diameter of the screw post holes, should be roughly screw diameter not including threads
|
||
p_screwpostOD = 10.0 # Outer Diameter of the screw posts.\nDetermines 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_flipLid = True # Whether to place the lid with the top facing down or not.
|
||
p_lipHeight = 1.0 # Height of lip on the underside of the lid.\nSits 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 = oshell.edges("|Z").fillet(p_sideRadius)
|
||
oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
|
||
else:
|
||
oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
|
||
oshell = 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 = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
|
||
|
||
# make the box outer box
|
||
box = oshell.cut(ishell)
|
||
|
||
# make the screw posts
|
||
POSTWIDTH = (p_outerWidth - 2.0*p_screwpostInset)
|
||
POSTLENGTH = (p_outerLength - 2.0*p_screwpostInset)
|
||
|
||
box = (box.faces(">Z").workplane(-p_thickness)
|
||
.rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
|
||
.vertices().circle(p_screwpostOD/2.0).circle(p_screwpostID/2.0)
|
||
.extrude(-1.0*(p_outerHeight + p_lipHeight - 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() # splits into two solids
|
||
|
||
# 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)
|
||
|
||
# flip lid upside down if desired
|
||
if p_flipLid:
|
||
topOfLid = topOfLid.rotateAboutCenter((1, 0, 0), 180)
|
||
|
||
# return the combined result
|
||
result = topOfLid.union(bottom)
|
||
|
||
|
||
.. topic:: Api References
|
||
|
||
.. hlist::
|
||
:columns: 3
|
||
|
||
* :py:meth:`Workplane.circle`
|
||
* :py:meth:`Workplane.rect`
|
||
* :py:meth:`Workplane.extrude`
|
||
* :py:meth:`Workplane.box`
|
||
* :py:meth:`Workplane.all`
|
||
* :py:meth:`Workplane.faces`
|
||
* :py:meth:`Workplane.vertices`
|
||
* :py:meth:`Workplane.edges`
|
||
* :py:meth:`Workplane.workplane`
|
||
* :py:meth:`Workplane.fillet`
|
||
* :py:meth:`Workplane.cut`
|
||
* :py:meth:`Workplane.union`
|
||
* :py:meth:`Workplane.rotateAboutCenter`
|
||
* :py:meth:`Workplane.cboreHole`
|
||
* :py:meth:`Workplane.cskHole`
|
||
* :py:meth:`Workplane.hole`
|
||
|
||
Lego Brick
|
||
-------------------
|
||
|
||
This script will produce any size regular rectangular Lego(TM) brick. Its only tricky because of the logic
|
||
regarding the underside of the brick.
|
||
|
||
.. cadquery::
|
||
:select: tmp
|
||
:height: 400px
|
||
|
||
#####
|
||
# Inputs
|
||
######
|
||
lbumps = 6 # number of bumps long
|
||
wbumps = 2 # 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
|
||
|
||
|
||
Braille Example
|
||
---------------------
|
||
|
||
.. cadquery::
|
||
:height: 400px
|
||
|
||
from collections import namedtuple
|
||
|
||
|
||
# text_lines is a list of text lines.
|
||
# 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)
|
||
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)
|
||
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))
|
||
|
||
result = make_embossed_plate(text_lines, _cell_geometry)
|
||
|
||
Panel With Various Connector Holes
|
||
-----------------------------------
|
||
|
||
.. cadquery::
|
||
:height: 400px
|
||
|
||
# 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()
|
||
|
||
|
||
Cycloidal gear
|
||
--------------
|
||
|
||
You can define complex geometries using the parametricCurve functionality.
|
||
This specific examples generates a helical cycloidal gear.
|
||
|
||
.. cadquery::
|
||
:height: 400px
|
||
|
||
import cadquery as cq
|
||
from math import sin, cos, pi, floor
|
||
|
||
# define the generating function
|
||
def hypocycloid(t, r1, r2):
|
||
return ((r1-r2)*cos(t)+r2*cos(r1/r2*t-t), (r1-r2)*sin(t)+r2*sin(-(r1/r2*t-t)))
|
||
|
||
def epicycloid(t, r1, r2):
|
||
return ((r1+r2)*cos(t)-r2*cos(r1/r2*t+t), (r1+r2)*sin(t)-r2*sin(r1/r2*t+t))
|
||
|
||
def gear(t, r1=4, r2=1):
|
||
if (-1)**(1+floor(t/2/pi*(r1/r2))) < 0:
|
||
return epicycloid(t, r1, r2)
|
||
else:
|
||
return hypocycloid(t, r1, r2)
|
||
|
||
# create the gear profile and extrude it
|
||
result = (cq.Workplane('XY').parametricCurve(lambda t: gear(t*2*pi, 6, 1))
|
||
.twistExtrude(15, 90).faces('>Z').workplane().circle(2).cutThruAll())
|