Updated all cqparts libraries and catalogs.

This commit is contained in:
Jeremy Mack Wright 2018-07-09 10:47:58 -04:00
parent 4fc32ec77b
commit 3b9ea9d1cb
79 changed files with 3639 additions and 2153 deletions

1
ThirdParty/cqparts/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

131
ThirdParty/cqparts/README.rst vendored Normal file
View File

@ -0,0 +1,131 @@
.. image:: https://fragmuffin.github.io/cqparts/media/logo/dark.svg
:align: center
=====================
What is `cqparts`?
=====================
``cqparts`` is CAD for Python programmers, short for "``cadquery`` parts".
Using ``cqparts`` you can wrap geometry made with ``cadquery`` to build complex
and deeply parametric models.
Full documentation at: https://fragmuffin.github.io/cqparts
Installing
------------------
Pre-requisites
^^^^^^^^^^^^^^^^^^
You'll need to fulfill the requirements of ``cadquery``, the simplest way to do
that is to install ``cadquery`` first by following the instructions here:
http://dcowden.github.io/cadquery/installation.html
PyPI
^^^^^^^^^
Once ``cadquery`` is installed, install ``cqparts`` with::
pip install cqparts
``cqparts_*`` Content Libraries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also install content libraries with a similar ``pip install`` command.
List available libraries with::
pip search cqparts-
For example, to install the ``cqparts_bearings`` content library, run::
pip install cqparts-bearings
_Note_: ``pip`` packages use ``-`` to separate words, but when importing them the
standard ``_`` is used.
Example Usage
-------------------
Here is just one of the simplest examples to give you an idea of what this
library does.
More detailed examples found in
`the official documentation for cqparts <https://fragmuffin.github.io/cqparts/doc>`_.
Wrapping a Cube
^^^^^^^^^^^^^^^^^^
.. image:: https://fragmuffin.github.io/cqparts/media/img/unit-cube.png
A simple cube defined with ``cadquery`` alone::
# create unit cube solid
import cadquery
size = 10
cube = cadquery.Workplane('XY').box(size, size, size)
# display cube (optional)
from Helpers import show
show(cube)
Wrapping this in a ``cqparts.Part`` object can be done like this::
# create unit cube as cqparts.Part
import cadquery
import cqparts
from cqparts.params import PositiveFloat
class MyCube(cqparts.Part):
size = PositiveFloat(1, doc="cube size")
def make(self):
return cadquery.Workplane('XY').box(self.size, self.size, self.size)
# create cube instance
cube = MyCube(size=10)
# display cube (optional)
from cqparts.display import display
display(cube)
You can see that under the bonnet (in the ``make`` function) the geometry is
created with ``cadquery``, but the resulting ``MyCube`` class is instantiated
more intuitively, and more object orientated.
Creating a Hierarchy
^^^^^^^^^^^^^^^^^^^^^^
``cqparts`` can also be used to create a deep hierarchy of *parts* and
*assemblies* to build something deeply complicated and entirely parametric.
A simple example of this is the
`toy car tutorial <https://fragmuffin.github.io/cqparts/doc/tutorials/assembly.html>`_.
.. image:: https://fragmuffin.github.io/cqparts/media/img/toy-car.png
``cqparts`` Capabilities
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The work done in ``cqparts_fasteners`` is a good example of how useful
``cqparts`` wrapping can be; read about the ``Fastener`` class, how it works,
and what can be done with it in the
`cqparts_fasteners docs <https://fragmuffin.github.io/cqparts/doc/cqparts_fasteners/index.html>`_
.. image:: https://fragmuffin.github.io/cqparts/media/img/nut-bolt-fastener.png
Contributing
-----------------
Issues, and Pull Requests are encouraged, and happily received, please read
`CONTRIBUTING.md <https://github.com/fragmuffin/cqparts/blob/master/CONTRIBUTING.md>`_
for guidance on how to contribute.

View File

@ -22,7 +22,7 @@ limitations under the License.
# 1.x - Development Status :: 5 - Production/Stable
# <any above>.y - developments on that version (pre-release)
# <any above>*.dev* - development release (intended purely to test deployment)
__version__ = '0.2.1'
__version__ = '0.2.2.dev1'
__title__ = 'cqparts'
__description__ = 'Hierarchical and deeply parametric models using cadquery'
@ -31,7 +31,7 @@ __url__ = 'https://github.com/fragmuffin/cqparts'
__author__ = 'Peter Boin'
__email__ = 'peter.boin+cqparts@gmail.com'
__license__ = 'GPLv3'
__license__ = 'Apache Public License 2.0'
__keywords__ = ['cadquery', 'cad', '3d', 'modeling']

View File

@ -47,8 +47,23 @@ class STEPPartImporter(Importer):
Abstraction layer to avoid duplicate code for :meth:`_mangled_filename`.
"""
@classmethod
def _mangled_filename(cls, filename):
return re.sub(r'(^\d|[^a-z0-9_\-+])', '_', filename, flags=re.I)
def _mangled_filename(cls, name):
# ignore sub-directories
name = os.path.basename(name)
# encode to ascii (for a clean class name)
name = name.encode('ascii', 'ignore')
if type(name).__name__ == 'bytes': # a python3 thing
name = name.decode() # type: bytes -> str
# if begins with a number, inject a '_' at the beginning
if re.search(r'^\d', name):
name = '_' + name
# replace non alpha-numeric characters with a '_'
name = re.sub(r'[^a-z0-9_]', '_', name, flags=re.I)
return name
@register_importer('step', Part)

37
ThirdParty/cqparts/constraint/README.md vendored Normal file
View File

@ -0,0 +1,37 @@
# Mainstream CAD Mating / Constraints
**Solidworks**
http://help.solidworks.com/2013/english/solidworks/sldworks/HelpViewerDS.aspx?version=2013&prod=solidworks&lang=english&path=sldworks%2fr_Types_of_Mates_SWassy.htm
Types:
* angle, coincident, concentric, distance, lock, parallel, perpendicular, and tangent mates
* limit, linear/linear coupler, path, symmetry, and width
* cam-follower, gear, hinge, rack and pinion, screw, and universal joint
**OnShape**
https://cad.onshape.com/help/Content/mate.htm
Types:
* Fastened
* Revolute
* Slider
* Planar
* Cylindrical
* Pin Slot
* Ball
# Useful links for 3d transforms
## Basics Explanations
* https://www.tutorialspoint.com/computer_graphics/3d_transformation.htm
* http://planning.cs.uiuc.edu/node100.html
## Implementations
* https://www.lfd.uci.edu/~gohlke/code/transformations.py.html
* https://afni.nimh.nih.gov/pub/dist/src/pkundu/meica.libs/nibabel/eulerangles.py

View File

@ -1,21 +1,23 @@
# generate the files and notify the cqparts server
# look at https://github.com/zignig/cqparts-server
# copied and edited from web.py
# Copyright 2018 Peter Boin
# and Simon Kirkby 2018
""" generate the files and notify the cqparts server
look at https://github.com/zignig/cqparts-server
copied and edited from web.py
Copyright 2018 Peter Boin
and Simon Kirkby 2018
"""
import os
import sys
import inspect
import time
import requests
import tempfile
import shutil
import logging
log = logging.getLogger(__name__)
import requests
from .environment import map_environment, DisplayEnvironment
log = logging.getLogger(__name__)
ENVVAR_SERVER = 'CQPARTS_SERVER'
@ -38,7 +40,7 @@ class CQPartsServerDisplayEnv(DisplayEnvironment):
os.mkdir(dir_path)
return dir_path
def display_callback(self, component):
def display_callback(self, component, **kwargs):
"""
:param component: the component to render
:type component: :class:`Component <cqparts.Component>`
@ -47,6 +49,9 @@ class CQPartsServerDisplayEnv(DisplayEnvironment):
if ENVVAR_SERVER not in os.environ:
raise KeyError("environment variable '%s' not set" % ENVVAR_SERVER)
# get the server from the environment
server_url = os.environ[ENVVAR_SERVER]
# Verify Parameter(s)
# check that it is a component
from .. import Component
@ -55,23 +60,55 @@ class CQPartsServerDisplayEnv(DisplayEnvironment):
Component, type(component)
))
# check that the server is running
try:
requests.get(server_url + '/status')
#TODO inspect response for actual status and do stuff
except requests.exceptions.ConnectionError:
print('cqpart-server unavailable')
return
# get the name of the object
cp_name = type(component).__name__
# create temporary folder
temp_dir = self._mkdir(tempfile.gettempdir(), 'cqpss')
temp_dir = self._mkdir(tempfile.gettempdir(), 'cqpss', cp_name)
temp_dir = tempfile.mkdtemp()
base_dir = self._mkdir(temp_dir, cp_name)
# export the files to the name folder
exporter = component.exporter('gltf')
exporter(
filename=os.path.join(temp_dir, 'out.gltf'),
embed=False,
)
try:
# export the files to the name folder
start_time = time.time()
exporter = component.exporter('gltf')
exporter(
filename=os.path.join(base_dir, 'out.gltf'),
embed=False,
)
finish_time = time.time()
duration = finish_time - start_time
# get the server from the environment
server_url = os.environ[ENVVAR_SERVER]
# create the list of files to upload
file_list = os.listdir(base_dir)
file_load_list = []
for i in file_list:
# path of file to upload
file_name = os.path.join(base_dir, i)
# short reference to file
file_ref = os.path.join(cp_name, i)
# make dict for file upload
file_load_list.append(
('objs', (file_ref, open(file_name, 'rb')))
)
# notify the cq parts server
resp = requests.post(server_url + '/notify', data={
'name': cp_name,
})
# upload the files as multipart upload
requests.post(server_url + '/upload', files=file_load_list)
# notify the cq parts server
# TODO more data in post, bounding box , other data
requests.post(server_url + '/notify', data={
'duration': duration,
'name': cp_name,
})
finally:
# finally check that it's sane and delete
if os.path.isdir(temp_dir):
shutil.rmtree(temp_dir)

View File

