Skip to content

Commit

Permalink
Merge pull request #12 from gumyr/dev
Browse files Browse the repository at this point in the history
Release 0.4.2
  • Loading branch information
gumyr authored Dec 17, 2021
2 parents 86cfc20 + 64b8b47 commit f6be795
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 23 deletions.
93 changes: 93 additions & 0 deletions examples/align_fastener_holes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Align Fastener Holes Example
name: align_fastener_holes.py
by: Gumyr
date: December 11th 2021
desc: Example of using the pushFastenerLocations() method to align cq_warehouse.fastener
holes between to plates in an assembly.
license:
Copyright 2021 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 cadquery as cq
from cq_warehouse.fastener import SocketHeadCapScrew

# Create the screws that will fasten the plates together
cap_screw = SocketHeadCapScrew(
size="M2-0.4", length=6, fastener_type="iso4762", simple=False
)

# Two assemblies are required - the top will contain the screws
bracket_assembly = cq.Assembly(None, name="top_plate_assembly")
square_tube_assembly = cq.Assembly(None, name="base_plate_assembly")

# --- Angle Bracket ---

# Create an angle bracket and add clearance holes for the screws
angle_bracket = (
cq.Workplane("YZ")
.moveTo(-9, 1)
.hLine(10)
.vLine(-10)
.offset2D(1)
.extrude(10, both=True)
.faces(">Z")
.workplane()
.pushPoints([(5, -5), (-5, -5)])
.clearanceHole(fastener=cap_screw, counterSunk=False, baseAssembly=bracket_assembly)
.faces(">Y")
.workplane()
.pushPoints([(0, -7)])
.clearanceHole(fastener=cap_screw, counterSunk=False, baseAssembly=bracket_assembly)
)
# Add the top plate to the top assembly so it can be placed with the screws
bracket_assembly.add(angle_bracket, name="angle_bracket")
# Add the top plate and screws to the base assembly
square_tube_assembly.add(
bracket_assembly,
name="top_plate_assembly",
loc=cq.Location(cq.Vector(20, 10, 10)),
)

# --- Square Tube ---

# Create the square tube
square_tube = (
cq.Workplane("YZ").rect(18, 18).rect(14, 14).offset2D(1).extrude(30, both=True)
)
# Complete the square tube assembly by adding the square tube
square_tube_assembly.add(square_tube, name="square_tube")
# Add tap holes to the square tube that align with the angle bracket
square_tube = square_tube.pushFastenerLocations(
cap_screw, square_tube_assembly
).tapHole(fastener=cap_screw, counterSunk=False, depth=10)


# Where are the cap screw holes in the square tube?
for loc in square_tube_assembly.fastenerLocations(cap_screw):
print(loc)

# How many fasteners are used in the square_tube_assembly and all sub-assemblies
print(square_tube_assembly.fastenerQuantities())

