Skip to content

Commit

Permalink
Merge pull request #45 from gumyr/dev
Browse files Browse the repository at this point in the history
Release 0.7 Content
  • Loading branch information
gumyr authored Jun 4, 2022
2 parents bc08c5d + be12a7d commit 65f283b
Show file tree
Hide file tree
Showing 30 changed files with 2,004 additions and 564 deletions.
Binary file added dist/cq_warehouse-0.7.0-py3-none-any.whl
Binary file not shown.
Binary file added dist/cq_warehouse-0.7.0.tar.gz
Binary file not shown.
Binary file added docs/captive_nuts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 12 additions & 10 deletions docs/chain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ For example, one can create the chain for a bicycle with a rear deraileur as fol
.. code-block:: python
import cadquery as cq
import cq_warehouse.chain as Chain
from cq_warehouse.chain import Chain
MM = 1
derailleur_chain = Chain(
spkt_teeth=[32, 10, 10, 16],
positive_chain_wrap=[True, True, False, True],
spkt_locations=[
(0, 158.9 * MM, 50 * MM),
(+190 * MM, 0, 50 * MM),
(+190 * MM, 78.9 * MM, 50 * MM),
(+205 * MM, 158.9 * MM, 50 * MM)
]
spkt_teeth=[32, 10, 10, 16],
positive_chain_wrap=[True, True, False, True],
spkt_locations=[
(0, 158.9 * MM, 50 * MM),
(+190 * MM, 0, 50 * MM),
(+190 * MM, 78.9 * MM, 50 * MM),
(+205 * MM, 158.9 * MM, 50 * MM),
],
)
if "show_object" in locals():
show_object(derailleur_chain.cq_object, name="derailleur_chain")
show_object(derailleur_chain.cq_object, name="derailleur_chain")
The chain is created on the XY plane (methods to move the chain are described below)
with the sprocket centers being described by:
Expand Down
38 changes: 36 additions & 2 deletions docs/fastener.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Here is a list of the classes (and fastener types) provided:

* ``BradTeeNut``: Hilitchi
* ``DomedCapNut``: din1587
* ``HeatSetNut``: McMaster-Carr
* ``HeatSetNut``: McMaster-Carr, Hilitchi
* ``HexNut``: iso4033, iso4035, iso4032
* ``HexNutWithFlange``: din1665
* ``UnchamferedHexagonNut``: iso4036
Expand Down Expand Up @@ -149,7 +149,7 @@ parameters. All derived nuts inherit the same API as the base Nut class.

* ``BradTeeNut``: Hilitchi
* ``DomedCapNut``: din1587
* ``HeatSetNut``: McMaster-Carr
* ``HeatSetNut``: McMaster-Carr, Hilitchi
* ``HexNut``: iso4033, iso4035, iso4032
* ``HexNutWithFlange``: din1665
* ``UnchamferedHexagonNut``: iso4036
Expand Down Expand Up @@ -407,6 +407,40 @@ Note that with imperial sized holes (e.g. 7/16), the drill sizes could be a frac
or a numbered or lettered size (e.g. U). This information can be added to your designs with the
:ref:`drafting <drafting>` sub-package.

Screw and Hole Alignment
========================
If a ``depth`` parameter is provided to the hole methods when placing a screw, the screw will
be located in the provided assembly such that the tip of the screw to bottom out in the hole.
This may result in the screw extending above the top surface. For example:

.. literalinclude:: ../examples/screw_at_depth.py

Which results in:

.. image:: screw_at_depth.png
:alt: screw_at_depth

If no ``depth`` is provided, the hole will extend through the part and the screw will be
aligned with the surface.

Captive Nuts
============

The :meth:`~extensions_doc.Workplane.clearanceHole` method has a ``captiveNut`` parameter that
when used with a hex or square nut will create a hole that captures the nut such that it
can't spin. Here is an example:

.. literalinclude:: ../examples/captive_nuts.py

Which results in:

.. image:: captive_nuts.png
:alt: captive_nuts

The space around the nuts is controlled by the ``fit`` parameter.

Insert Nuts
===========