@ -102,7 +102,7 @@ class DisplayEnvironment(object):
def display(self, *args, **kwargs):
return self.display_callback(*args, **kwargs)
def display_callback(self, *args, **kwargs):
def display_callback(self, component, **kwargs):
"""
Display given component in this environment.

View File

@ -2,12 +2,14 @@
<html>
<head>
<title>CQParts display-o-tron</title>
<link rel="stylesheet" href="/static/css/viewer.css">
<script src="/static/js/three.min.js"></script>
<script src="/static/js/OrbitControls.js"></script>
<script src="/static/js/GLTFLoader.js"></script>
</head>
<body>
<script src="/static/js/app.js"></script>
<body style="margin:0; overflow:hidden;">
<button id="autorotate" onclick="ActivateAutorotate()">AutoRotate</button>
<script src="/static/js/cqpartsViewer.js"></script>
<script>
camera.position.set({{camera_pos}});
controls.target.set({{camera_target}});

View File

@ -0,0 +1,7 @@
/* CSS for the viewer */
#autorotate {
position: absolute;
left: 5px;
};

View File

@ -1,51 +0,0 @@
// three js local files
//
var container, controls , obj;
var camera, scene, renderer, light;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight,0.0001);
camera.position.set(0.5,0.5,0.5);
controls = new THREE.OrbitControls( camera );
controls.autoRotate = true;
controls.autoRotateSpeed = 2;
controls.target.set(0,0,0);
controls.update();
scene = new THREE.Scene({antialias: true});
scene.background = new THREE.Color(255,255,255)
// light 1
light = new THREE.HemisphereLight( 0xbbbbff, 0x444422 );
light.position.set( 0, 20, 0 );
scene.add( light );
// light 2
light2 = new THREE.PointLight(0xf0f0f0,2,100);
light2.position.set( 50,50,50);
scene.add( light2 );
// model
var loader = new THREE.GLTFLoader();
loader.load( 'model/out.gltf', function ( gltf ) {
scene.add( gltf.scene );
obj = gltf.scene;
} );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.gammaOutput = true;
container.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}

View File

@ -0,0 +1,141 @@
// A viewer for cqparts
//
var container, controls , obj;
var camera, scene, renderer, light;
var meshlist = [];
var raycaster ;
var mouse = new THREE.Vector2(), INTERSECTED ;
init();
animate();
load('./model/out.gltf');
function clear(){
scene.remove(obj);
}
function load(name){
var loader = new THREE.GLTFLoader();
loader.load( name, function ( gltf ) {
scene.add( gltf.scene );
obj = gltf.scene;
//grab all the meshes for selection
meshlist = [];
gltf.scene.traverse( function ( child ) {
if ( child.isMesh ) {
meshlist.push(child);
}
} );
} );
}
function init() {
container = document.createElement('div');
document.body.appendChild(container)
camera = new THREE.PerspectiveCamera( 30, window.innerWidth/ window.innerHeight, 0.001, 1000);
camera.position.set( 0.2, 0.2, 0.2);
controls = new THREE.OrbitControls(camera);
//controls.autoRotate = true;
controls.autoRotateSpeed = 2;
controls.target.set(0,0,0);
controls.update();
scene = new THREE.Scene({antialias: true});
scene.background = new THREE.Color(255,255,255)
// light 1
light = new THREE.HemisphereLight( 0xbbbbff, 0x444422 );
light.position.set( 0, 20, 0 );
scene.add( light );
// light 2
light2 = new THREE.PointLight(0xf0f0f0,2,100);
light2.position.set( 50,50,50);
scene.add( light2 );
// light 3
light3 = new THREE.PointLight(0xf0f0f0,2,100);
light3.position.set( 0,0,-50);
scene.add( light2) ;
// renderer
renderer = new THREE.WebGLRenderer( {antialias: true, preserveDrawingBuffer: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.gammaOutput = true;
// raycaster
raycaster = new THREE.Raycaster();
// add to doc and bind events
window.addEventListener( 'resize', onWindowResize, false );
window.addEventListener('mousemove',onDocumentMouseMove,false);
window.addEventListener('mousedown',onDocumentClick,false);
window.addEventListener('keydown',onKey,false);
// Base grid helps us orient ourselves
var baseGrid = new THREE.GridHelper(1, 10);
//baseGrid.geometry.rotateX( Math.PI / 2 );
scene.add(baseGrid);
container.appendChild(renderer.domElement);
}
function ActivateAutorotate(){
but = document.getElementById("autorotate");
if (controls.autoRotate == true){
controls.autoRotate = false;
} else {
controls.autoRotate = true;
}
}
function onKey( event ) {
if (event.code == "KeyA"){
//console.log(event);
for ( var i in meshlist){
meshlist[i].visible = true;
}
};
//console.log(event);
if (event.code == "Space"){
if ( INTERSECTED ) {
INTERSECTED.visible = false;
};
};
}
function onDocumentClick( event ) {
if ( INTERSECTED ) {
//console.log(INTERSECTED);
//INTERSECTED.visible = false;
};
}
function onDocumentMouseMove( event ) {
event.preventDefault();
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / (rect.right - rect.left)) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / (rect.bottom - rect.top)) * 2 + 1;
}
function onWindowResize() {
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth,window.innerHeight);
}
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
function render(){
raycaster.setFromCamera(mouse,camera);
var intersects = raycaster.intersectObjects(meshlist);
if (intersects.length > 0){
if ( INTERSECTED != intersects[0].object){
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHEX);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0x551111);
}
} else {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}
renderer.render( scene, camera );
}

View File

@ -84,7 +84,7 @@ class WebDisplayEnv(DisplayEnvironment):
an errorcode 404 (file not found), because the http service has stopped.
"""
def display_callback(self, component, port=9041, autorotate=False):
def display_callback(self, component, **kwargs):
"""
:param component: the component to render
:type component: :class:`Component <cqparts.Component>`
@ -103,6 +103,10 @@ class WebDisplayEnv(DisplayEnvironment):
Component, type(component)
))
# Parameter defaults
port = kwargs.get('port', 9041)
autorotate = kwargs.get('autorotate', False)
# Create temporary file to host files
temp_dir = tempfile.mkdtemp()
host_dir = os.path.join(temp_dir, 'html')

6
ThirdParty/cqparts/requirements.txt vendored Normal file
View File

@ -0,0 +1,6 @@
cadquery
six
numpy
Jinja2
tinydb
requests

View File

@ -1,40 +0,0 @@
import os
def get_env_name():
"""
Get the name of the currently running environment.
:return: environment name
:rtype: :class:`str`
=========== =============
Name Environment
=========== =============
``freecad`` freecad module
``cmdline`` directly in a python interpreter
=========== =============
These environments are intended to switch a script's behaviour from
displaying models, or exporting them to file.
For example::
from cqparts.utils.env import get_env_name
from cqparts.display import display
my_model = SomeAssembly(foo=10, bar=-3)
if get_env_name() == 'cmdline':
with open('model.gltf', 'w') as fh:
fh.write(my_model.get_export_gltf())
else:
display(my_model)
"""
# FIXME: this is not a good test method, but it will do for now.
if 'MYSCRIPT_DIR' in os.environ:
return 'freecad'
return 'cmdline'
env_name = get_env_name() # it's unlikely to change

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

1
ThirdParty/cqparts_bearings/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

58
ThirdParty/cqparts_bearings/README.rst vendored Normal file
View File

@ -0,0 +1,58 @@
=========================================
`cqparts` Content Library : Bearings
=========================================
Components
-------------------------
* Ball Bearings
* Tapered Roller Bearings
Examples
-------------------------
`BallBearing`
^^^^^^^^^^^^^^^^^^^^^^^
Create a ball bearing with::
from cqparts_bearings.ball import BallBearing
bearing = BallBearing(
# outer edges
inner_diam=8,
outer_diam=20,
width=5,
# internal rolling elements
ball_count=6,
ball_diam=4,
)
# display [optional]
from cqparts.display import display
display(bearing)
# export model to file, various formats available [optional]
bearing.exporter('gltf')('bearing.gltf')
.. image:: https://fragmuffin.github.io/cqparts/media/img/bearings/ball-example.png
All `BallBearing` parameters are documented
`here <https://fragmuffin.github.io/cqparts/doc/api/cqparts_bearings.html#cqparts_bearings.ball.BallBearing>`_.
The bearing is generated in the following hierarchy:
::
>>> print(bearing.tree_str())
<BallBearing: angle=0.0, ball_count=6, ball_diam=4.0, ball_min_gap=0.4, inner_diam=8.0, inner_width=2.0, outer_diam=20.0, outer_width=2.0, rolling_radius=7.0, tolerance=0.001, width=5.0>
├○ inner_ring
├○ outer_ring
└─ rolling_elements
├○ ball_000
├○ ball_001
├○ ball_002
├○ ball_003
├○ ball_004
└○ ball_005

View File

@ -1,3 +1,19 @@
"""
Copyright 2018 Peter Boin
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.
"""
# =========================== Package Information ===========================
# Version Planning:
# 0.1.x - Development Status :: 2 - Pre-Alpha
@ -15,7 +31,7 @@ __url__ = 'https://github.com/fragmuffin/cqparts'
__author__ = 'Peter Boin'
__email__ = 'peter.boin+cqparts@gmail.com'
__license__ = 'GPLv3'
__license__ = 'Apache Public License 2.0'
__keywords__ = ['cadquery', 'cad', '3d', 'modeling', 'bearings']

View File

@ -0,0 +1 @@
cqparts

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

1
ThirdParty/cqparts_fasteners/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

180
ThirdParty/cqparts_fasteners/README.rst vendored Normal file
View File

@ -0,0 +1,180 @@
=========================================
`cqparts` Content Library : Fasteners
=========================================
Components
-------------------------
**Base Components**
Few of these are useful on their own, they're used to build more complex parts.
Heads
^^^^^^^^^^^^^^^^^
* Counter-sunk varieties
* Cylindrical varieties
* Externally Driven (eg: hex head)
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/heads-assorted.png
Drive Types
^^^^^^^^^^^^^^^^^
* Cruciform varieties (eg: phillips, frearson)
* Hex (aka: alan) varieties
* Square varieties (eg: single, double, triple square)
* Slotted
* Tamper Resistant varieties
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/drives-assorted.png
Threads
^^^^^^^^^^^^^^^^^^
Standard threads included:
* ISO68 (standard for metric bolts)
* Triangular (eg: for woodscrews)
* Ball Screw
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/threads-assorted.png
Any custom thread can be built by creating a *profile* as a ``Wire`` from within
an object inheriting from the base ``Thread`` class.
(`read more here <https://fragmuffin.github.io/cqparts/doc/api/cqparts_fasteners.solidtypes.threads.html?highlight=build_profile#cqparts_fasteners.solidtypes.threads.base.Thread>`_)
.. note::
Threads currently bugged by `issue #1 <https://github.com/fragmuffin/cqparts/issues/1>`_.
Threads are currently simplified to a cylinder until this is fixed.
Warning when using for 3d printing, threads will not form correctly until the
bug is fixed... please ``+1`` the issue if you'd like to use properly formed
threads.
Male Fastener Components
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Bolts
* Screws
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/male-assorted.png
Female Fastener Components
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Nuts
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/female-assorted.png
Utilities
-------------------------
The ``Fasteners`` utility assembly can be used to automatically apply fasteners
to arbitrary materials.
For example, with the following 2 detatched blocks:
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/fastener-detatched.png
A ``Fastener`` can be applied to these two blocks to hold them together in a
variety of ways, with varied parameters, such as these 2 exmples:
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/fastener-assorted.png
More detailed examples of customizing a ``Fastener`` are
`documented here <https://fragmuffin.github.io/cqparts/doc/cqparts_fasteners/>`_.
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/fastener-custom-assorted.png
Catalogue(s)
-------------------------
**BoltDepot**
`boltdepot.com <https://www.boltdepot.com/>`_ has an exceptional website for
details of their products, enough to build 3d models accurate enough for most
applications.
At this time, the catalogue you get with this library contains some of the
products for `boltdepot.com <https://www.boltdepot.com/>`_ in the categories:
* Bolts : ``boltdepot-bolts.json``
* Nuts : ``boltdepot-nuts.json``
* Woodscrews : ``boltdepot-woodscrews.json``
**Other Suppliers**
With increased interest in this library I would like to see this list grow, but
at this time, it's just the catalogues listed above.
Examples
-------------------------
Machine Screw
^^^^^^^^^^^^^^^^^^^^^^^
We can create a fastener with many tuned parameters, for this example we'll create
an M3 machine screw, 4mm long, with a domed cheese head, and a 2mm hex drive::
from cqparts_fasteners.male import MaleFastenerPart
screw = MaleFastenerPart(
head=('cheese', {
'diameter': 4.5,
'height': 1.5,
'domed': True,
'dome_ratio': 0.5,
}),
drive=('hex', {
'depth': 1,
'width': 2,
}),
thread=('iso68', {
'diameter': 3, # M3
}),
length=4,
)
from cqparts.display import display
display(screw)
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/example-screw.png
Catalogue ``Bolt``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With use of a ``JSONCatalogue`` we can search for all fasteners within that
catalogue that suit certain parameters, such as length, diameter, anything used
as a parameter to build the part.
For this example, we'll explicitly define the product's ``id``, guarenteeing
only one result is returned::
import os
from cqparts.catalogue import JSONCatalogue
import cqparts_fasteners
catalogue_filename = os.path.join(
os.path.dirname(cqparts_fasteners.__file__),
'catalogue',
'boltdepot-bolts.json',
)
catalogue = JSONCatalogue(catalogue_filename)
item = catalogue.get_query()
bolt = catalogue.get(item.id == '221')
from cqparts.display import display
display(bolt)
This should generate an accurate model for BoltDepot's
`product #221 <https://www.boltdepot.com/Product-Details.aspx?product=221>`_.
.. image:: https://fragmuffin.github.io/cqparts/media/img/fasteners/example-catalogue.png

View File

@ -1,3 +1,19 @@
"""
Copyright 2018 Peter Boin
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.
"""
# =========================== Package Information ===========================
# Version Planning:
# 0.1.x - Development Status :: 2 - Pre-Alpha
@ -15,7 +31,7 @@ __url__ = 'https://github.com/fragmuffin/cqparts/tree/master/src/cqparts_fastene
__author__ = 'Peter Boin'
__email__ = 'peter.boin+cqparts@gmail.com'
__license__ = 'GPLv3'
__license__ = 'Apache Public License 2.0'
__keywords__ = ['cadquery', 'cad', '3d', 'modeling', 'fasteners']
__package_data__ = ['catalogue/*.json']

View File

@ -0,0 +1,6 @@
# Ignore scrapers' intermediate files
*.csv
*.json
# libreoffice lock file (when opening generated csv files)
.~lock.*

View File

@ -0,0 +1,952 @@
#!/usr/bin/env python
import os
import sys
import inspect
import scrapy
import scrapy.crawler
import scrapy.exporters
import re
import argparse
import logging
import fnmatch
import json
import csv
import itertools
import six
import progressbar
# cqparts
import cqparts
# cqparts_fasteners
import cqparts_fasteners
import cqparts_fasteners.screws
import cqparts_fasteners.bolts
import cqparts_fasteners.nuts
import cqparts_fasteners.solidtypes
from cqparts_fasteners.solidtypes.fastener_heads import find as find_head
from cqparts_fasteners.solidtypes.screw_drives import find as find_drive
from cqparts_fasteners.solidtypes.threads import find as find_thread
# ---------- Constants ----------
STORE_NAME = 'BoltDepot'
STORE_URL = 'https://www.boltdepot.com'
# ---------- Utilities ----------
def split_url(url):
match = re.search(r'^(?P<base>.*)\?(?P<params>.*)$', url, flags=re.I)
return (
match.group('base'),
{k: v for (k, v) in (p.split('=') for p in match.group('params').split('&'))}
)
def join_url(base, params):
return "{base}?{params}".format(
base=base,
params='&'.join('%s=%s' % (k, v) for (k, v) in params.items()),
)
def utf8encoded(d):
return {k.encode('utf-8'): v.encode('utf-8') for (k, v) in d.items()}
def us2mm(value):
if isinstance(value, six.string_types):
# valid string formats include:
# 1-3/4", 1/2", -5/9", 17/64", 1", 0.34", .34", 6ft
match = re.search(
r'''^
(?P<neg>-)?
(?P<whole>(\d+)?(\.\d+)?)??
-?
((?P<numerator>\d+)/(?P<denominator>\d+))?
\s*(?P<unit>("|ft))
$''',
value,
flags=re.MULTILINE | re.VERBOSE,
)
# calculate value (as decimal quantity)
value = float(match.group('whole') or 0) + (
float(match.group('numerator') or 0) / \
float(match.group('denominator') or 1)
)
if match.group('neg'):
value *= -1
if match.group('unit') == 'ft':
value *= 12
else:
# numeric value given:
# assumption: value given in inches
pass
return value * 25.4
def mm2mm(value):
if isinstance(value, six.string_types):
# valid string formats include:
# 1mm, -4mm, -0.3mm, -.3mm, 1m
match = re.search(r'^(?P<value>[\-0-9\.]+)\s*(?P<unit>(mm|m|))$', value.lower())
value = float(match.group('value'))
if match.group('unit') == 'm':
value *= 1000
return float(value)
UNIT_FUNC_MAP = {
'metric': mm2mm,
'us': us2mm,
}
def unit2mm(value, unit):
unit = unit.strip().lower()
if unit in UNIT_FUNC_MAP:
return UNIT_FUNC_MAP[unit](value)
else:
raise ValueError("invalid unit: %r" % unit)
# ---------- Scraper Spiders ----------
class BoltDepotSpider(scrapy.Spider):
prefix = '' # no prefix by default
name = None # no name, should raise exception if not set
FEED_URI = "%(prefix)sscrape-%(name)s.json"
@classmethod
def get_feed_uri(cls):
return cls.FEED_URI % {k: getattr(cls, k) for k in dir(cls)}
@classmethod
def get_data(cls):
if not hasattr(cls, '_data'):
with open(cls.get_feed_uri(), 'r') as fh:
setattr(cls, '_data', json.load(fh))
return cls._data
@classmethod
def get_data_item(cls, key, criteria=lambda i: True, cast=lambda v: v):
# utility function to get data out of a json dict list easily
valid_data = []
for item in cls.get_data():
if criteria(item):
try:
valid_data.append(cast(item[key]))
except AttributeError:
raise ValueError("%r value %r invalid (cannot be cast)" % (key, item[key]))
assert len(valid_data) == 1, "%r" % valid_data
return valid_data[0]
class BoltDepotProductSpider(BoltDepotSpider):
# criteria added to every cqparts.catalogue.JSONCatalogue entry
common_catalogue_criteria = {
'store': STORE_NAME,
'store_url': STORE_URL,
}
def parse(self, response):
# Look for : Product catalogue table
product_catalogue = response.css('table.product-catalog-table')
if product_catalogue:
for catalogue_link in product_catalogue.css('li a'):
next_page_url = catalogue_link.css('::attr("href")').extract_first()
yield response.follow(next_page_url, self.parse)
# Look for : Product list table
product_list = response.css('#product-list-table')
if product_list:
for product in product_list.css('td.cell-prod-no'):
next_page_url = product.css('a::attr("href")').extract_first()
yield response.follow(next_page_url, self.parse_product_detail)
def parse_product_detail(self, response):
heading = response.css('#catalog-header-title h1::text').extract_first()
print("Product: %s" % heading)
sys.stdout.flush()
(url_base, url_params) = split_url(response.url)
# details table
detail_table = response.css('#product-property-list')
details = {}
for row in detail_table.css('tr'):
key = row.css('td.name span::text').extract_first()
value = row.css('td.value span::text').extract_first()
if key and value:
(key, value) = (key.strip('\n\r\t '), value.strip('\n\r\t '))
if key and value:
details[key] = value
product_data = {
'id': url_params['product'],
'name': heading,
'url': response.url,
'details': details,
}
# Image url
image_url = response.css('.catalog-header-product-image::attr("src")').extract_first()
if image_url:
product_data.update({'image_url': image_url})
yield product_data
# --- cqparts catalogue building specific functions
# These functions are kept with the spider as a means to encapsulate
# component-specific logic.
@classmethod
def add_to_catalogue(cls, data, catalogue):
criteria = cls.item_criteria(data)
criteria.update(cls.common_catalogue_criteria)
criteria.update({'scraperclass': cls.__name__})
catalogue.add(
id=data['id'],
obj=cls.build_component(data),
criteria=criteria,
_check_id=False,
)
@classmethod
def item_criteria(cls, data):
return {} # should be overridden
@classmethod
def build_component(cls, data):
from cqparts_misc.basic.primatives import Cube
return Cube() # should be overridden
CATALOGUE_URI = "%(prefix)s%(name)s.json"
CATALOGUE_CLASS = cqparts.catalogue.JSONCatalogue
@classmethod
def get_catalogue_uri(cls):
filename = cls.CATALOGUE_URI % {k: getattr(cls, k) for k in dir(cls)}
return os.path.join('..', filename)
@classmethod
def get_catalogue(cls, **kwargs):
return cls.CATALOGUE_CLASS(cls.get_catalogue_uri(), **kwargs)
@classmethod
def get_item_str(cls, data):
return "[%(id)s] %(name)s" % data
class WoodScrewSpider(BoltDepotProductSpider):
name = 'woodscrews'
start_urls = [
'https://www.boltdepot.com/Wood_screws_Phillips_flat_head.aspx',
'https://www.boltdepot.com/Wood_screws_Slotted_flat_head.aspx',
]
@classmethod
def item_criteria(cls, data):
criteria = {
'name': data['name'],
'url': data['url'],
}
criteria_content = [ # (<key>, <header>), ...
('units', 'Units:'),
('diameter', 'Diameter:'),
('material', 'Material:'),
('plating', 'Plating:'),
]
for (key, header) in criteria_content:
value = data['details'].get(header, None)
if value is not None:
criteria[key] = value.lower()
return criteria
@classmethod
def build_component(cls, data):
details = data['details']
# --- Head
head = None
if details['Head style:'] == 'Flat': # countersunk
head_diam = ( # averaged min/max
unit2mm(details['Head diameter Min:'], details['Units:']) + \
unit2mm(details['Head diameter Max:'], details['Units:'])
) / 2
head = find_head(name='countersunk')(
diameter=head_diam,
bugle=False,
raised=0,
# TODO: details['Head angle:'] usually 82deg
)
else:
raise ValueError("head style %r not supported" % details['Head style:'])
# --- Drive
drive = None
if details['Drive type:'] == 'Phillips':
# FIXME: use actual drive sizes from DataPhillipsDriveSizes to shape
drive = find_drive(name='phillips')(
diameter=head_diam * 0.6
)
elif details['Drive type:'] == 'Slotted':
drive = find_drive(name='slot')(
diameter=head_diam,
)
else:
raise ValueError("drive type %r not supported" % details['Drive type:'])
# --- Thread
# Accuracy is Questionable:
# Exact screw thread specs are very difficult to find, so some
# of this is simply observed from screws I've salvaged / bought.
thread_diam = DataWoodScrewDiam.get_data_item(
'Decimal',
criteria=lambda i: i['Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
thread = find_thread(name='triangular')(
diameter=thread_diam,
pitch=thread_diam * 0.6,
)
# --- Build screw
screw_length = unit2mm(details['Length:'], details['Units:'])
screw = cqparts_fasteners.screws.Screw(
drive=drive,
head=head,
thread=thread,
length=screw_length,
neck_length=screw_length * 0.25,
tip_length=1.5 * thread_diam,
)
return screw
class BoltSpider(BoltDepotProductSpider):
name = 'bolts'
start_urls = [
'https://www.boltdepot.com/Hex_bolts_2.aspx',
'https://www.boltdepot.com/Metric_hex_bolts_2.aspx',
]
@classmethod
def item_criteria(cls, data):
criteria = {
'name': data['name'],
'url': data['url'],
}
criteria_content = [ # (<key>, <header>), ...
('units', 'Units:'),
('diameter', 'Diameter:'),
('material', 'Material:'),
('plating', 'Plating:'),
('finish', 'Finish:'),
('color', 'Color:'),
]
for (key, header) in criteria_content:
value = data['details'].get(header, None)
if value is not None:
criteria[key] = value.lower()
return criteria
@classmethod
def build_component(cls, data):
details = data['details']
# --- Thread
thread = None
thread_diam = unit2mm(details['Diameter:'], details['Units:'])
if details['Units:'].lower() == 'us':
thread_pitch = unit2mm(1, 'us') / int(details['Thread count:'])
elif details['Units:'].lower() == 'metric':
thread_pitch = unit2mm(details['Thread pitch:'], details['Units:'])
# ISO 68 thread: not accurate for imperial bolts, but close enough
# FIXME: imperial threads?
thread = find_thread(name='iso68')(
diameter=thread_diam,
pitch=thread_pitch,
lefthand=details['Thread direction:'] == 'Left hand',
)
# --- Head
head = None
if details['Head style:'].lower() == 'hex': # countersunk
# Width Across Flats
try:
if details['Units:'].lower() == 'us':
across_flats = DataUSBoltHeadSize.get_data_item(
'Hex Bolt - Lag Bolt - Square Bolt',
criteria=lambda i: i['Bolt Diameter'] == details['Diameter:'],
cast=lambda v: DataUSBoltHeadSize.unit_cast(v),
)
elif details['Units:'].lower() == 'metric':
try:
across_flats = DataMetricBoltHeadSize.get_data_item(
'ANSI/ISO',
criteria=lambda i: i['Bolt Diameter (mm)'] == ("%g" % unit2mm(details['Diameter:'], 'metric')),
cast=lambda v: unit2mm(v, 'metric'),
)
except (ValueError, AttributeError):
# assumption: 'ANSI/ISO' field is non-numeirc, use 'DIN' instead
across_flats = DataMetricBoltHeadSize.get_data_item(
'DIN',
criteria=lambda i: i['Bolt Diameter (mm)'] == ("%g" % unit2mm(details['Diameter:'], 'metric')),
cast=lambda v: unit2mm(v, 'metric'),
)
else:
raise ValueError('unsupported units %r' % details['Units:'])
except (AssertionError, AttributeError):
# assumption: table lookup unsuccessful, see if it's explicitly specified
across_flats = unit2mm(details['Width across the flats:'], details['Units:'])
# raises KeyError if 'Width across the flats:' is not specified
# Head Height
head_height = 0.63 * thread_diam # average height of all bolts with a defined head height
head = find_head(name='hex')(
width=across_flats,
height=head_height,
washer=False,
# TODO: details['Head angle:'] usually 82deg
)
else:
raise ValueError("head style %r not supported" % details['Head style:'])
# --- Build bolt
length = unit2mm(details['Length:'], details['Units:'])
# Neck & Thread length
neck_len = None
try:
neck_len = unit2mm(details['Body length Min:'], details['Units:'])
except KeyError:
pass # 'Body length Min:' not specified
thread_len = length
try:
thread_len = unit2mm(details['Thread length Min:'], details['Units:'])
if neck_len is None:
neck_len = length - thread_len
else:
neck_len = (neck_len + (length - thread_len)) / 2
except KeyError:
if neck_len is None:
neck_len = 0
bolt = cqparts_fasteners.bolts.Bolt(
head=head,
thread=thread,
length=length,
neck_length=neck_len,
)
return bolt
class NutSpider(BoltDepotProductSpider):
name = 'nuts'
start_urls = [
'https://www.boltdepot.com/Hex_nuts.aspx',
'https://www.boltdepot.com/Square_nuts.aspx',
'https://www.boltdepot.com/Metric_hex_nuts.aspx',
]
@classmethod
def item_criteria(cls, data):
criteria = {
'name': data['name'],
'url': data['url'],
}
criteria_content = [ # (<key>, <header>), ...
('units', 'Units:'),
('diameter', 'Diameter:'),
('material', 'Material:'),
('plating', 'Plating:'),
('finish', 'Finish:'),
('color', 'Color:'),
]
for (key, header) in criteria_content:
value = data['details'].get(header, None)
if value is not None:
criteria[key] = value.lower()
return criteria
@classmethod
def build_component(cls, data):
details = data['details']
# --- Thread
thread = None
# diameter
try:
thread_diam = unit2mm(details['Diameter:'], details['Units:'])
except AttributeError: # assumption: non-numeric diameter
if details['Units:'] == 'US':
thread_diam = DataUSThreadSize.get_data_item(
'Decimal',
criteria=lambda i: i['Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
else:
raise
# pitch
if details['Units:'].lower() == 'us':
thread_pitch = unit2mm(1, 'us') / int(details['Thread count:'])
elif details['Units:'].lower() == 'metric':
thread_pitch = unit2mm(details['Thread pitch:'], details['Units:'])
# ISO 68 thread: not accurate for imperial bolts, but close enough
# FIXME: imperial threads?
thread = find_thread(name='iso68')(
diameter=thread_diam,
pitch=thread_pitch,
lefthand=details['Thread direction:'] == 'Left hand',
inner=True,
)
# --- build nut
try:
nut_width = unit2mm(details['Width across the flats:'], details['Units:'])
except KeyError: # assumption: 'Width across the flats:' not supplied
if details['Units:'] == 'US':
try:
nut_width = DataUSNutSize.get_data_item(
'Diameter*:Hex Nut',
criteria=lambda i: i['Size:Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
except ValueError:
nut_width = DataUSNutSize.get_data_item(
'Diameter*:Machine Screw Nut', # only use if 'Hex Nut' not avaliable
criteria=lambda i: i['Size:Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
else:
raise
# height
try:
nut_height = unit2mm(details['Height:'], details['Units:'])
except KeyError: # assumption: 'Height:' not specified
if details['Units:'] == 'US':
try:
nut_height = DataUSNutSize.get_data_item(
'Height:Hex Nut',
criteria=lambda i: i['Size:Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
except ValueError:
nut_height = DataUSNutSize.get_data_item(
'Height:Machine Screw Nut', # only use if 'Hex Nut' not avaliable
criteria=lambda i: i['Size:Size'] == details['Diameter:'],
cast=lambda v: unit2mm(v, 'us'),
)
else:
raise
if details['Subcategory:'] == 'Hex nuts':
nut_class = cqparts_fasteners.nuts.HexNut
elif details['Subcategory:'] == 'Square nuts':
nut_class = cqparts_fasteners.nuts.SquareNut
else:
raise ValueError("unsupported nut class %r" % details['Subcategory:'])
nut = nut_class(
thread=thread,
width=nut_width,
height=nut_height,
washer=False,
)
return nut
SPIDERS = [
WoodScrewSpider,
BoltSpider,
NutSpider,
]
SPIDER_MAP = {
cls.name: cls
for cls in SPIDERS
}
class GrowingList(list):
"""
A list that will automatically expand if indexed beyond its limit.
(the list equivalent of collections.defaultdict)
"""
def __init__(self, *args, **kwargs):
self._default_type = kwargs.pop('default_type', lambda: None)
super(GrowingList, self).__init__(*args, **kwargs)
def __getitem__(self, index):
if index >= len(self):
self.extend([self._default_type() for i in range(index + 1 - len(self))])
return super(GrowingList, self).__getitem__(index)
def __setitem__(self, index, value):
if index >= len(self):
self.extend([self._default_type() for i in range(index + 1 - len(self))])
super(GrowingList, self).__setitem__(index, value)
class BoltDepotDataSpider(BoltDepotSpider):
# set to True if the last header row does not uniquely identify each column
merge_headers = False
@staticmethod
def table_data(table):
# Pull data out of a table into a 2d list.
# Merged Cells:
# any merged cells (using rowspan / colspan) will have duplicate
# data over each cell.
# "merging cells does not a database make" said me, just now
def push_data(row, i, val):
# push data into next available slot in the given list
# return the index used (will be >= i)
assert isinstance(row, GrowingList), "%r" % row
assert val is not None
try:
while row[i] is not None:
i += 1
except IndexError:
pass
row[i] = val
return i
data = GrowingList(default_type=GrowingList) # nested growing list
header_count = 0
for (i, row) in enumerate(table.css('tr')):
j = 0
is_header_row = True
for cell in row.css('th, td'):
# cell data
value = ' '.join([v.strip() for v in cell.css('::text').extract()])
if value is None:
value = ''
value = value.rstrip('\r\n\t ')
rowspan = int(cell.css('::attr("rowspan")').extract_first() or 1)
colspan = int(cell.css('::attr("colspan")').extract_first() or 1)
# is header row?
if cell.root.tag != 'th':
is_header_row = False
# populate data (duplicate merged content)
j = push_data(data[i], j, value)
for di in range(rowspan):
for dj in range(colspan):
data[i + di][j + dj] = value
j += 1
if is_header_row:
header_count += 1
return (data, header_count)
def parse(self, response):
table = response.css('table.fastener-info-table')
(data, header_count) = self.table_data(table)
if self.merge_headers:
header = [ # join all headers per column
':'.join(data[i][j] for i in range(header_count))
for j in range(len(data[0]))
]
else:
header = data[header_count - 1] # last header row
for row in data[header_count:]:
row_data = dict(zip(header, row))
if any(v for v in row_data.values()):
# don't yield if there's no data
yield row_data
class DataWoodScrewDiam(BoltDepotDataSpider):
name = 'd-woodscrew-diam'
start_urls = [
'https://www.boltdepot.com/fastener-information/Wood-Screws/Wood-Screw-Diameter.aspx',
]
class DataUSBoltThreadLen(BoltDepotDataSpider):
name = 'd-us-bolt-thread-len'
start_urls = [
'https://www.boltdepot.com/fastener-information/Bolts/US-Thread-Length.aspx',
]
class DataUSThreadPerInch(BoltDepotDataSpider):
name = 'd-us-tpi'
start_urls = [
'https://www.boltdepot.com/fastener-information/Measuring/US-TPI.aspx',
]
class DataUSBoltHeadSize(BoltDepotDataSpider):
name = 'd-us-boltheadsize'
start_urls = [
'https://www.boltdepot.com/fastener-information/Bolts/US-Bolt-Head-Size.aspx',
]
@staticmethod
def unit_cast(value):
# special case for the '7/16" or 3/8"' cell
return unit2mm(re.split('\s*or\s*', value)[-1], 'us') # last value
class DataUSNutSize(BoltDepotDataSpider):
name = 'd-us-nutsize'
start_urls = [
'https://www.boltdepot.com/fastener-information/Nuts-Washers/US-Nut-Dimensions.aspx',
]
merge_headers = True # header 'Hex Nut' is repeated
class DataUSThreadSize(BoltDepotDataSpider):
name = 'd-us-threadsize'
start_urls = [
'https://www.boltdepot.com/fastener-information/Machine-Screws/Machine-Screw-Diameter.aspx',
]
class DataMetricThreadPitch(BoltDepotDataSpider):
name = 'd-met-threadpitch'
start_urls = [
'https://www.boltdepot.com/fastener-information/Measuring/Metric-Thread-Pitch.aspx',
]
class DataMetricBoltHeadSize(BoltDepotDataSpider):
name = 'd-met-boltheadsize'
start_urls = [
'https://www.boltdepot.com/fastener-information/Bolts/Metric-Bolt-Head-Size.aspx',
]
class DataPhillipsDriveSizes(BoltDepotDataSpider):
name = 'd-drivesizes-phillips'
start_urls = [
'https://www.boltdepot.com/fastener-information/Driver-Bits/Phillips-Driver-Sizes.aspx',
]
METRICS_SPIDERS = [
DataWoodScrewDiam,
DataUSBoltThreadLen,
DataUSThreadPerInch,
DataUSBoltHeadSize,
DataUSNutSize,
DataUSThreadSize,
DataMetricThreadPitch,
DataMetricBoltHeadSize,
DataPhillipsDriveSizes,
]
# ---------- Command-line Arguments Parser ----------
DEFAULT_PREFIX = os.path.splitext(os.path.basename(
os.path.abspath(inspect.getfile(inspect.currentframe()))
))[0] + '-'
parser = argparse.ArgumentParser(
description='Build Bolt Depot catalogue by crawling their website',
epilog="""
Actions:
scrape scrape product details from website
csv convert scraped output to csv [optional]
build builds catalogue from scraped data
all (run all above actions)
Note: Actions will always be performed in the order shown above,
even if they're not listed in that order on commandline.
""",
formatter_class=argparse.RawTextHelpFormatter,
)
VALID_ACTIONS = set(['scrape', 'csv', 'build', 'all'])
def action_type(value):
value = value.lower()
if value not in VALID_ACTIONS:
raise argparse.ArgumentError()
return value
parser.add_argument(
'actions', metavar='action', type=action_type, nargs='*',
help='action(s) to perform'
)
# Scraper arguments
parser.add_argument(
'--prefix', '-p', dest='prefix', default=DEFAULT_PREFIX,
help="scraper file prefix (default: '%s')" % DEFAULT_PREFIX,
)
parser.add_argument(
'--onlymetrics', '-om', dest='onlymetrics',
action='store_const', const=True, default=False,
help="if set, when scraping, only metrics data is scraped"
)
# Catalogues
parser.add_argument(
'--list', '-l', dest='list',
default=False, action='store_const', const=True,
help="list catalogues to build",
)
def catalogues_list_type(value):
catalogues_all = set(SPIDER_MAP.keys())
catalogues = set()
for filter_str in value.split(','):
catalogues |= set(fnmatch.filter(catalogues_all, filter_str))
return sorted(catalogues)
parser.add_argument(
'--catalogues', '-c', dest='catalogues',
type=catalogues_list_type, default=catalogues_list_type('*'),
help="csv list of catalogues to act on",
)
parser.add_argument(
'--strict', '-s', dest='strict',
default=False, action='store_const', const=True,
help="if set, exceptions during a build stop progress",
)
args = parser.parse_args()
args.actions = set(args.actions) # convert to set
BoltDepotSpider.prefix = args.prefix
# list catalogues & exit
if args.list:
print("Catalogues:")
for name in args.catalogues:
print(" - %s" % name)
exit(0)
# no actions, print help & exit
if not args.actions:
parser.print_help()
exit(1)
# ----- Start Crawl -----
if {'all', 'scrape'} & args.actions:
print("----- Scrape: %s (+ metrics)" % (', '.join(args.catalogues)))
sys.stdout.flush()
# --- Clear feed files
feed_names = []
if not args.onlymetrics:
feed_names += args.catalogues
feed_names += [cls.name for cls in METRICS_SPIDERS]
for name in feed_names:
feed_filename = BoltDepotSpider.FEED_URI % {
'prefix': args.prefix, 'name': name,
}
if os.path.exists(feed_filename):
os.unlink(feed_filename) # remove feed file to populate from scratch
# --- Create Crawlers
process = scrapy.crawler.CrawlerProcess(
settings={
'LOG_LEVEL': logging.INFO,
'FEED_FORMAT': "json",
'FEED_URI': BoltDepotSpider.FEED_URI,
},
)
# product crawlers
if not args.onlymetrics:
for name in args.catalogues:
process.crawl(SPIDER_MAP[name])
# metrics crawlers
for metrics_spider in METRICS_SPIDERS:
process.crawl(metrics_spider)
# --- Start Scraping
process.start()
# ----- Convert to CSV -----
# Conversion of json files to csv is optional, csv's are easy to open
# in a 3rd party application to visualise the data that was scraped.
if {'all', 'csv'} & args.actions:
def flatten_dict(dict_in):
# flatten nested dicts using '.' separated keys
def inner(d, key_prefix=''):
for (k, v) in d.items():
if isinstance(v, dict):
for (k1, v1) in inner(v, k + '.'):
yield (k1, v1)
else:
yield (key_prefix + k, v)
return dict(inner(dict_in))
for cls in itertools.chain([SPIDER_MAP[n] for n in args.catalogues], METRICS_SPIDERS):
print("----- CSV: %s" % cls.name)
feed_json = cls.get_feed_uri()
feed_csv = "%s.csv" % os.path.splitext(feed_json)[0]
print(" %s --> %s" % (feed_json, feed_csv))
data = cls.get_data()
# pull out all possible keys to build header row
headers = set(itertools.chain(*[
tuple(str(k) for (k, v) in flatten_dict(row).items())
for row in data
]))
# Write row by row
with open(feed_csv, 'w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=headers)
writer.writeheader()
for rowdata in data:
writer.writerow(utf8encoded(flatten_dict(rowdata)))
# ----- Build Catalogues -----
if {'all', 'build'} & args.actions:
for name in args.catalogues:
cls = SPIDER_MAP[name]
print("----- Build: %s" % name)
catalogue_file = os.path.join(
'..', "%s.json" % os.path.splitext(cls.get_feed_uri())[0]
)
catalogue = cls.get_catalogue(clean=True)
data = cls.get_data()
sys.stdout.flush() # make sure prints come through before bar renders
bar = progressbar.ProgressBar()
for item_data in bar(data):
try:
cls.add_to_catalogue(item_data, catalogue)
except Exception as e:
print("couldn't add: %s" % cls.get_item_str(item_data))
print("%s: %s" % (type(e).__name__, e))
sys.stdout.flush()
if args.strict:
raise

View File

@ -0,0 +1,313 @@
#!/usr/bin/env python
import os
import inspect
import scrapy
import scrapy.crawler
import scrapy.exporters
import re
import argparse
import logging
import fnmatch
import json
import csv
# ---------- Utilities ----------
def split_url(url):
match = re.search(r'^(?P<base>.*)\?(?P<params>.*)$', url, flags=re.I)
return (
match.group('base'),
{k: v for (k, v) in (p.split('=') for p in match.group('params').split('&'))}
)
def join_url(base, params):
return "{base}?{params}".format(
base=base,
params='&'.join('%s=%s' % (k, v) for (k, v) in params.items()),
)
# ---------- Scraper Spiders ----------
class BunningsProductSpider(scrapy.Spider):
def parse(self, response):
"""Parse pagenated list of products"""
# Check if page is out of range
no_more_products = re.search(
r'No matching products were found',
response.css('div.paged-results').extract_first(),
flags=re.I
)
if no_more_products:
pass # no more pages to populate, stop scraping
else:
# Scrape products list
for product in response.css('article.product-list__item'):
product_url = product.css('a::attr("href")').extract_first()
yield response.follow(product_url, self.parse_detail)
(base, params) = split_url(response.url)
params.update({'page': int(params.get('page', '1')) + 1})
next_page_url = join_url(base, params)
self.logger.info(next_page_url)
yield response.follow(next_page_url, self.parse)
def parse_detail(self, response):
"""Parse individual product's detail"""
# Product Information (a start)
product_data = {
'url': response.url,
'name': response.css('div.page-title h1::text').extract_first(),
}
# Inventory Number
inventory_number = re.search(
r'(?P<inv_num>\d+)$',
response.css('span.product-in::text').extract_first(),
).group('inv_num')
product_data.update({'in': inventory_number})
# Specifications (arbitrary key:value pairs)
specs_table = response.css('#tab-specs dl')
for row in specs_table.css('div.spec-row'):
keys = row.css('dt::text').extract()
values = row.css('dd::text').extract()
product_data.update({
key: value
for (key, value) in zip(keys, values)
})
self.logger.info(product_data['name'])
yield product_data
class ScrewSpider(BunningsProductSpider):
name = 'screws'
start_urls = [
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/decking?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/batten?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/wood?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/metal-fix?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/chipboard?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/treated-pine?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/plasterboard?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/roofing?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/screws/general-purpose?page=1',
]
class BoltSpider(BunningsProductSpider):
name = 'bolts'
start_urls = [
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/bolts/cup-head-bolts?page=1',
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/bolts/hex-head-bolts?page=1',
]
class NutSpider(BunningsProductSpider):
name = 'nuts'
start_urls = [
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/bolts/nuts?page=1',
]
class ThreadedRodSpider(BunningsProductSpider):
name = 'threaded-rods'
start_urls = [
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/bolts/threaded-rod?page=1',
]
class WasherSpider(BunningsProductSpider):
name = 'washers'
start_urls = [
'https://www.bunnings.com.au/our-range/building-hardware/fixings-fasteners/bolts/washers?page=1',
]
SPIDERS = [
ScrewSpider,
BoltSpider,
NutSpider,
ThreadedRodSpider,
WasherSpider,
]
SPIDER_MAP = {
cls.name: cls
for cls in SPIDERS
}
# ---------- Command-line Arguments Parser ----------
DEFAULT_PREFIX = os.path.splitext(os.path.basename(
os.path.abspath(inspect.getfile(inspect.currentframe()))
))[0] + '-'
parser = argparse.ArgumentParser(
description='Build Bunnings catalogue by crawling their website',
epilog="""
WORK IN PROGRESS
At this time, this script will scrape the Bunnings website for fasteners,
however it will not create a catalogue.
This is because there is not enough information on the website to accurately
determine fastener geometry.
Actions:
scrape scrape product details from website
csv convert scraped output to csv [optional]
build builds catalogue from scraped data
Note: Actions will always be performed in the order shown above,
even if they're not listed in that order on commandline.
""",
formatter_class=argparse.RawTextHelpFormatter,
)
VALID_ACTIONS = set(['scrape', 'csv', 'build'])
def action_type(value):
value = value.lower()
if value not in VALID_ACTIONS:
raise argparse.ArgumentError()
return value
parser.add_argument(
'actions', metavar='action', type=action_type, nargs='*',
help='action(s) to perform'
)
# Scraper arguments
parser.add_argument(
'--prefix', '-p', dest='prefix', default=DEFAULT_PREFIX,
help="scraper file prefix (default: '%s')" % DEFAULT_PREFIX,
)
# Catalogues
parser.add_argument(
'--list', '-l', dest='list',
default=False, action='store_const', const=True,
help="list catalogues to build",
)
def catalogues_list_type(value):
catalogues_all = set(SPIDER_MAP.keys())
catalogues = set()
for filter_str in value.split(','):
catalogues |= set(fnmatch.filter(catalogues_all, filter_str))
return sorted(catalogues)
parser.add_argument(
'--catalogues', '-c', dest='catalogues',
type=catalogues_list_type, default=catalogues_list_type('*'),
help="csv list of catalogues to act on",
)
args = parser.parse_args()
BunningsProductSpider.prefix = args.prefix
# list catalogues & exit
if args.list:
for name in args.catalogues:
print(name)
exit(0)
# no actions, print help & exit
if not args.actions:
parser.print_help()
exit(1)
FEED_URI = "%(prefix)sscrape-%(name)s.json"
# ----- Start Crawl -----
if 'scrape' in args.actions:
print("----- Scrape: %s" % (', '.join(args.catalogues)))
# Clear feed files
for name in args.catalogues:
feed_filename = FEED_URI % {
'prefix': args.prefix, 'name': name,
}
if os.path.exists(feed_filename):
os.unlink(feed_filename) # remove feed file to populate from scratch
# Create Crawlers
process = scrapy.crawler.CrawlerProcess(
settings={
'LOG_LEVEL': logging.INFO,
'FEED_FORMAT': "json",
'FEED_URI': FEED_URI,
},
)
for name in args.catalogues:
process.crawl(SPIDER_MAP[name])
# Start Scraping
process.start()
# ----- Convert to CSV -----
if 'csv' in args.actions:
for name in args.catalogues:
print("----- CSV: %s" % name)
feed_json = FEED_URI % {
'prefix': args.prefix, 'name': name,
}
with open(feed_json, 'r') as json_file:
data = json.load(json_file)
# Pull out headers
headers = set()
for item in data:
headers |= set(item.keys())
# Write Output
def utf8encoded(d):
return {k.encode('utf-8'): v.encode('utf-8') for (k, v) in d.items()}
feed_csv = "%s.csv" % os.path.splitext(feed_json)[0]
with open(feed_csv, 'w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=headers)
writer.writeheader()
for item in data:
writer.writerow(utf8encoded(item))
# ----- Build Catalogues -----
def build_screw(row):
# Required Parameters:
# - drive
# -
# - head
# - <countersunk>
# - <>
# - thread <triangular>
# - diameter
# - diameter_core (defaults to 2/3 diameter)
# - pitch
# - angle (defaults to 30deg)
# - length
# - neck_diam
# - neck_length
# - neck_taper
# - tip_diameter
# - tip_length
pass
if 'build' in args.actions:
print("BUILD ACTION NOT IMPLEMENTED")
print('\n'.join([
"The information on conventional commercial web-pages is too iratic and",
"innacurate to formulate a quality catalogue.",
"At the time of writing this, I've abandoned this idea, (at least for the",
"bunnings.com.au website anyway)"
]) + '\n')
raise NotImplementedError("'build' action")
#for name in args.catalogues:
# print("----- Build: %s" % name)

View File

@ -0,0 +1,2 @@
scrapy
progressbar2

View File

@ -0,0 +1,5 @@
__all__ = [
'Fastener',
]
from .base import Fastener

View File

@ -0,0 +1 @@
cqparts

View File

@ -0,0 +1,19 @@
# Threads
Threads are built from a manually build profile wire, which is converted into
a closed cross-sectional face, which is swept through a helix to create a solid.
If you're interested in the development process, read:
https://forum.freecadweb.org/viewtopic.php?f=22&t=24628
# Making a Custom Thread Type
It's recommended you adapt something simple, like `TriangularThread`.
TODO: make guide on how to customize a thread
# Research
* https://www.freecadweb.org/wiki/index.php?title=Macro_screw_maker1_2

View File

@ -52,11 +52,13 @@ def profile_to_cross_section(profile, lefthand=False, start_count=1, min_vertice
vertices to set for each wire.
where: len(min_vertices) == number of edges in profile
**Example**::
**Example**
.. doctest::
import cadquery
from cqparts.solidtypes.threads.base import profile_to_cross_section
from Helpers import show
from cqparts_fasteners.solidtypes.threads.base import profile_to_cross_section
from Helpers import show # doctest: +SKIP
profile = cadquery.Workplane("XZ") \
.moveTo(1, 0) \
@ -64,8 +66,8 @@ def profile_to_cross_section(profile, lefthand=False, start_count=1, min_vertice
.wire()
cross_section = profile_to_cross_section(profile)
show(profile)
show(cross_section)
show(profile) # doctest: +SKIP
show(cross_section) # doctest: +SKIP
Will result in:

View File

@ -0,0 +1,60 @@
# Applying a Fastener
This `fasteners` collection has some utilities to automatically apply and select
an apropriate fastener. What they do, and how to use them is documented below.
Using these tools is **not mandatory**; you'll always have the option to
explicitly select your fastener with the placement and orientation of your
choosing.
## Evaluation
An evaluation is an assessment of the workpieces intended to be fastened along
a given vector.
An evaluation is required for a fastener selector, and an applicator.
If a fastener selector is used to fine an appropriate fastener, that same evaluation
can be passed to the applicator.
Evaluations are given, the _what_, and the _where_:
- _what_: list of parts (those that may be effected, should be as short as possible)
- _where_: linear edge, a vector along which the fastener is intended to be applied
A successful evaluation will yield:
- list of parts effected (in order)
- start & finish points on each effected part
(the start being closer to the head)
There are 2 methods of evaluation
- vector : faster, but may miss something
- cylindrical : slower, but will find all interfacing solids
```python
# TODO: sample eval code
```
## Fastener Selector
An evaluation can be used to determine which fastener you need.
Or simply, how long the thread on your chosen fastener needs to be.
```python
# TODO: sample selector code
```
## Fastener Application
So you have the fastener to apply, and the workpieces to attach, the workpieces
just need holes.
```python
from cqparts.search import find
screw = find('10g_30mm_CS_PHIL')()
# TODO:
box = Box() # FIXME
plate = Plate() # FIXME
screw.apply()
```

1
ThirdParty/cqparts_gearboxes/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

View File

@ -0,0 +1,17 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build

View File

@ -0,0 +1 @@
cqparts

1
ThirdParty/cqparts_gears/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

26
ThirdParty/cqparts_gears/README.md vendored Normal file
View File

@ -0,0 +1,26 @@
# Involute Gear Profile
this may be a bit tricky
## Exiting Scripts
Existing scripts:
* [DXF generator for involute gears](https://www.thingiverse.com/thing:829)
* [Cnc25D Gear Profile Function](http://pythonhosted.org/Cnc25D/gear_profile_function.html) ([source](https://github.com/charlyoleg/Cnc25D/blob/master/cnc25d/gear_profile_outline.py))
I can't use them directly, but I can learn from them.
## More Reading
Some helpful descriptions for generating a gear profile:
* http://khkgears.net/gear-knowledge/gear-technical-reference/involute-gear-profile/
* http://lcamtuf.coredump.cx/gcnc/ch6/
* http://khkgears.net/gear-knowledge/gear-technical-reference/calculation-gear-dimensions/
## Undercutting
> Question:
> What's the formula for the undercut region
yeah, I've forgotten all this stuff from uni... at least,
I assume I learnt it at uni... who knows!?

21
ThirdParty/cqparts_gears/__init__.py vendored Normal file
View File

@ -0,0 +1,21 @@
"""
Copyright 2018 Peter Boin
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.
"""
__all__ = [
'trapezoidal',
]
from . import trapezoidal

13
ThirdParty/cqparts_gears/base.py vendored Normal file
View File

@ -0,0 +1,13 @@
import cadquery
import cqparts
from cqparts.params import *
class Gear(cqparts.Part):
effective_radius = PositiveFloat(25, doc="designed equivalent wheel radius")
tooth_count = PositiveInt(12, doc="number of teeth")
width = PositiveFloat(10, doc="gear thickness")
def make_simple(self):
return cadquery.Workplane('XY', origin=(0, 0, -self.width)) \
.circle(self.effective_radius).extrude(self.width)

View File

@ -0,0 +1 @@
cqparts

101
ThirdParty/cqparts_gears/trapezoidal.py vendored Normal file
View File

@ -0,0 +1,101 @@
from math import pi, radians, sin, cos, acos
import cadquery
from cqparts.params import *
from cqparts.utils import CoordSystem
from cqparts.constraint import Mate
from .base import Gear
class TrapezoidalGear(Gear):
"""
Basic gear with trapezoidal tooth shape.
.. image:: /_static/img/gears/trapezoidal.png
"""
tooth_height = PositiveFloat(None, doc="radial height of teeth")
face_angle = PositiveFloat(30, doc="angle of tooth edge radial edge (unit: degrees)")
spacing_ratio = FloatRange(0, 1, 0.5, doc="tooth thickness as a ratio of the distance between them")
flat_top = Boolean(False, doc="if ``True`` the tops of teeth are flat")
def initialize_parameters(self):
super(TrapezoidalGear, self).initialize_parameters()
if self.tooth_height is None:
self.tooth_height = 0.2 * self.effective_radius # default: 20% effective radius
def _make_tooth_template(self):
"""
Builds a single tooth including the cylinder with tooth faces
tangential to its circumference.
"""
# parameters
period_arc = (2 * pi) / self.tooth_count
tooth_arc = period_arc * self.spacing_ratio # the arc between faces at effective_radius
outer_radius = self.effective_radius + (self.tooth_height / 2)
face_angle_rad = radians(self.face_angle)
# cartesian isosceles trapezoid dimensions
side_angle = face_angle_rad - (tooth_arc / 2)
side_tangent_radius = sin(face_angle_rad) * self.effective_radius
extra_side_angle = side_angle + acos(side_tangent_radius / outer_radius)
tooth = cadquery.Workplane('XY', origin=(0, 0, -self.width / 2)) \
.moveTo(
side_tangent_radius * cos(side_angle),
side_tangent_radius * sin(side_angle)
)
opposite_point = (
-side_tangent_radius * cos(side_angle),
side_tangent_radius * sin(side_angle)
)
if self.face_angle:
tooth = tooth.lineTo(*opposite_point)
#tooth = tooth.threePointArc(
# (0, -side_tangent_radius),
# opposite_point
#)
tooth = tooth.lineTo(
-cos(extra_side_angle) * outer_radius,
sin(extra_side_angle) * outer_radius
)
opposite_point = (
cos(extra_side_angle) * outer_radius,
sin(extra_side_angle) * outer_radius
)
if self.flat_top:
tooth = tooth.lineTo(*opposite_point)
else:
tooth = tooth.threePointArc((0, outer_radius), opposite_point)
tooth = tooth.close().extrude(self.width)
return tooth
def make(self):
# create inside cylinder
inner_radius = self.effective_radius - (self.tooth_height / 2)
gear = cadquery.Workplane('XY', origin=(0, 0, -self.width / 2)) \
.circle(inner_radius).extrude(self.width)
# copy & rotate once per tooth
tooth_template = self._make_tooth_template()
period_arc = 360. / self.tooth_count
for i in range(self.tooth_count):
gear = gear.union(
CoordSystem().rotated((0, 0, i * period_arc)) + tooth_template
)
return gear
@property
def mate_top(self):
return Mate(self, CoordSystem((0, 0, self.width / 2)))
@property
def mate_bottom(self):
return Mate(self, CoordSystem((0, 0, -self.width / 2)))

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

1
ThirdParty/cqparts_misc/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

72
ThirdParty/cqparts_misc/README.rst vendored Normal file
View File

@ -0,0 +1,72 @@
====================================================
`cqparts` Content Library : Miscellaneous
====================================================
Components
-------------------------
Primative Shapes
^^^^^^^^^^^^^^^^^^^^
Primative shapes to build or test ideas quickly
* Cube
* Box
* Sphere
* Cylinder
Indicators
^^^^^^^^^^^^^^^^^^^^
These components can be used in assemblies during development as a means
to debug your part placement, and to demonstrate ``Mate`` coordinate systems.
* Coordinate System Indicator
* Planar Indicator
.. image:: https://fragmuffin.github.io/cqparts/media/img/misc/indicators.png
Examples
-------------------------
Use indicator on a primative
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To illustrate how an inciator can be used to show where a ``Mate`` is on a
``Part``, we'll create a simple ``Assembly``::
import cqparts
from cqparts.constraint import Fixed, Coincident
from cqparts_misc.basic.indicators import CoordSysIndicator
from cqparts_misc.basic.primatives import Box
class MyAsm(cqparts.Assembly):
def make_components(self):
return {
'box': Box(length=30, width=20, height=10),
'indicator': CoordSysIndicator(),
}
def make_constraints(self):
return [
Fixed(self.components['box'].mate_origin), # fix at world origin
Coincident(
self.components['indicator'].mate_origin,
self.components['box'].mate_neg_y,
),
]
from cqparts.display import display
display(MyAsm())
.. image:: https://fragmuffin.github.io/cqparts/media/img/misc/example-coordsys-indicator.png
From this we can see that the ``mate_neg_y`` mate has:
* its Z-axis along the world -Y-axis, and
* its X-axis along the world Z-axis.

View File

@ -1,3 +1,19 @@
"""
Copyright 2018 Peter Boin
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.
"""
# =========================== Package Information ===========================
# Version Planning:
# 0.1.x - Development Status :: 2 - Pre-Alpha
@ -15,7 +31,7 @@ __url__ = 'https://github.com/fragmuffin/cqparts/tree/master/src/cqparts_misc'
__author__ = 'Peter Boin'
__email__ = 'peter.boin+cqparts@gmail.com'
__license__ = 'GPLv3'
__license__ = 'Apache Public License 2.0'
__keywords__ = ['cadquery', 'cad', '3d', 'modeling']

View File

@ -0,0 +1 @@
cqparts

1
ThirdParty/cqparts_motors/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

17
ThirdParty/cqparts_motors/__init__.py vendored Normal file
View File

@ -0,0 +1,17 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build

View File

@ -0,0 +1 @@
{"items": {"1": {"obj": {"params": {"profile": "rect", "diam": 23.0, "shaft_length": 8.0, "cover_height": 0.0, "step_height": 0.0, "thickness": 15.4, "bush_height": 1.6, "shaft_diam": 2.0, "step_diam": 12.0, "height": 17.0, "bush_diam": 4.0}, "class": {"name": "DCMotor", "module": "cqparts_motors.dc"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "slotcar", "criteria": {"diam": 23, "type": "motor", "class": "dc"}}, "2": {"obj": {"params": {"profile": "flat", "diam": 20.4, "shaft_length": 11.55, "cover_height": 0.0, "step_diam": 12.0, "thickness": 15.4, "bush_height": 1.6, "step_height": 0.0, "shaft_diam": 2.0, "bush_diam": 6.15, "height": 25.1}, "class": {"name": "DCMotor", "module": "cqparts_motors.dc"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "130", "criteria": {"diam": 20.4, "type": "motor", "class": "dc"}}, "3": {"obj": {"params": {"profile": "circle", "diam": 20.4, "shaft_length": 6.0, "cover_height": 0.0, "step_height": 0.0, "thickness": 15.4, "height": 10.0, "bush_height": 0.4, "shaft_diam": 2.0, "bush_diam": 6.15, "step_diam": 12.0}, "class": {"name": "DCMotor", "module": "cqparts_motors.dc"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "flat", "criteria": {"diam": 20.4, "type": "motor", "class": "dc"}}, "4": {"obj": {"params": {"profile": "circle", "diam": 20.4, "shaft_length": 11.55, "cover_height": 0.0, "bush_diam": 6.15, "height": 25.1, "bush_height": 1.6, "step_height": 1.0, "shaft_diam": 2.0, "step_diam": 12.0, "thickness": 15.4}, "class": {"name": "DCMotor", "module": "cqparts_motors.dc"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "R140", "criteria": {"diam": 20.4, "type": "motor", "class": "dc"}}}, "_dbinfo": {"1": {"lib": "cqparts", "ver": "0.1", "name": "JSONCatalogue", "module": "cqparts.catalogue.json", "lib_version": "0.2.2.dev1"}}}

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
# Catalogue Usage example:
# >>> from cqparts.catalogue import JSONCatalogue
# >>> import cqparts_motors
# >>> filename = os.path.join(
# ... os.path.dirname(cqparts_motors.__file__),
# ... 'catalogue', 'dcmotor.json',
# ... )
# >>> catalogue = JSONCatalogue(filename)
# >>> item = catalogue.get_query()
# >>> dc = catalogue.get(item.criteria.size == 304)
# >>> from cqparts.display import display
# >>> display(dc)
import os
import cqparts
from cqparts.catalogue import JSONCatalogue
# DC Motor
from cqparts_motors.dc import DCMotor
CATALOGUE_NAME = 'dcmotor.json'
# DC Motor hobby examples
# example only
DC_HOBBY = {
'130':
{
"profile": "flat",
"diam": 20.4,
"shaft_length": 11.55,
"cover_height": 0.0,
"thickness": 15.4,
"bush_height": 1.6,
"shaft_diam": 2.0,
"bush_diam": 6.15,
"height": 25.1
},
'R140':
{
"profile": "circle",
"diam": 20.4,
"shaft_length": 11.55,
"cover_height": 0.0,
"step_diam": 12.0,
"thickness": 15.4,
"bush_height": 1.6,
"step_height": 1,
"shaft_diam": 2.0,
"bush_diam": 6.15,
"height": 25.1
},
'slotcar':
{
"profile": "rect",
"diam": 23,
"shaft_length": 8,
"cover_height": 0.0,
"step_diam": 12.0,
"thickness": 15.4,
"bush_height": 1.6,
"step_height": 0.0,
"shaft_diam": 2.0,
"bush_diam": 4,
"height": 17
},
'flat':
{
"profile": "circle",
"diam": 20.4,
"shaft_length": 6,
"cover_height": 0.0,
"step_diam": 12.0,
"thickness": 15.4,
"bush_height": 0.4,
"step_height": 0.0,
"shaft_diam": 2.0,
"bush_diam": 6.15,
"height": 10
}
}
# Generate Catalogue
catalogue = JSONCatalogue(
filename=os.path.join('..', CATALOGUE_NAME),
clean=True, # starts fresh
)
# Create Motors
for (name, params) in DC_HOBBY.items():
catalogue.add(
id= name,
obj=DCMotor(**params),
criteria={
'type': 'motor',
'class': 'dc',
'diam': params['diam'],
},
)

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
# Catalogue Usage example:
# >>> from cqparts.catalogue import JSONCatalogue
# >>> import cqparts_motors
# >>> filename = os.path.join(
# ... os.path.dirname(cqparts_motors.__file__),
# ... 'catalogue', 'stepper-nema.json',
# ... )
# >>> catalogue = JSONCatalogue(filename)
# >>> item = catalogue.get_query()
# >>> stepper = catalogue.get(item.criteria.size == 17)
# >>> from cqparts.display import display
# >>> display(stepper)
import os
import cqparts
from cqparts.catalogue import JSONCatalogue
# Stepper
from cqparts_motors.stepper import Stepper
CATALOGUE_NAME = 'stepper-nema.json'
# Sized motors , build into collection
# http://www.osmtec.com/stepper_motors.htm is a good reference
# sizes 8, 11, 14, 16, 17, 23, 24, 34, 42
# Stepper.class_params().keys()
NEMA_SIZES = {
8 : {
'shaft_length': 10.0,
'hole_spacing': 15.4,
'hole_size': 2.0,
'length': 28.0,
'width': 20.3,
'boss_size': 16.0,
'shaft_diam': 4.0,
'boss_length': 1.5,
},
11 : {
'shaft_length': 20.0,
'hole_spacing': 23.0,
'hole_size': 2.5,
'length': 31.5,
'width': 28.2,
'boss_size': 22.0,
'shaft_diam': 4.0,
'boss_length': 2.0,
},
14 : {
'shaft_length': 24.0,
'hole_spacing': 26.0,
'hole_size': 3.0,
'length': 28.0,
'width': 35.2,
'boss_size': 22.0,
'shaft_diam': 5.0,
'boss_length': 2.0,
},
17 : {
'shaft_length': 24.0,
'hole_spacing': 31.0,
'hole_size': 3.0,
'length': 50.0,
'width': 42.0,
'boss_size': 22.0,
'shaft_diam': 5.0,
'boss_length': 2.0,
},
23 : {
'shaft_length': 21.0,
'hole_spacing': 47.0,
'hole_size': 5.0,
'length': 56.0,
'width': 57.0,
'boss_size': 38.0,
'shaft_diam': 6.35,
'boss_length': 1.6,
},
}
# Generate Catalogue
catalogue = JSONCatalogue(
filename=os.path.join('..', CATALOGUE_NAME),
clean=True, # starts fresh
)
# Create Steppers
for (size, params) in NEMA_SIZES.items():
catalogue.add(
id='NEMA_Stepper_%i' % size,
obj=Stepper(**params),
criteria={
'type': 'motor',
'class': 'stepper',
'size': size,
},
)

View File

@ -0,0 +1 @@
{"items": {"1": {"obj": {"params": {"boss_length": 1.5, "shaft_length": 10.0, "hole_spacing": 15.4, "hole_size": 2.0, "boss_size": 16.0, "length": 28.0, "shaft_diam": 4.0, "width": 20.3}, "class": {"name": "Stepper", "module": "cqparts_motors.stepper"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "NEMA_Stepper_8", "criteria": {"type": "motor", "class": "stepper", "size": 8}}, "2": {"obj": {"params": {"boss_length": 2.0, "shaft_length": 24.0, "hole_spacing": 31.0, "hole_size": 3.0, "boss_size": 22.0, "length": 50.0, "shaft_diam": 5.0, "width": 42.0}, "class": {"name": "Stepper", "module": "cqparts_motors.stepper"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "NEMA_Stepper_17", "criteria": {"type": "motor", "class": "stepper", "size": 17}}, "3": {"obj": {"params": {"boss_length": 2.0, "shaft_length": 20.0, "hole_spacing": 23.0, "hole_size": 2.5, "boss_size": 22.0, "length": 31.5, "shaft_diam": 4.0, "width": 28.2}, "class": {"name": "Stepper", "module": "cqparts_motors.stepper"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "NEMA_Stepper_11", "criteria": {"type": "motor", "class": "stepper", "size": 11}}, "4": {"obj": {"params": {"boss_length": 2.0, "shaft_length": 24.0, "hole_spacing": 26.0, "hole_size": 3.0, "boss_size": 22.0, "length": 28.0, "shaft_diam": 5.0, "width": 35.2}, "class": {"name": "Stepper", "module": "cqparts_motors.stepper"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "NEMA_Stepper_14", "criteria": {"type": "motor", "class": "stepper", "size": 14}}, "5": {"obj": {"params": {"boss_length": 1.6, "shaft_length": 21.0, "hole_spacing": 47.0, "hole_size": 5.0, "boss_size": 38.0, "length": 56.0, "shaft_diam": 6.35, "width": 57.0}, "class": {"name": "Stepper", "module": "cqparts_motors.stepper"}, "lib": {"version": "0.2.2.dev1", "name": "cqparts"}}, "id": "NEMA_Stepper_23", "criteria": {"type": "motor", "class": "stepper", "size": 23}}}, "_dbinfo": {"1": {"module": "cqparts.catalogue.json", "ver": "0.1", "name": "JSONCatalogue", "lib": "cqparts", "lib_version": "0.2.2.dev1"}}}

170
ThirdParty/cqparts_motors/dc.py vendored Normal file
View File

@ -0,0 +1,170 @@
"""
DC motor cqparts model
2018 Simon Kirkby obeygiantrobot@gmail.com
"""
import math
import cadquery as cq
import cqparts
from cqparts.params import PositiveFloat, String
from cqparts.display import render_props
from cqparts.constraint import Fixed, Coincident
from cqparts.constraint import Mate
from cqparts.utils.geometry import CoordSystem
from cqparts_motors import shaft, motor
# defines the profile of the motor , returns a wire
def _profile(shape, diam, thickness):
work_plane = cq.Workplane("XY")
if shape == "circle":
profile = work_plane.circle(diam/2)
if shape == "flat":
radius = diam / 2
half_thickness = thickness / 2
intersect = math.sqrt(radius*radius-half_thickness*half_thickness)
profile = work_plane.moveTo(0, half_thickness)\
.lineTo(intersect, half_thickness)\
.threePointArc((radius, 0), (intersect, -half_thickness))\
.lineTo(0, -half_thickness)\
.mirrorY()
if shape == "rect":
profile = work_plane.rect(thickness/2, diam/2)
return profile
# the motor cup
class _Cup(cqparts.Part):
height = PositiveFloat(25.1, doc="cup length")
diam = PositiveFloat(20.4, doc="cup diameter")
thickness = PositiveFloat(15.4, doc="cup thickness for flat profile")
hole_spacing = PositiveFloat(12.4, doc="distance between the holes")
hole_size = PositiveFloat(2, doc="hole size")
step_diam = PositiveFloat(12, doc="step diameter")
step_height = PositiveFloat(0, doc="height if step, if zero no step")
bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
bush_height = PositiveFloat(1.6, doc="height of the bush")
profile = String("flat", doc="profile shape (circle|flat|rect)")
def make(self):
# grab the correct profile
work_plane = cq.Workplane("XY")
cup = _profile(self.profile, self.diam, self.thickness)\
.extrude(-self.height)
if self.step_height > 0:
step = work_plane.circle(self.step_diam/2).extrude(self.step_height)
cup = cup.union(step)
bush = work_plane.workplane(
offset=self.step_height)\
.circle(self.bush_diam/2)\
.extrude(self.bush_height)
cup = cup.union(bush)
return cup
def get_cutout(self, clearance=0):
" get the cutout for the shaft"
return cq.Workplane('XY', origin=(0, 0, 0)) \
.circle((self.diam / 2) + clearance) \
.extrude(10)
@property
def mate_bottom(self):
" connect to the bottom of the cup"
return Mate(self, CoordSystem(\
origin=(0, 0, -self.height),\
xDir=(1, 0, 0),\
normal=(0, 0, 1)))
class _BackCover(cqparts.Part):
height = PositiveFloat(6, doc="back length")
diam = PositiveFloat(20.4, doc="back diameter")
thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
profile = String("flat", doc="profile shape (circle|flat|rect)")
bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
bush_height = PositiveFloat(1.6, doc="height of the bush")
_render = render_props(color=(50, 255, 255))
def make(self):
# grab the correct profile
work_plane = cq.Workplane("XY")
back = work_plane.workplane(offset=-self.height)\
.circle(self.bush_diam/2)\
.extrude(-self.bush_height)
if self.height > 0:
back = _profile(self.profile, self.diam, self.thickness)\
.extrude(-self.height)
back = back.union(back)
return back
class DCMotor(motor.Motor):
"""
DC motors for models
.. image:: /_static/img/motors/DCMotor.png
"""
height = PositiveFloat(25.1, doc="motor length")
diam = PositiveFloat(20.4, doc="motor diameter")
thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
profile = String("flat", doc="profile shape (circle|flat|rect)")
bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
bush_height = PositiveFloat(1.6, doc="height of the bush")
shaft_type = shaft.Shaft #replace with other shaft
shaft_length = PositiveFloat(11.55, doc="length of the shaft")
shaft_diam = PositiveFloat(2, doc="diameter of the shaft")
cover_height = PositiveFloat(0, doc="back cover height")
# a step on the top surface
step_height = PositiveFloat(0, doc="height if step, if zero no step")
step_diam = PositiveFloat(12, doc="step diameter")
def get_shaft(self):
return self.shaft_type
def mount_points(self):
# TODO handle mount points
pass
def make_components(self):
return {
'body': _Cup(
height=self.height,
thickness=self.thickness,
diam=self.diam,
profile=self.profile,
bush_diam=self.bush_diam,
bush_height=self.bush_height,
step_height=self.step_height
),
'shaft': self.shaft_type(length=self.shaft_length, diam=self.shaft_diam),
'back': _BackCover(
height=self.cover_height,
thickness=self.thickness,
diam=self.diam,
profile=self.profile,
bush_diam=self.bush_diam,
bush_height=self.bush_height
)
}
def make_constraints(self):
return [
Fixed(self.components['body'].mate_origin),
Coincident(
self.components['shaft'].mate_origin,
self.components['body'].mate_origin,
),
Coincident(
self.components['back'].mate_origin,
self.components['body'].mate_bottom,
)
]

18
ThirdParty/cqparts_motors/motor.py vendored Normal file
View File

@ -0,0 +1,18 @@
# base motor class
#
# 2018 Simon Kirkby obeygiantrobot@gmail.com
import cqparts
# base motor class
# TODO lift all motor things up to here
class Motor(cqparts.Assembly):
def mount_points(self):
raise NotImplementedError("mount_points function not implemented")
def get_shaft(self):
raise NotImplementedError("get_shaft function not implemented")

View File

@ -0,0 +1 @@
cqparts

48
ThirdParty/cqparts_motors/shaft.py vendored Normal file
View File

@ -0,0 +1,48 @@
"""
cp parts
base shaft collection
# 2018 Simon Kirkby obeygiantrobot@gmail.com
"""
# TODO
# need tip , base and offset mate points
# maybe shaft needs to go into it's own module
#
# there are lots of types of shafts and extras
# need a clean way to build shafts
import cadquery as cq
import cqparts
from cqparts.params import PositiveFloat
from cqparts.display import render_props
# base shaft type
class Shaft(cqparts.Part):
" base shaft , override ME"
length = PositiveFloat(24, doc="shaft length")
diam = PositiveFloat(5, doc="shaft diameter")
_render = render_props(color=(50, 255, 255))
def make(self):
shft = cq.Workplane("XY")\
.circle(self.diam/2)\
.extrude(self.length)\
.faces(">Z")\
.chamfer(0.4)
return shft
def cut_out(self):
cutout = cq.Workplane("XY")\
.circle(self.diam/2)\
.extrude(self.length)
return cutout
# TODO , mate for shafts
def get_cutout(self, clearance=0):
" clearance cut out for shaft "
return cq.Workplane('XY', origin=(0, 0, 0)) \
.circle((self.diam / 2) + clearance) \
.extrude(self.length*2)

225
ThirdParty/cqparts_motors/stepper.py vendored Normal file
View File

@ -0,0 +1,225 @@
""" cqparts motors
2018 Simon Kirkby obeygiantrobot@gmail.com
stepper motor generic
"""
# TODO
# even 4 fasteners so it auto mounts to whatever it is parented to.
import cadquery as cq
import cqparts
from cqparts.params import PositiveFloat
from cqparts.constraint import Fixed, Coincident
from cqparts.constraint import Mate
from cqparts.display import render_props
from cqparts.utils.geometry import CoordSystem
import shaft
import motor
class _EndCap(cqparts.Part):
# Parameters
width = PositiveFloat(42.3, doc="Motor Size")
length = PositiveFloat(10, doc="End length")
cham = PositiveFloat(3, doc="chamfer")
# _render = render_props(color=(50, 50, 50),alpha=0.4)
def make(self):
base = cq.Workplane("XY")\
.box(self.width, self.width, self.length)\
.edges("|Z")\
.chamfer(self.cham)
return base
@property
def mate_top(self):
" connect to the end of the top cap"
return Mate(self, CoordSystem(
origin=(0, 0, -self.length/2),
xDir=(0, 1, 0),
normal=(0, 0, -1)
))
@property
def mate_bottom(self):
" bottom of the top cap"
return Mate(self, CoordSystem(
origin=(0, 0, -self.length/2),
xDir=(0, 1, 0),
normal=(0, 0, 1)
))
class _Stator(cqparts.Part):
# Parameters
width = PositiveFloat(40.0, doc="Motor Size")
length = PositiveFloat(20, doc="stator length")
cham = PositiveFloat(3, doc="chamfer")
_render = render_props(color=(50, 50, 50))
def make(self):
base = cq.Workplane("XY")\
.box(self.width, self.width, self.length,centered=(True,True,True))\
.edges("|Z")\
.chamfer(self.cham)
return base
@property
def mate_top(self):
" top of the stator"
return Mate(self, CoordSystem(
origin=(0, 0, self.length/2),
xDir=(0, 1, 0),
normal=(0, 0, 1)
))
@property
def mate_bottom(self):
" bottom of the stator"
return Mate(self, CoordSystem(
origin=(0, 0, -self.length/2),
xDir=(1, 0, 0),
normal=(0, 0, -1)
))
class _StepperMount(_EndCap):
spacing = PositiveFloat(31, doc="hole spacing")
hole_size = PositiveFloat(3, doc="hole size")
boss = PositiveFloat(22, doc="boss size")
boss_length = PositiveFloat(2, doc="boss_length")
def make(self):
obj = super(_StepperMount, self).make()
obj.faces(">Z").workplane() \
.rect(self.spacing, self.spacing, forConstruction=True)\
.vertices() \
.hole(self.hole_size)
obj.faces(">Z").workplane()\
.circle(self.boss/2).extrude(self.boss_length)
return obj
@property
def mate_top(self):
" top of the mount"
return Mate(self, CoordSystem(
origin=(0, 0, self.length/2),
xDir=(0, 1, 0),
normal=(0, 0, 1)
))
@property
def mate_bottom(self):
" bottom of the mount"
return Mate(self, CoordSystem(
origin=(0, 0,-self.length/2),
xDir=(0, 1, 0),
normal=(0, 0, 1)
))
class _Back(_EndCap):
spacing = PositiveFloat(31, doc="hole spacing")
hole_size = PositiveFloat(3, doc="hole size")
def make(self):
obj = super(_Back, self).make()
obj.faces(">Z").workplane() \
.rect(self.spacing, self.spacing, forConstruction=True)\
.vertices() \
.hole(self.hole_size)
return obj
class Stepper(motor.Motor):
" Stepper Motor , simple rendering "
shaft_type = shaft.Shaft
width = PositiveFloat(42.3, doc="width and depth of the stepper")
length = PositiveFloat(50, doc="length of the stepper")
hole_spacing = PositiveFloat(31.0, doc="distance between centers")
hole_size = PositiveFloat(3, doc="hole diameter , select screw with this")
boss_size = PositiveFloat(22, doc="diameter of the raise circle")
boss_length = PositiveFloat(2, doc="length away from the top surface")
shaft_diam = PositiveFloat(5, doc="diameter of the the shaft ")
shaft_length = PositiveFloat(24, doc="length from top surface")
def get_shaft(self):
return self.components['shaft']
def mount_points(self):
" return mount points"
wp = cq.Workplane("XY")
h = wp.rect(self.hole_spacing,self.hole_spacing
,forConstruction=True).vertices()
return h.objects
def make_components(self):
sec = self.length / 6
return {
'topcap': _StepperMount(
width=self.width,
length=sec,
spacing=self.hole_spacing,
hole_size=self.hole_size,
boss=self.boss_size
),
'stator': _Stator(width=self.width-3, length=sec*4),
'botcap': _Back(
width=self.width,
length=sec,
spacing=self.hole_spacing,
hole_size=self.hole_size,
),
'shaft': self.shaft_type(
length=self.shaft_length,
diam=self.shaft_diam)
}
def make_constraints(self):
return [
Fixed(self.components['topcap'].mate_top),
Coincident(
self.components['stator'].mate_top,
self.components['topcap'].mate_bottom,
),
Coincident(
self.components['botcap'].mate_bottom,
self.components['stator'].mate_bottom
),
Coincident(
self.components['shaft'].mate_origin,
self.components['topcap'].mate_top
),
]
def apply_cutout(self):
" shaft cutout "
stepper_shaft = self.components['shaft']
top = self.components['topcap']
local_obj = top.local_obj
local_obj = local_obj.cut(stepper_shaft.get_cutout(clearance=0.5))
def make_alterations(self):
self.apply_cutout()
def boss_cutout(self,clearance=0):
bc = cq.Workplane("XY")\
.circle(self.boss_size/2)\
.extrude(self.shaft_length)
return bc
def cut_boss(self,part,clearance=0):
co = self.boss_cutout(clearance=clearance)
lo = part.local_obj\
.cut((self.world_coords - part.world_coords)+co)
if __name__ == "__main__":
from cqparts.display import display
st = Stepper()
display(st)

1
ThirdParty/cqparts_springs/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

17
ThirdParty/cqparts_springs/__init__.py vendored Normal file
View File

@ -0,0 +1,17 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build

View File

@ -0,0 +1 @@
cqparts

1
ThirdParty/cqparts_template/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

25
ThirdParty/cqparts_template/README.md vendored Normal file
View File

@ -0,0 +1,25 @@
# `cqparts` library template
This is a library template intended to be copied then modified to contain your
created components.
This library has a number of examples intended to illustrate the following:
* End user buildable components
* `Part` and `Assembly` classes
* Registration with `cqparts.search` [optional]
* Intermediate sub-components used for building
* `Part` and `Assembly` classes prefixed with `_`
* Sphinx documentation practices
* Creating a `Catalogue` of your component variants.
# Steps to Creating a New Library
The following steps should give you an idea of how to proceed:
1. **Copy**: Make a copy of this folder, and re-name it to
`cqparts_<your-lib-name>`.
1. **Create**: Create your _components_.
1. **TODO**: search for occurances of `TODO`, and addres each item.
1. **`README.md`**: `TODO`: remember to rename `README.tmp.md` to `README.md`,
replacing this file.

View File

@ -0,0 +1,20 @@
# `cqparts_TODO` Components
TODO: brief description of your library, include an image
## Components
TODO: List components by group, give a user an idea of the breadth of the
library's content
## Utilities
TODO: List anything that isn't strictly a `Component` that may be used as part
of this library.
## Example
TODO: A brief example of your library, include fully qualified code and an image
of the resulting model
For more detailed documentation, consider using sphinx, that way cross-linking
of components in documentation is easy.

41
ThirdParty/cqparts_template/__init__.py vendored Normal file
View File

@ -0,0 +1,41 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build
__title__ = "cqparts_<lib_name>" # TODO
__description__ = "<brief description>" # TODO
__url__ = "<library url>" # TODO
__version__ = "0.1.0"
__author__ = "<your name>" # TODO
# ------ Registration
from cqparts.search import (
find as _find,
search as _search,
register as _register,
)
from cqparts.search import common_criteria
module_criteria = {
'library': __name__,
}
register = common_criteria(**module_criteria)(_register)
search = common_criteria(**module_criteria)(_search)
find = common_criteria(**module_criteria)(_find)

View File

@ -0,0 +1 @@
{"items": {"1": {"obj": {"params": {"spring_wire_diam": 1.3, "minor_radius": 1.0, "spring_arm_length": 17.5, "tip_chamfer": 5.0, "minor_depth": 1.0, "handle_length": 35.0, "depth": 7.0, "width": 10.0, "handle_tip_depth": 2.0, "minor_offset": 31.0, "major_depth": 2.0, "spring_diam": 5.0, "major_radius": 10.0, "length": 90.0, "major_offset": 17.0}, "class": {"name": "ClothesPeg", "module": "cqparts_template.clamp.peg"}, "lib": {"version": "0.1.0", "name": "cqparts"}}, "id": "PEG01", "criteria": {"style": "long"}}, "2": {"obj": {"params": {"spring_wire_diam": 1.3, "minor_radius": 1.0, "spring_arm_length": 17.5, "tip_chamfer": 5.0, "minor_depth": 1.0, "handle_length": 30.0, "handle_tip_depth": 2.0, "width": 10.0, "depth": 7.0, "minor_offset": 31.0, "major_depth": 2.0, "spring_diam": 5.0, "major_radius": 10.0, "length": 75.0, "major_offset": 17.0}, "class": {"name": "ClothesPeg", "module": "cqparts_template.clamp.peg"}, "lib": {"version": "0.1.0", "name": "cqparts"}}, "id": "PEG02", "criteria": {"style": "norm"}}, "3": {"obj": {"params": {"depth": 7.0, "spring_wire_diam": 1.3, "minor_radius": 1.0, "spring_arm_length": 17.5, "tip_chamfer": 5.0, "minor_depth": 1.0, "handle_length": 20.0, "handle_tip_depth": 2.0, "width": 10.0, "spring_diam": 5.0, "minor_offset": 31.0, "major_depth": 2.0, "major_radius": 10.0, "length": 60.0, "major_offset": 17.0}, "class": {"name": "ClothesPeg", "module": "cqparts_template.clamp.peg"}, "lib": {"version": "0.1.0", "name": "cqparts"}}, "id": "PEG03", "criteria": {"style": "short"}}}, "_dbinfo": {"1": {"lib_version": "0.1.0", "ver": "0.1", "name": "JSONCatalogue", "module": "cqparts.catalogue.json", "lib": "cqparts"}}}

View File

@ -0,0 +1,9 @@
# Catalogue Scripts
Scripts, and input files in this folder are used to build catalogues for the
parent directory.
This folder is not a sub-module so it should **not** contain a `__init__.py`
file.
TODO: remove this file, or replace it with content relevant to the scripts.

View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
import argparse
import os
import inspect
import csv
import cqparts
from cqparts.catalogue import JSONCatalogue
# TODO: import your library
# TODO: if you don't offer a catalogue for your library, then
# remove this `scripts` folder.
import cqparts_template
from cqparts_template.clamp.peg import ClothesPeg
# ---------- Parameter Defaults ----------
_this_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
def _relative_path_to(path_list, filename):
"""Get a neat relative path to files relative to the CWD"""
return os.path.join(
os.path.relpath(os.path.join(*path_list), os.getcwd()),
filename
)
DEFAULT_OUTPUT = _relative_path_to([_this_path, '..'], 'pegs.json')
DEFAULT_INPUT = _relative_path_to([_this_path], 'pegs.csv')
parser = argparse.ArgumentParser(
prog='Build catalogue', # TODO: change program name
)
parser.add_argument(
'-o', '--output', dest='output',
default=DEFAULT_OUTPUT, metavar="FILE",
help='catalogue file to write',
)
parser.add_argument(
'-i', '--input', dest='input',
default=DEFAULT_INPUT, metavar="FILE",
help='component parameters file',
)
args = parser.parse_args()
# ---------- Component Builders ----------
# TODO: build your own objects in whatever way suits your application.
def build_obj(cls, **kwargs):
# Take any parameters relevent to the given class from kwargs
# (all other parameters ignored)
class_params = cls.class_params()
obj_kwargs = {
key: kwargs.pop(key, param.default)
for (key, param) in class_params.items()
}
return cls(**obj_kwargs)
# ---------- Create Catalogue ----------
catalogue = JSONCatalogue(args.output, clean=True)
with open(args.input, "rb" ) as csv_fileio:
csv_reader = csv.DictReader(csv_fileio)
for line in csv_reader:
obj = build_obj(ClothesPeg, **line)
criteria = {
key: line[key]
for key in line.keys()
if (not hasattr(obj, key)) and (key not in ('ID',))
}
catalogue.add(id=line['ID'], obj=obj, criteria=criteria)

View File

@ -0,0 +1,4 @@
ID,length,handle_length,style
PEG01,90,35,long
PEG02,75,30,norm
PEG03,60,20,short
1 ID length handle_length style
2 PEG01 90 35 long
3 PEG02 75 30 norm
4 PEG03 60 20 short

View File

@ -0,0 +1,5 @@
__all__ = [
'ClothesPeg',
]
from .peg import ClothesPeg

182
ThirdParty/cqparts_template/clamp/peg.py vendored Normal file
View File

@ -0,0 +1,182 @@
# TODO: illustrative model only; remove this file
import cadquery
import cqparts
from cqparts.params import *
from cqparts.display import render_props
from cqparts import constraint
from cqparts.utils import CoordSystem
from .. import register
class _PegSide(cqparts.Part):
"""
One side of a wooden clothes peg.
Note that this docstring does not get rendered in the sphinx automated
documentation, this is because the class is prefixed with a ``_``.
Also: idiomatic Python dictates that components with a ``_`` prefix are not
intended for an end-user, which is why they're not documented.
"""
length = PositiveFloat()
width = PositiveFloat()
depth = PositiveFloat()
tip_chamfer = PositiveFloat()
handle_tip_depth = PositiveFloat()
handle_length = PositiveFloat()
# spring
spring_diam = PositiveFloat()
spring_arm_length = PositiveFloat()
spring_wire_diam = PositiveFloat()
# major indent
major_radius = PositiveFloat()
major_depth = PositiveFloat()
major_offset = PositiveFloat()
# minor indent
minor_radius = PositiveFloat()
minor_depth = PositiveFloat()
minor_offset = PositiveFloat()
# Default material to render
_render = render_props(template='wood')
def make(self):
# Main profile shape of peg
points = [
(0, 0), (self.length, 0),
(self.length, self.handle_tip_depth),
(self.length - self.handle_length, self.depth),
(self.tip_chamfer, self.depth),
(0, self.depth - self.tip_chamfer),
]
side = cadquery.Workplane('XY') \
.moveTo(*points[0]).polyline(points[1:]).close() \
.extrude(self.width)
# cut spring
side = side.cut(cadquery.Workplane('XY') \
.moveTo(self.length - self.handle_length, self.depth) \
.circle(self.spring_diam / 2).extrude(self.width)
)
# cut indents
def cut_indent(obj, radius, depth, offset):
return obj.cut(cadquery.Workplane('XY') \
.moveTo(offset, self.depth + (radius - depth)) \
.circle(radius).extrude(self.width)
)
side = cut_indent(
obj=side,
radius=self.major_radius,
depth=self.major_depth,
offset=self.major_offset,
)
side = cut_indent(
obj=side,
radius=self.minor_radius,
depth=self.minor_depth,
offset=self.minor_offset,
)
return side
@property
def mate_spring_center(self):
# mate in the center of the spring, z-axis along spring center
return constraint.Mate(self, CoordSystem(
origin=(self.length - self.handle_length, self.depth, self.width / 2),
))
@property
def mate_side(self):
# mate in middle of outside edge, z-axis into peg
return constraint.Mate(self, CoordSystem(
origin=(self.length / 2, 0, self.width / 2),
xDir=(0, 0, -1),
normal=(0, 1, 0),
))
class _Spring(cqparts.Part):
diam = PositiveFloat()
arm_length = PositiveFloat()
wire_diam = PositiveFloat()
width = PositiveFloat()
def make(self):
spring = cadquery.Workplane('XY', origin=(0, 0, -(self.width / 2 + self.wire_diam))) \
.circle(self.diam / 2).circle((self.diam / 2) - self.wire_diam) \
.extrude(self.width + (2 * self.wire_diam))
return spring
@register(module=__name__, name='clothespeg', type='peg_clamp')
class ClothesPeg(cqparts.Assembly):
"""
A common household clothes-peg
.. image:: /_static/img/template/peg.png
"""
length = PositiveFloat(75, doc="length of peg side")
width = PositiveFloat(10, doc="peg width")
depth = PositiveFloat(7, doc="depth of peg side, half peg's full depth")
tip_chamfer = PositiveFloat(5, doc="chamfer at tip")
handle_tip_depth = PositiveFloat(2, doc="depth at handle's tip")
handle_length = PositiveFloat(30, doc="length of tapered handle")
# spring
spring_diam = PositiveFloat(5, doc="diameter of spring's core")
spring_arm_length = PositiveFloat(17.5, doc="length of spring's arm converting torque to closing force")
spring_wire_diam = PositiveFloat(1.3, doc="diamter of spring's wire")
# major indent
major_radius = PositiveFloat(10, doc="large indentation's radius")
major_depth = PositiveFloat(2, doc="large indentation's depth")
major_offset = PositiveFloat(17, doc="large indentation center's distance from tip")
# minor indent
minor_radius = PositiveFloat(1, doc="small indentation's radius")
minor_depth = PositiveFloat(1, doc="small indentation's depth")
minor_offset = PositiveFloat(31, doc="small indentation center's distance from tip")
def make_components(self):
params = self.params(hidden=False) # common to _PegSide
return {
'bottom': _PegSide(**params),
'top': _PegSide(**params),
'spring': _Spring(
diam=self.spring_diam,
arm_length=self.spring_arm_length,
wire_diam=self.spring_wire_diam,
width=self.width,
),
}
def make_constraints(self):
bottom = self.components['bottom']
top = self.components['top']
spring = self.components['spring']
return [
constraint.Fixed(bottom.mate_side),
constraint.Coincident(
top.mate_spring_center,
bottom.mate_spring_center + CoordSystem(normal=(0, 0, -1))
),
constraint.Coincident(
spring.mate_origin,
bottom.mate_spring_center
),
]

View File

@ -0,0 +1 @@
cqparts

15
ThirdParty/cqparts_template/search.py vendored Normal file
View File

@ -0,0 +1,15 @@
from cqparts.search import (
find as _find,
search as _search,
register as _register,
)
from cqparts.search import common_criteria
module_criteria = {
'module': __name__,
}
register = common_criteria(**module_criteria)(_register)
search = common_criteria(**module_criteria)(_search)
find = common_criteria(**module_criteria)(_find)

View File

@ -0,0 +1 @@
../../LICENSE

View File

@ -0,0 +1,17 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build

View File

View File

@ -0,0 +1 @@
cqparts

View File

1
ThirdParty/cqparts_toys/LICENSE vendored Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE

17
ThirdParty/cqparts_toys/__init__.py vendored Normal file
View File

@ -0,0 +1,17 @@
"""
Copyright 2018 Peter Boin
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.
"""
__release_ready__ = False # TODO: remove to stop blocking build

View File

@ -0,0 +1 @@
cqparts

View File

188
ThirdParty/cqparts_toys/train/track.py vendored Normal file
View File

@ -0,0 +1,188 @@
import cadquery
import cqparts
from cqparts.params import *
from cqparts.utils import property_buffered
from cqparts.display.material import render_props
from cqparts.constraint import Mate
from cqparts.utils import CoordSystem
class _Track(cqparts.Part):
double_sided = Boolean(True, doc="if set, track is cut from both sides")
# Profile Metrics
width = PositiveFloat(30, doc="track width")
depth = PositiveFloat(8, doc="track thickness")
track_guage = PositiveFloat(None, doc="distance between wheel centers")
track_width = PositiveFloat(None, doc="wheel's width")
track_depth = PositiveFloat(2, doc="depth each track is cut")
track_chamfer = PositiveFloat(None, doc="chamfer at wheel's edges")
# Connector
conn_diam = PositiveFloat(None, doc="diameter of connector circle")
conn_neck_width = PositiveFloat(None, doc="connector neck width")
conn_neck_length = PositiveFloat(None, doc="connector neck length")
conn_clearance = PositiveFloat(0.5, doc="clearance ")
_render = render_props(template='wood')
def initialize_parameters(self):
super(_Track, self).initialize_parameters()
if self.track_guage is None:
self.track_guage = self.width * (2. / 3)
if self.track_width is None:
self.track_width = self.track_guage / 4
if self.track_depth is None:
self.track_depth = self.track_width / 2
if self.track_chamfer is None:
self.track_chamfer = self.track_depth / 3
if self.conn_diam is None:
self.conn_diam = self.depth
if self.conn_neck_width is None:
self.conn_neck_width = self.conn_diam / 3
if self.conn_neck_length is None:
self.conn_neck_length = self.conn_diam * 0.6
@property
def _wheel_profile(self):
if self.track_chamfer:
left_side = (self.track_guage / 2) - (self.track_width / 2)
points = [
(left_side - (self.track_chamfer * 2), (self.depth / 2) + self.track_depth),
(left_side - (self.track_chamfer * 2), (self.depth / 2) + self.track_chamfer),
(left_side, (self.depth / 2) - self.track_chamfer), # remove if self.track_chamfer == 0
(left_side, (self.depth / 2) - self.track_depth),
]
# mirror over x = self.track_guage / 2 plane
points += [(self.track_guage - x, y) for (x, y) in reversed(points)]
else:
# no chamfer, just plot the points for a rectangle
points = [
((self.track_guage / 2) - (self.track_width / 2), (self.depth / 2) + self.track_depth),
((self.track_guage / 2) - (self.track_width / 2), (self.depth / 2) - self.track_depth),
((self.track_guage / 2) + (self.track_width / 2), (self.depth / 2) - self.track_depth),
((self.track_guage / 2) + (self.track_width / 2), (self.depth / 2) + self.track_depth),
]
flip = lambda p, xf, yf: (p[0] * xf, p[1] * yf)
profile = cadquery.Workplane('XZ') \
.moveTo(*flip(points[0], 1, 1)).polyline([flip(p, 1, 1) for p in points[1:]]).close() \
.moveTo(*flip(points[0], -1, 1)).polyline([flip(p, -1, 1) for p in points[1:]]).close()
if self.double_sided:
profile = profile \
.moveTo(*flip(points[0], 1, -1)).polyline([flip(p, 1, -1) for p in points[1:]]).close() \
.moveTo(*flip(points[0], -1, -1)).polyline([flip(p, -1, -1) for p in points[1:]]).close()
return profile
@property
def _track_profile(self):
return cadquery.Workplane('XZ').rect(self.width, self.depth)
def _get_connector(self, clearance=False):
clear_dist = self.conn_clearance if clearance else 0.
return cadquery.Workplane('XY').box(
self.conn_neck_width + (clear_dist * 2),
self.conn_neck_length + self.conn_diam / 2,
self.depth,
centered=(True, False, True),
).union(cadquery.Workplane('XY', origin=(
0, self.conn_neck_length + self.conn_diam / 2, -self.depth / 2
)).circle((self.conn_diam / 2) + clear_dist).extrude(self.depth))
@property
def conn_length(self):
return self.conn_neck_length + self.conn_diam
class StraightTrack(_Track):
"""
.. image:: /_static/img/toys/track-straight.png
"""
length = PositiveFloat(100, doc="track length")
def make(self):
track = self._track_profile.extrude(self.length) \
.translate((0, self.length / 2, 0)) \
.union(self._get_connector().translate((0, self.length / 2, 0))) \
.cut(self._get_connector(True).translate((0, -self.length / 2, 0)))
# cut tracks
track = track.cut(
self._wheel_profile \
.extrude(self.length + self.conn_length) \
.translate((0, self.length / 2 + self.conn_length, 0))
)
return track
def make_simple(self):
return self._track_profile.extrude(self.length) \
.translate((0, self.length / 2, 0))
@property
def mate_start(self):
return Mate(self, CoordSystem((0, -self.length / 2, 0)))
@property
def mate_end(self):
return Mate(self, CoordSystem((0, self.length / 2, 0)))
class CurvedTrack(_Track):
"""
.. image:: /_static/img/toys/track-curved.png
"""
turn_radius = Float(100, doc="radius of turn")
turn_angle = FloatRange(0, 360, 45, doc="arc angle covered by track (unit: degrees)")
def make(self):
revolve_params = {
'angleDegrees': self.turn_angle,
'axisStart': (self.turn_radius, 0),
'axisEnd': (self.turn_radius, 1 if (self.turn_radius > 0) else -1),
}
track = self._track_profile.revolve(**revolve_params) \
.translate((-self.turn_radius, 0, 0)) \
.cut(self.mate_start.local_coords + self._get_connector(True)) \
.union(self.mate_end.local_coords + self._get_connector(False))
# cut tracks
track = track.cut(
self._wheel_profile.revolve(**revolve_params) \
.translate((-self.turn_radius, 0, 0))
)
track = track.cut(
self.mate_end.local_coords + self._wheel_profile.extrude(-self.conn_length)
)
return track
#return self._wheel_profile.extrude(self.conn_length)
def make_simple(self):
return self._track_profile.revolve(
angleDegrees=self.turn_angle,
axisStart=(self.turn_radius, 0),
axisEnd=(self.turn_radius, 1),
).translate((-self.turn_radius, 0, 0)) \
@property
def mate_start(self):
return Mate(self, CoordSystem((-self.turn_radius, 0, 0)))
@property
def mate_end(self):
angle = self.turn_angle if (self.turn_radius < 0) else -self.turn_angle
return Mate(self, CoordSystem().rotated((0, 0, angle)) + self.mate_start.local_coords)