if "show_object" in locals():
show_object(angle_bracket, name="angle_bracket")
show_object(square_tube, name="square_tube")
show_object(square_tube_assembly, name="square_tube_assembly")
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = cq_warehouse
version = 0.4.1
version = 0.4.2
author = Gumyr
author_email = gumyr9@gmail.com
description = A cadquery parametric part collection
Expand Down
2 changes: 1 addition & 1 deletion src/cq_warehouse.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: cq-warehouse
Version: 0.4.1
Version: 0.4.2
Summary: A cadquery parametric part collection
Home-page: https://github.com/gumyr/cq_warehouse
Author: Gumyr
Expand Down
101 changes: 83 additions & 18 deletions src/cq_warehouse/fastener.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
limitations under the License.
"""
from functools import reduce
from abc import ABC, abstractmethod
from typing import Literal, Tuple, Optional, List, TypeVar, Union
from math import sin, cos, tan, radians, pi, degrees, sqrt
Expand Down Expand Up @@ -1968,10 +1969,8 @@ def _fastenerHole(
),
)
washer_thicknesses += washer.washer_thickness
if hasattr(baseAssembly, "metadata"):
baseAssembly.metadata[baseAssembly.children[-1].name] = washer
else:
baseAssembly.metadata = {baseAssembly.children[-1].name: washer}
# Create a metadata entry associating the auto-generated name & fastener
baseAssembly.metadata[baseAssembly.children[-1].name] = washer

baseAssembly.add(
fastener.cq_object,
Expand All @@ -1981,10 +1980,8 @@ def _fastenerHole(
* (head_offset - fastener.length_offset() - washer_thicknesses)
),
)
if hasattr(baseAssembly, "metadata"):
baseAssembly.metadata[baseAssembly.children[-1].name] = fastener
else:
baseAssembly.metadata = {baseAssembly.children[-1].name: fastener}
# Create a metadata entry associating the auto-generated name & fastener
baseAssembly.metadata[baseAssembly.children[-1].name] = fastener

# Make holes in the stack solid object
part = self.cutEach(lambda loc: fastener_hole.moved(loc), True, False)
Expand Down Expand Up @@ -2096,19 +2093,23 @@ def _threadedHole(
cq.Workplane.threadedHole = _threadedHole


def _fastener_quantities(self, bom: bool = True) -> dict:
def _fastener_quantities(self, bom: bool = True, deep: bool = True) -> dict:
"""Generate a bill of materials of the fasteners in an assembly augmented by the hole methods
bom: returns fastener.info if True else fastener
"""
if self.metadata is None:
return None
assembly_list = []
if deep:
for _name, sub_assembly in self.traverse():
assembly_list.append(sub_assembly)
else:
assembly_list.append(self)

fasteners = []
for sub_assembly in assembly_list:
for value in sub_assembly.metadata.values():
if isinstance(value, (Screw, Nut, Washer)):
fasteners.append(value)

# Extract a list of only the fasteners from the metadata
fasteners = [
value
for value in self.metadata.values()
if isinstance(value, (Screw, Nut, Washer))
]
unique_fasteners = set(fasteners)
if bom:
quantities = {f.info: fasteners.count(f) for f in unique_fasteners}
Expand All @@ -2117,4 +2118,68 @@ def _fastener_quantities(self, bom: bool = True) -> dict:
return quantities


cq.Assembly.fastener_quantities = _fastener_quantities
cq.Assembly.fastenerQuantities = _fastener_quantities


def _location_str(self):
"""A __str__ method to the Location class"""
loc_tuple = self.toTuple()
return f"({str(loc_tuple[0])}, {str(loc_tuple[1])})"


cq.Location.__str__ = _location_str


def _fastener_locations(self, fastener: Union[Nut, Screw]) -> list[cq.Location]:
"""Generate a list of cadquery Locations for the given fastener relative to the Assembly"""

name_to_fastener = {}
base_assembly_structure = {}
# Extract a list of only the fasteners from the metadata
for (name, a) in self.traverse():
base_assembly_structure[name] = a
if a.metadata is None:
continue

for key, value in a.metadata.items():
if value == fastener:
name_to_fastener[key] = value

fastener_path_locations = {}
base_assembly_path = self._flatten()
for assembly_name, _assembly_pointer in base_assembly_path.items():
for fastener_name in name_to_fastener.keys():
if fastener_name in assembly_name:
parents = assembly_name.split("/")
fastener_path_locations[fastener_name] = [
base_assembly_structure[name].loc for name in parents
]

fastener_locations = [
reduce(lambda l1, l2: l1 * l2, locs)
for locs in fastener_path_locations.values()
]

return fastener_locations


cq.Assembly.fastenerLocations = _fastener_locations


def _push_fastener_locations(
self: T,
fastener: Union[Nut, Screw],
baseAssembly: cq.Assembly,
):
"""Push the Location(s) of the given fastener relative to the given Assembly onto the stack"""

# The locations need to be pushed as global not local object locations
ns = self.__class__()
ns.plane = cq.Plane(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1))
ns.parent = self
ns.objects = baseAssembly.fastenerLocations(fastener)
ns.ctx = self.ctx
return ns


cq.Workplane.pushFastenerLocations = _push_fastener_locations
2 changes: 1 addition & 1 deletion src/cq_warehouse/socket_head_cap_parameters.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Size,iso4762:dk ,iso4762:k ,iso4762:s ,iso4762:t ,iso4762:short,iso4762:long,asme_b18.3:dk ,asme_b18.3:k ,asme_b18.3:s ,asme_b18.3:t ,asme_b18.3:short,asme_b18.3:long
M1.6-0.35 ,3.14,1.6,1.5,0.7,2.5,16,,,,,,
M1.6-0.35,3.14,1.6,1.5,0.7,2.5,16,,,,,,
M2-0.4,3.98,2,1.5,1,3,20,,,,,,
M2.5-0.45,4.68,2.5,2,1.1,4,25,,,,,,
M3-0.5,5.68,3,2.5,1.3,5,30,,,,,,
Expand Down
68 changes: 66 additions & 2 deletions tests/fastener_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ def test_clearance_hole(self):
)
self.assertLess(box.Volume(), 1000)
self.assertEqual(len(pillow_block.children), 1)
self.assertEqual(pillow_block.fastener_quantities(bom=False)[screw], 1)
self.assertEqual(len(pillow_block.fastener_quantities(bom=True)), 1)
self.assertEqual(pillow_block.fastenerQuantities(bom=False)[screw], 1)
self.assertEqual(len(pillow_block.fastenerQuantities(bom=True)), 1)

def test_invalid_clearance_hole(self):
for fastener_class in Screw.__subclasses__() + Nut.__subclasses__():
Expand Down Expand Up @@ -436,6 +436,70 @@ def test_threaded_hole(self):
)
self.assertLess(box.Volume(), 8000)
self.assertEqual(len(pillow_block.children), 3)
self.assertEqual(len(pillow_block.fastenerLocations(screw)), 1)

def test_push_fastener_locations(self):
# Create the screws that will fasten the plates together
cap_screw = SocketHeadCapScrew(
size="M2-0.4", length=6, fastener_type="iso4762", simple=False
)

# Two assemblies are required - the top will contain the screws
bracket_assembly = cq.Assembly(None, name="top_plate_assembly")
square_tube_assembly = cq.Assembly(None, name="base_plate_assembly")

# Create an angle bracket and add clearance holes for the screws
angle_bracket = (
cq.Workplane("YZ")
.moveTo(-9, 1)
.hLine(10)
.vLine(-10)
.offset2D(1)
.extrude(10, both=True)
.faces(">Z")
.workplane()
.pushPoints([(5, -5), (-5, -5)])
.clearanceHole(
fastener=cap_screw, counterSunk=False, baseAssembly=bracket_assembly
)
.faces(">Y")
.workplane()
.pushPoints([(0, -7)])
.clearanceHole(
fastener=cap_screw, counterSunk=False, baseAssembly=bracket_assembly
)
)
# Add the top plate to the top assembly so it can be placed with the screws
bracket_assembly.add(angle_bracket, name="angle_bracket")
# Add the top plate and screws to the base assembly
square_tube_assembly.add(
bracket_assembly,
name="top_plate_assembly",
loc=cq.Location(cq.Vector(20, 10, 10)),
)

# Create the square tube
square_tube = (
cq.Workplane("YZ")
.rect(18, 18)
.rect(14, 14)
.offset2D(1)
.extrude(30, both=True)
)
original_tube_volume = square_tube.val().Volume()
# Complete the square tube assembly by adding the square tube
square_tube_assembly.add(square_tube, name="square_tube")
# Add tap holes to the square tube that align with the angle bracket
square_tube = square_tube.pushFastenerLocations(
cap_screw, square_tube_assembly
).tapHole(fastener=cap_screw, counterSunk=False, depth=10)
self.assertLess(square_tube.val().Volume(), original_tube_volume)

# Where are the cap screw holes in the square tube?
fastener_positions = [(25.0, 5.0, 12.0), (15.0, 5.0, 12.0), (20.0, 12.0, 5.0)]
for i, loc in enumerate(square_tube_assembly.fastenerLocations(cap_screw)):
self.assertTupleAlmostEquals(loc.toTuple()[0], fastener_positions[i], 7)
self.assertTrue(str(fastener_positions[i]) in str(loc))


if __name__ == "__main__":
Expand Down

0 comments on commit f6be795

Please sign in to comment.