The :meth:`~extensions_doc.Workplane.insertHole` is much like the previous three custom hole
methods but creates holes for heat set inserts in plastic parts - commonly used in 3D printing.
Expand Down
Binary file added docs/finger_jointed_box_faces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/finger_jointed_boxes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions docs/finger_jointed_boxes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#################################
extensions - finger jointed boxes
#################################
CNC controlled laser cutters have enabled the creation of inexpensive finger jointed
boxes constructed from many materials. However, the creation of the patterns to feed
into the laser can be quite time consuming. The finger jointed boxes methods within
the cq_warehouse extensions package allow the creation of these patterns from a simple
solid object.

.. image:: finger_jointed_boxes.png
:alt: finger_jointed_boxes

*****
Usage
*****
To create these boxes, one just needs to create the solid object, select edges, and use the
:meth:`~extensions_doc.Workplane.makeFingerJoints` method, as follows:

.. literalinclude:: ../examples/finger_jointed_boxes.py
:language: python

.. doctest::

>>> polygon_box_assembly.areObjectsValid()
True
>>> polygon_box_assembly.doObjectsIntersect()
False


.. warning::
Not all shapes will result in a valid set of finger jointed box patterns.

Although the goal of this package is to enable the creation of a finger jointed
box from any object with planar faces, this goal has not been fully achieved.
Carefully inspect the output to ensure it is correct before cutting boxes
to avoid disappointment.


The Workplane used to create a finger jointed box must contain a solid object
(actually a Solid or Compound object), and one or more Edges that is to be jointed.
In the above example, all of the edges are selected except for those on the top
of the object which results in an open box. The resulting faces can be further
modified if required.

Here is an example of finger jointed faces of a simple box:

.. image:: finger_jointed_box_faces.png
:alt: finger_jointed_box_faces

Notice how the finger joints on the nearest corner are in a different pattern than
the simple fingers on the other parts of the box? This is done to avoid the
creation of a missing corner.

Finger joint size is calculated internally such that an integer number of finger joints
are present on each edge - i.e. if the ``targetFingerWidth`` would result in a partial
finger joint, the actual finger joint width will reduced such the number of finger
joints is rounded to an integer. This may result in finger joints on different
edges being different sizes.

The use of an Assembly is optional but is recommended to aid in the visual validation
of the output. Random colors are assigned to each of the box walls to aid this
validation. Corners are where errors are most likely to appear, either as interference
or as missing corners.

When working with shapes with non perpendicular faces (i.e. faces that don't meet at
90˚) the depth of the finger joint is calculated to compensate for the angle by either
making the joint extra deep (for angles greater than 90˚) or smaller (for angles less than 90˚).
Unfortunately, not all possible combinations of corner angles have been compensated for
so pay extra care when inspecting these corners.

The result of the :meth:`~extensions_doc.Workplane.makeFingerJoints` method is a set of
Faces within the Workplane aligned with the original solid object. To store these faces
as DXF files it is necessary to create a workplane oriented in the same way as the face
as shown in the above example.

The full API is as follows:

.. py:module:: extensions_doc
:noindex:

.. automethod:: Workplane.makeFingerJoints

The ``kerfWidth`` parameter can be used to compensate for the size of the laser cut
thus allowing a path for the laser to the created directly from the face. Check with
the manufacturer to see if this compensation is required.

**********
Validation
**********
To help validate the finger jointed box two Assembly methods are available:

* :meth:`~extensions_doc.Assembly.areObjectsValid`
* :meth:`~extensions_doc.Assembly.doObjectsIntersect`

Checking for intersecting objects within the Assembly (a general purpose method where every
pair of objects within the Assembly - in their given Location - are checked for an intersection)
will identify if there are overlapping finger joints but will not find missing fingers. To check
for missing corners, one can use the ``Volume`` method as follows:

.. code-block:: python
import cadquery as cq
import cq_warehouse.extensions
simple_box_assembly = Assembly()
simple_box = Workplane("XY").box(100, 80, 50)
simple_box_volume = simple_box.faces(">Z").shell(-5, kind="intersection").val().Volume()
simple_box_faces = (
simple_box.edges("not >Z").makeFingerJoints(
materialThickness=5,
targetFingerWidth=10,
baseAssembly=simple_box_assembly,
)
)
simple_box_assembly_volume_error = abs(
simple_box_assembly.toCompound().Volume() - simple_box_volume
)
print(f"Is volume correct: {simple_box_assembly_volume_error<1e-5=}")
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Table Of Contents
chain.rst
drafting.rst
extensions.rst
finger_jointed_boxes.rst
fastener.rst
sprocket.rst
thread.rst
Expand Down
Binary file added docs/screw_at_depth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions examples/captive_nuts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import cadquery as cq
from cq_warehouse.fastener import HexNut, SquareNut
import cq_warehouse.extensions

hex_nut = HexNut(size="M6-1", fastener_type="iso4033")
square_nut = SquareNut(size="M6-1", fastener_type="din557")
test_assembly = cq.Assembly()
block = (
cq.Workplane("XY")
.box(50, 50, 10)
.faces(">Z")
.workplane()
.pushPoints([(-12.5, 0)])
.clearanceHole(
fastener=hex_nut, fit="Loose", captiveNut=True, baseAssembly=test_assembly
)
.pushPoints([(+12.5, 0)])
.clearanceHole(fastener=square_nut, captiveNut=True, baseAssembly=test_assembly)
)
test_assembly.add(block, color=cq.Color("tan"))

if "show_object" in locals():
show_object(test_assembly, name="test_assembly")
109 changes: 109 additions & 0 deletions examples/embossing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Extensions Examples
name: extensions_examples.py
by: Gumyr
date: January 10th 2022
desc: Emboss examples.
license:
Copyright 2022 Gumyr
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.
"""
import timeit
from enum import Enum, auto
import cadquery as cq
import cq_warehouse.extensions

# The emboss examples
class Testcase(Enum):
EMBOSS_TEXT = auto()
EMBOSS_WIRE = auto()


# A sphere used as a projection target
sphere = cq.Solid.makeSphere(50, angleDegrees1=-90)

for example in list(Testcase):

# if example != Testcase.EMBOSS_WIRE:
# continue

if example == Testcase.EMBOSS_TEXT:
"""Emboss a text string onto a shape"""

starttime = timeit.default_timer()

arch_path = (
cq.Workplane(sphere)
.cut(
cq.Solid.makeCylinder(
80, 100, pnt=cq.Vector(-50, 0, -70), dir=cq.Vector(1, 0, 0)
)
)
.edges("<Z")
.val()
)
projected_text = sphere.embossText(
txt="emboss - 'the quick brown fox jumped over the lazy dog'",
fontsize=14,
font="Serif",
fontPath="/usr/share/fonts/truetype/freefont",
depth=3,
path=arch_path,
)

print(f"Example #{example} time: {timeit.default_timer() - starttime:0.2f}s")

if "show_object" in locals():
show_object(sphere, name="sphere_solid", options={"alpha": 0.8})
show_object(arch_path, name="arch_path", options={"alpha": 0.8})
show_object(projected_text, name="embossed_text")

elif example == Testcase.EMBOSS_WIRE:
"""Emboss a wire and use it to create a feature"""

starttime = timeit.default_timer()
target_object = cq.Solid.makeCylinder(
50, 100, pnt=cq.Vector(0, 0, -50), dir=cq.Vector(0, 0, 1)
)
path = cq.Workplane(target_object).section().edges().val()
to_emboss_wire = cq.Wire.makeRect(
80, 40, cq.Vector(), cq.Vector(0, 0, 1), cq.Vector(1, 0, 0)
)
embossed_wire = to_emboss_wire.embossToShape(
targetObject=target_object,
surfacePoint=path.positionAt(0),
surfaceXDirection=path.tangentAt(0),
tolerance=0.1,
)
embossed_edges = embossed_wire.Edges()
for i, e in enumerate(to_emboss_wire.Edges()):
target = e.Length()
actual = embossed_edges[i].Length()
print(
f"Edge lengths: target {target}, actual {actual}, difference {abs(target-actual)}"
)

print(f"Example #{example} time: {timeit.default_timer() - starttime:0.2f}s")

if "show_object" in locals():
show_object(target_object, name="target_object", options={"alpha": 0.8})
show_object(path, name="path")
show_object(to_emboss_wire, name="to_emboss_wire")
show_object(embossed_wire, name="embossed_wire")
Loading

0 comments on commit 65f283b

Please sign in to comment.