90 Commits

Author SHA1 Message Date
looooo
85ae781b73 add basegear and keep feature.py 2024-01-02 02:02:20 +01:00
looooo
0ea2f229b7 Revert "rename features to basegear"
This reverts commit 878811ae54.
2024-01-02 01:53:29 +01:00
looooo
878811ae54 rename features to basegear 2024-01-02 01:37:03 +01:00
looooo
0d10df1997 ruff formating 2024-01-02 01:28:04 +01:00
looooo
7c747bf151 refactoring (split in different files)
make explicitly dependent on scipy
2024-01-02 01:25:46 +01:00
looooo
9e14aac76b add timing gear t-shape 2024-01-02 00:02:42 +01:00
looooo
17715c0754 update notebook 2024-01-01 16:07:33 +01:00
looooo
cb22a878a5 add example for worm cutting 2023-12-29 15:38:18 +01:00
lorenz
37e2b04c7e add theory for a arbitrary rack 2023-12-28 12:47:13 +01:00
lorenz
a3dfa1229e Update init_gui.py 2023-12-26 13:14:03 +01:00
lorenz
7076d2ff4b Update __init__.py 2023-12-26 13:14:03 +01:00
Syres916
6197bfdc16 Update package.xml 2023-12-26 13:14:03 +01:00
Syres916
48f3f4a7d3 [Gears] Allow for both Python 3.11 and above as...
well as 3.10 and below as they are all currently supported by FreeCAD core.
2023-12-26 13:14:03 +01:00
Syres916
6d04fb1031 [Gears] Check if Python 3.11 or above is in use
and if so is the version of FreeCAD sufficient to properly handle pickle of JSON data
2023-12-26 13:14:03 +01:00
looooo
0d183f691c ruff format 2023-12-25 23:13:36 +01:00
looooo
17f04bfde4 ruff format pygears 2023-12-25 23:11:41 +01:00
looooo
22baac93bd __getstate__, __setstate__ -> loads, dumps 2023-12-25 14:15:31 +01:00
lorenz
681ad0a222 Create pylint.yml 2023-12-24 19:03:53 +01:00
Syres916
7af3806181 [Gears] Fix AddonManager not updating to...
...most recent master commit
2023-11-27 17:16:57 +01:00
Syres916
fe626176a3 [Gears] Remove unused import 2023-11-27 13:07:37 +01:00
Syres916
c6f09bd864 [Gears] Remove Deepcopy dependency 2023-11-27 13:07:37 +01:00
Syres916
e6e6e9b7d4 [Gears] Remove dependency on Deepcopy 2023-11-27 13:07:37 +01:00
Scott Mudge
0eb9913420 fixed issue loading old document without traverse_module existing in gear XML 2023-10-15 11:11:12 +02:00
BearTM
2def33833a Update features.py to support HTD Timing Gear Profiles
Added 3M, 5M, 8M HTD Timing Gear Profiles
Corrected GT8 Profile naming from "htd8" to "gt8"
2023-09-24 13:14:48 +02:00
luzpaz
2f21661462 Remove LGTM badges from README.md
LGTM was folded into Gitub (https://github.blog/2022-08-15-the-next-step-for-lgtm-com-github-code-scanning/)
2023-09-11 17:14:04 +02:00
looooo
80fbd3d49e internal gear: don't use expression for dw 2023-01-22 20:58:36 +01:00
looooo
b38bfda072 remove expression for involute gear dw, it doesn't update correctly 2023-01-22 19:42:41 +01:00
looooo
23a649cd23 add gear-shaft example 2023-01-17 20:03:13 +01:00
looooo
bc41c864a1 traverse pitch computation 2022-12-28 13:32:51 +01:00
looooo
84026d912b fix involute-gear computation 2022-12-15 19:15:46 +01:00
lorenz
ba87611235 fix computed property dw 2022-12-15 18:25:44 +01:00
looooo
29e45aa613 fix rack traverse_pitch 2022-10-23 17:34:23 +02:00
Benjamin Vedder
60db69f302 Added HTD8 timing gear 2022-09-08 13:36:58 +02:00
looooo
4e6223bffa Merge branch 'develop' of https://github.com/looooo/freecad.gears into develop 2022-06-10 14:18:54 +02:00
looooo
07bfe0446d do not use pythonocc-core 2022-06-10 14:18:37 +02:00
lorenz
05d2afef44 InvoluteGearRack: properties_from_tool default = False 2022-05-03 14:48:55 +02:00
looooo
2ede7f0bdc Merge branch 'develop' of https://github.com/looooo/freecad.gears into develop 2022-02-22 11:07:24 +01:00
looooo
6042cb8b4d add root-fillet for cycloid rack 2022-02-22 11:07:02 +01:00
luz paz
2e25ba6b97 Add extra metadata to package.xml 2022-02-21 10:34:55 +01:00
looooo
6a9baf4fed add package.xml 2022-02-20 12:14:04 +01:00
looooo
1cf83e7344 gear-rack add fillets 2022-02-10 14:15:19 +01:00
looooo
39e5ce1378 add head-fillet for cycloid gear racks 2022-02-02 23:33:47 +01:00
Jonas Bähr
9f1e87bedb Calculate pitch diameter and angular backlash via expressions
Using expressions instead of filling these values in the `execute` method
has the advantage that the values can be used in other expressions on the
same object. First, the expressions are evaluated, then the feature is
"executed". Previously, the changed values are only available after the
execution and thus had no effect if e.g. used in calculating the
placement of the very same gear (until another recompute happens).
2022-01-12 11:24:06 +01:00
Jonas Bähr
7c4723c9b7 Interpret the backlash as "Circumferential Backlash"
This fixes some unit mismatch, as the backlash is specified as length,
but the involute tooth generation code used to interpret it as angular
value in radians (no idea why it was additionaly diveded by four; we need
the half on each side of the tooth).
Now half of the value specified as "backlash" can be directly measured at
the pitch circle when comparing each tooth flank of a gear with and
without backlash.
2022-01-08 11:03:26 +01:00
Jonas Bähr
d0e7720988 Fix head and clearance for the cycloid gear
Head and cleance values have been applied only once, but as we're dealing
with a diameter, not a radius, they are needed twice. (The involute gear
does not suffer from this bug)
2022-01-04 20:08:52 +01:00
Jonas Bähr
da42164552 Use expression as inner_diameter for cycloid gear
The cycloid gear is created with a default number of teeth of 15 and an
`inner_diameter` of 7.5. That is half the number of teeth and results in
an hypocycloid going orthogonal from the pitch circle right to the center.
This change now replaces the hard coded value "7.5" with the expression
"teeth / 2" so that this straight hypocycloid is kept, even if the number
of teeth is adapted. This allows easy reduction of the number of teeth
(down to two, in fact) without the recomputation to fail.
2022-01-04 20:05:28 +01:00
looooo
73f6b7f1f7 Merge branch 'develop' of https://github.com/looooo/freecad.gears into develop 2021-12-16 13:20:43 +01:00
looooo
590994984f connector: add support for internal involute gears 2021-12-16 13:20:11 +01:00
luz paz
b4783df755 Fix source comment typos 2021-12-07 21:19:20 +01:00
looooo
2ecc0e8744 cycloid gear rack: some fixes 2021-12-07 11:58:10 +01:00
looooo
d77f5c1ab4 connector: add support for racks 2021-12-06 21:03:14 +01:00
looooo
4aa2559629 connector: add support for cycloid gears 2021-12-04 14:07:08 +01:00
looooo
5846ffbfdc connector: add support for shifted gears 2021-12-04 13:25:20 +01:00
looooo
7edc50c32d some clean up 2021-12-02 15:14:06 +01:00
looooo
05abf3054d first prototyp for gear connector 2021-12-02 15:09:21 +01:00
looooo
10d4bf0bbf add missing files for gear connector 2021-12-02 12:45:49 +01:00
looooo
ea904a2a7d add prototype for gear connector 2021-12-02 12:44:34 +01:00
looooo
0eca70d832 change size of image 2021-12-01 13:28:57 +01:00
looooo
469e00cd98 add cycloid rack image 2021-12-01 13:26:12 +01:00
looooo
3668892abe cycloidgearrack additions 2021-12-01 11:10:27 +01:00
looooo
8b8f8e0779 cycloide gear rack 2021-11-30 15:27:52 +01:00
Scott Mudge
ef73b66040 add icon in same style as existing cycloid gear 2021-11-27 13:39:36 +01:00
Scott Mudge
89e99bef0e fix bugs 2021-11-24 15:00:51 +01:00
looooo
15f3f2410d add proto cycloide rack, fix icons-issue 2021-11-23 14:46:39 +01:00
looooo
fd363e2b84 some simplifications 2021-11-03 14:53:27 +01:00
looooo
81d6a77218 refactoring involute gear 2021-10-21 12:01:49 +02:00
lorenz
b75336b0ea fix lgtm allert 2021-10-19 10:46:32 +02:00
looooo
95a14777b9 use icons in treeview 2021-10-19 10:44:36 +02:00
looooo
7cb84f17e1 minor fix for the cycloide gear 2021-09-30 11:26:58 +02:00
looooo
b7a4192f70 fix internalgear, to work with head-, rootfillets 2021-09-29 10:40:45 +02:00
looooo
4db17dd2b0 fix internal gear 2021-09-29 10:21:00 +02:00
Jonas Bähr
165b52d967 Speedup the double helical gear generation significantly
It turned out that the "moving up" of the gear shape was responsible for
the majority of processing time: it took 10x longer then the pipe
creation and 100x longer then the mirroring.
Now the moving is done of the helix and base face so that the other faces
are generated directly where they should be, thus preventing a movement
of all of them.
In addition, as we don't have to transform the final shape, we don't have
to use transformGeometry, but just change the placement via translate --
again much faster.
2021-09-29 10:17:20 +02:00
Jonas Bähr
f1531a183b Cleanup: harmonize helicalextrusion and helicalextrusion2
The now only helicalextrusion is the one working on a face, not a wire,
and allows extrusions of "faces with holes". This is required for the
internal gears but also allows e.g. center holes for ordinary gears as
well (not in this commit).
2021-09-29 10:17:11 +02:00
Jonas Bähr
055aab2e07 Build the helical internal gear directly, not via boolen cut
The original, now replaced, way had the benefit of reusing the existing
helicalextrusion method, but the following boolean cut operation was a)
slow and b) suffered from co-planar issues. The new way extrudes the
face directly which does not have these shortcommings.
Unfortunaly, we had to use a different API: the original one is simple
and straight forward, gives no easy way to access the end of the sweep.
Collecting the wires manually, filtering on the position of all vertexes,
unvailed tolerances heigher then 0.1, so that a face created from it was
not planar and prevented ceating a valid solid.
The now used API requires more manual work in the initial setup, but the
end wires for construction the top face are directly accesible and the
tolerances are below 0.001 so we can create a planar face and valid solid.
This new way also much faster (1.6 sec vs. 5.0 sec on my machine
using default gear parameters on a double helix, naively measured via
`time.perf_counter()`).
Currently, the new helical extusion method is only used for the internal
gear. Migrating the other usages will be done as a separate commit.
2021-09-29 10:15:10 +02:00
Jonas Bähr
c40bae47b3 Give the internal involute gear its own icon 2021-09-29 10:14:41 +02:00
Jonas Bähr
6daa3114be Add an "internal involute gear" command and feature
This reuses the same (external) tooth profile from pygear but swap some
parameters to make the resulting gear internal.
This prototype is mostly a copy of the external involute gear. Eventually
we should refactor this to share more code but this has to be coordinated
with the megagrant endevour. Otherwise merging becomes a nightmere.
Note that in contrast to the involute rack I choose to base the
"thickness" on the pitch diameter, not the root diameter. This has the
benefit of keeping the outside diameter stable when e.g. adjusting the
clearance. And setting the outside diameter directly could result in an
invalid shape when chaning the numnber of teeth.
The default head value of "-0.4" is choosen to match the invernal gear
profile from the PartDewign WB.
2021-09-29 10:14:26 +02:00
looooo
4e16fd2560 merge error 2021-07-21 14:54:37 +02:00
looooo
56acace6ef Merge branch 'feature/fix-partdesign-tip' into megagrants 2021-07-21 14:39:43 +02:00
Jonas Bähr
37b99b119d Fix crown gear preview mode in PartDesign Bodies
Previously, the `preview_mode` of the crown gear returned a compound of
the base and the cut-outs. This caused problems in PD::Bodies where a
single solid is requried.
The solution in this commit changes the preview_mode to only output the
base, not generating the cutout shapes at all. This is consistent with
the involute gears having "simple=true" and saves again 0.5 Seconds
processsing time on my system using defaults (15 teeth, 4 loft profiles).

In addition, "preview = false" is also speed up by first collecting all
cut-outs, and then passing them all at once to a single cut operation.
This reduced the cutting time from 3.0 Seconds to 2.2 Seconds here.
So preview now generats the shape immediately (0.0008s vs 0.5s) and the
actual crown is generated in 2.7s instead of 3.5s (again, using the
defaut parameters, measued via Python's time.perf_counter).
2021-07-08 23:23:36 +02:00
Jonas Bähr
9983f5ee61 Extend the "additiveness" to all gears when used in PD:Bodies
There are still some issues when the generated Shape is not a solid,
e.g. in the preview mode of the Crown Gear.
2021-07-07 01:09:16 +02:00
Jonas Bähr
bd6c2107ee First proof of concept of "additive gears" in PartDesign bodies
In this PoC only the involute gears work, and there is still a lot of
cleanup pending.
What does work, however, is that those gears now play nicely with
PartDesign's concept of stacking features onto each other, i.e. that the
result of a feature is the fusion of all previous ones.

Special Thanks goes to DeepSOIC for his tutorial in the forum at [1] as
well as this Part-o-Matic which showed me how this works in real live [2]
[1]: https://forum.freecadweb.org/viewtopic.php?f=22&t=21097#p163340
[2]: https://github.com/DeepSOIC/Part-o-magic/blob/master/PartOMagic/Features/PartDesign/PDShapeFeature.py
2021-07-06 00:56:22 +02:00
luz paz
9225b5b4ed Fix source comment typo 2021-07-04 21:54:27 +02:00
looooo
175b746a7a foot -> root, bevelgear add warning for max height 2021-07-04 21:54:27 +02:00
looooo
1f70e28ac1 root fillet 2021-07-04 21:54:27 +02:00
looooo
403505ae95 add cycloide gear foot- and headfillet 2021-07-04 21:54:27 +02:00
looooo
ae1c272c42 use correct license headers 2021-07-04 21:54:27 +02:00
looooo
9ddd493b82 add head parameter for cycloide gear 2021-07-04 21:54:27 +02:00
looooo
3c9f6f0c6b add property for fillets (head, foot) 2021-07-04 21:54:27 +02:00
looooo
8174345a38 refactor 2021-07-04 21:54:27 +02:00
looooo
3dedcf3e21 add todo list 2021-07-04 21:54:27 +02:00
44 changed files with 6481 additions and 1634 deletions

23
.github/workflows/pylint.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Pylint
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')

View File

@@ -1,5 +1,5 @@
## A Gear module for FreeCAD
[![Liberapay](http://img.shields.io/liberapay/patrons/looooo.svg?logo=liberapay)](https://liberapay.com/looooo/donate) [![Total alerts](https://img.shields.io/lgtm/alerts/g/looooo/freecad.gears.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/looooo/freecad.gears/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/looooo/freecad.gears.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/looooo/freecad.gears/context:python)
[![Liberapay](http://img.shields.io/liberapay/patrons/looooo.svg?logo=liberapay)](https://liberapay.com/looooo/donate)
## Requirements
FreeCAD > v0.16
@@ -26,6 +26,10 @@ __python > 3 (for python2 use branch py2)__
![cycloid-gear](examples/cycloid-gear.png)
### Cycloid Rack
![cycloid-rack](examples/cycloid-rack.png)
### Spherical Involute Bevel-Gear
* Spiral

6
TODO.md Normal file
View File

@@ -0,0 +1,6 @@
#TODO:
## refactoring
- [ ] fp.gear.z -> fp.gear.num_teeth
- [ ] fp.teeth -> fp.gear.num_teeth

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
examples/cycloid-rack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

BIN
examples/gear-shaft.FCStd Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
examples/worm_cutting_tool/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -0,0 +1,434 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5e24589a-461d-46b5-8141-37f948dcf4dc",
"metadata": {},
"source": [
"# cutting tool for a worm gear\n",
"\n",
"1. idea 1: \n",
"\n",
"<img src=\"../../docs/computing a profile_from_a_given_rack.jpg\">"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "7eacf041-aa83-49e2-9cbe-066f177197f6",
"metadata": {},
"outputs": [],
"source": [
"import sympy as sp\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "980417d0-c79d-4501-a7cc-9725b3bbea83",
"metadata": {},
"outputs": [],
"source": [
"def symbolic_transformation(angle, axis, translation=np.array([0., 0., 0.])):\n",
" \"\"\"\n",
" see http://en.wikipedia.org/wiki/SO%284%29#The_Euler.E2.80.93Rodrigues_formula_for_3D_rotations\n",
" sympy enabled transformation\n",
" angle: angle of rotation\n",
" axis: the axis of the rotation\n",
" translation: translation of transformation\n",
" \"\"\"\n",
" assert len(axis) == 3\n",
" a = sp.cos(angle / 2)\n",
" axis_normalized = axis / sp.sqrt(axis.dot(axis))\n",
" (b, c, d) = -axis_normalized * sp.sin(angle / 2)\n",
" mat = sp.Matrix(\n",
" [\n",
" [\n",
" a**2 + b**2 - c**2 - d**2,\n",
" 2 * (b * c - a * d),\n",
" 2 * (b * d + a * c),\n",
" translation[0],\n",
" ],\n",
" [\n",
" 2 * (b * c + a * d),\n",
" a**2 + c**2 - b**2 - d**2,\n",
" 2 * (c * d - a * b),\n",
" translation[1],\n",
" ],\n",
" [\n",
" 2 * (b * d - a * c),\n",
" 2 * (c * d + a * b),\n",
" a**2 + d**2 - b**2 - c**2,\n",
" translation[2],\n",
" ],\n",
" [0.0, 0.0, 0.0, 1.0],\n",
" ]\n",
" )\n",
" return sp.simplify(mat)\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5852aa56-e66b-4f3c-a50d-0cb4cb21abd2",
"metadata": {},
"outputs": [],
"source": [
"t = sp.Symbol(\"t\")\n",
"T1 = symbolic_transformation(np.pi / 2.,\n",
" np.array([1., 0., 0.]),\n",
" np.array([12.5,0., 1.15]))\n",
"T2 = symbolic_transformation(-t / 7.5,\n",
" np.array([0., 0., 1.]),\n",
" np.array([0., 0., 0.]))\n",
"T3 = symbolic_transformation(0.,\n",
" np.array([1., 0., 0.]),\n",
" np.array([0., 0., t]))\n",
"\n",
"T = sp.nsimplify(T2.inv() @ T1.inv() @ T3, tolerance=10e-16)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "7c9837b8-caf7-4447-bf0d-eba9085197a5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0. , 0. , -0.13333333, 0.15333333],\n",
" [-0.13333333, 0. , 0. , 0.66666667],\n",
" [ 0. , 0. , 0. , 0. ],\n",
" [ 0. , 0. , 0. , 0. ]])"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"T_fn = sp.lambdify(t, T)\n",
"dT_fn = sp.lambdify(t, T.diff(t))\n",
"dT_fn(0.)"
]
},
{
"cell_type": "markdown",
"id": "e4354a36-409a-40a6-8f4f-bb5a6c84f3b7",
"metadata": {},
"source": [
"Diese Methode funktioniert nicht, weil die Bedingung zur Bestimmung des Kontaktpunktes falsch ist.\n",
"Eine Bedingung für die generierung einer konstanten Übersetzung ist, dass die Normale auf die Kontaktfläche (Zahnstange) immer durch den Punkt p (Eingriffspunkt für Ersatzzahnrad (Zylinder) und Ersatzzahnstange (Quader) gehen muss.\n",
"Gesucht sind also Punkte auf der Fläche S welche verbunden mit P normal auf die Fläche stehen. Dies kann auch als minimaler Abstand von P zur Fläche gesehen werden.\n",
"\n",
"für jedes u: min(norm(S(u,v)-P)) -> d(norm(S(u,v)-P)/dv = 0\n",
"die Änderung des Abstands ist 0 -> die Gerade steht normal auf die Fläche\n"
]
},
{
"cell_type": "markdown",
"id": "43f00b65-c05c-4734-8c5a-e7845a6f4dff",
"metadata": {},
"source": [
"## Vorgehensweise\n",
"\n",
"1. approximate the surface by a BSplineSurface\n",
"\n",
"- Erstellen eines \"Cross-Sektion\" objekts aus dem \"Werkzeug\"\n",
"\n",
"<img src=\"cross_section.png\"> \n",
"\n",
"- Draft downgrade um Kanten zu bekommen\n",
"das Cross-section Objekt beinhaltet nicht die anfangs und end Kanten. Diese müssen zusätzlich vom Werkzeug extrahiert werden\n",
"\n",
"- Part Loft zum erstellen einer schönen BSplinefläche\n",
"\n",
"<img src=\"loft_bspline_flaechen.png\"> \n",
"\n",
"3. Loft -> Surface\n",
"\n",
"```python\n",
"# select the cutting faces\n",
"face_1 = App.ActiveDocument.Loft.Shape.Faces[0].copy()\n",
"face_2 = App.ActiveDocument.Loft001.Shape.Faces[0].copy()\n",
"\n",
"# compute the contact curve:\n",
"bsp_1 = face_1.Surface\n",
"bsp_2 = face_2.Surface\n",
"\n",
"```\n",
"\n",
"4. Minimierung des Abstands zum \"Pitch-Punkt\"\n",
"\n",
"```python\n",
"import scipy as scp\n",
"point = App.Vector(5., 0., 1.15 - time) \n",
"xyz_1 = []\n",
"for v in np.linspace(0, 1, 5):\n",
" def dist_1(u):\n",
" distance = bsp_1.value(u, v) - point\n",
" return distance.x ** 2 + distance.z ** 2\n",
" u_1 = scp.optimize.minimize(dist_1, 0.5, tol=1e-6).x[0]\n",
" xyz_1.append(bsp_1.value(u_1, v))\n",
"```\n",
"\n",
"5. erstellen einer B-Spline Kurve welche durch die Kinematik T transformiert wird\n",
"\n",
"```python\n",
"c_1 = Part.BSplineCurve()\n",
"c_1.interpolate(Points=xyz_1)\n",
"c_1 = c_1.toShape()\n",
"\n",
"Part.show(c_1.transformShape(T))\n",
"```\n",
"\n",
"6. Loft anwenden auf die erstellten BSpline Kurven\n",
"\n",
"<img src=\"loft_of_generated_bsplines.png\">\n",
"\n",
"7. Array für das Zahnrad\n",
"\n",
"<img src=\"gear_assembly.png\">"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "cfd8026b-5a84-4882-a1de-63580776a579",
"metadata": {},
"outputs": [],
"source": [
"import sympy as sp\n",
"t, x, z = sp.symbols([\"t\", \"x\", \"z\"], real=True)\n",
"s, alpha, n_t, y = sp.symbols([\"s\", \"alpha\", \"n_t\", \"y\"], real=True, positiv=True)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "65bb90d7-0f5b-410e-9a3a-0f9953a4d846",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(\\frac{n_{t} t}{\\pi} \\right)} & \\sin{\\left(\\frac{n_{t} t}{\\pi} \\right)} & 0 & 0\\\\- \\sin{\\left(\\frac{n_{t} t}{\\pi} \\right)} & \\cos{\\left(\\frac{n_{t} t}{\\pi} \\right)} & 0 & 0\\\\0 & 0 & 1 & n_{t} t\\\\0 & 0 & 0 & 1.0\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[ cos(n_t*t/pi), sin(n_t*t/pi), 0, 0],\n",
"[-sin(n_t*t/pi), cos(n_t*t/pi), 0, 0],\n",
"[ 0, 0, 1, n_t*t],\n",
"[ 0, 0, 0, 1.0]])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"T_spiral = symbolic_transformation(t * n_t / sp.pi, np.array([0, 0, 1]), np.array([0, 0, t * n_t]))\n",
"T_spiral"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "0f305b8b-0fb5-4b71-80b6-9a2b43b59e26",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}0\\\\s \\cos{\\left(\\alpha \\right)}\\\\s \\sin{\\left(\\alpha \\right)}\\\\1\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[ 0],\n",
"[s*cos(alpha)],\n",
"[s*sin(alpha)],\n",
"[ 1]])"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"l = sp.Matrix([0, s * sp.cos(alpha), s * sp.sin(alpha), 1])\n",
"l"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "da3c8575-99ad-4258-8734-c165ea65b014",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}s \\sin{\\left(\\frac{n_{t} t}{\\pi} \\right)} \\cos{\\left(\\alpha \\right)}\\\\s \\cos{\\left(\\alpha \\right)} \\cos{\\left(\\frac{n_{t} t}{\\pi} \\right)}\\\\n_{t} t + s \\sin{\\left(\\alpha \\right)}\\\\1.0\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[s*sin(n_t*t/pi)*cos(alpha)],\n",
"[s*cos(alpha)*cos(n_t*t/pi)],\n",
"[ n_t*t + s*sin(alpha)],\n",
"[ 1.0]])"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spiral = (T_spiral @ l)\n",
"spiral"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "e47eb83b-6e89-4246-a82a-bd5629aedc2a",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\frac{\\pi \\operatorname{asin}{\\left(\\frac{x}{s \\cos{\\left(\\alpha \\right)}} \\right)}}{n_{t}}$"
],
"text/plain": [
"pi*asin(x/(s*cos(alpha)))/n_t"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x_cross_section = sp.simplify(sp.solve(spiral[0] - x, t)[1])\n",
"x_cross_section # parameter s"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "c2954b39-eea0-4e27-987f-07a5d0dedaad",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}x\\\\s \\sqrt{1 - \\frac{x^{2}}{s^{2} \\cos^{2}{\\left(\\alpha \\right)}}} \\cos{\\left(\\alpha \\right)}\\\\s \\sin{\\left(\\alpha \\right)} + \\pi \\operatorname{asin}{\\left(\\frac{x}{s \\cos{\\left(\\alpha \\right)}} \\right)}\\\\1.0\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[ x],\n",
"[s*sqrt(1 - x**2/(s**2*cos(alpha)**2))*cos(alpha)],\n",
"[ s*sin(alpha) + pi*asin(x/(s*cos(alpha)))],\n",
"[ 1.0]])"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spiral_x = sp.simplify(spiral.subs({t: x_cross_section}))\n",
"spiral_x"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "268c6302-4e7d-45e0-be28-1b64cf8b766e",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\frac{\\sqrt{x^{2} + y^{2}}}{\\cos{\\left(\\alpha \\right)}}$"
],
"text/plain": [
"sqrt(x**2 + y**2)/cos(alpha)"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"y_cross_section = sp.simplify(sp.solve(spiral_x[1]- y, s)[0])\n",
"y_cross_section"
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "7284bd6a-b47b-483c-8e9f-79fcaecce5e3",
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}x\\\\\\left|{y}\\right|\\\\\\sqrt{x^{2} + y^{2}} \\tan{\\left(\\alpha \\right)} + \\pi \\operatorname{asin}{\\left(\\frac{x}{\\sqrt{x^{2} + y^{2}}} \\right)}\\\\1.0\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[ x],\n",
"[ Abs(y)],\n",
"[sqrt(x**2 + y**2)*tan(alpha) + pi*asin(x/sqrt(x**2 + y**2))],\n",
"[ 1.0]])"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"spiral_xy = sp.simplify(spiral_x.subs({s: y_cross_section}))\n",
"spiral_xy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "82e6880a-7240-44a4-a346-4fc45e24971b",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,23 +1,21 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import pygears
__version__ = pygears.__version__

672
freecad/gears/basegear.py Normal file
View File

@@ -0,0 +1,672 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCAD as App
import Part
import numpy as np
import math
from pygears import __version__
from pygears.involute_tooth import InvoluteTooth, InvoluteRack
from pygears.cycloid_tooth import CycloidTooth
from pygears.bevel_tooth import BevelTooth
from pygears._functions import (
rotation3D,
rotation,
reflection,
arc_from_points_and_center,
)
def fcvec(x):
if len(x) == 2:
return App.Vector(x[0], x[1], 0)
else:
return App.Vector(x[0], x[1], x[2])
class ViewProviderGear(object):
def __init__(self, obj, icon_fn=None):
# Set this object to the proxy object of the actual view provider
obj.Proxy = self
self._check_attr()
dirname = os.path.dirname(__file__)
self.icon_fn = icon_fn or os.path.join(dirname, "icons", "involutegear.svg")
def _check_attr(self):
"""Check for missing attributes."""
if not hasattr(self, "icon_fn"):
setattr(
self,
"icon_fn",
os.path.join(os.path.dirname(__file__), "icons", "involutegear.svg"),
)
def attach(self, vobj):
self.vobj = vobj
def getIcon(self):
self._check_attr()
return self.icon_fn
if sys.version_info[0] == 3 and sys.version_info[1] >= 11:
def dumps(self):
self._check_attr()
return {"icon_fn": self.icon_fn}
def loads(self, state):
if state and "icon_fn" in state:
self.icon_fn = state["icon_fn"]
else:
def __getstate__(self):
self._check_attr()
return {"icon_fn": self.icon_fn}
def __setstate__(self, state):
if state and "icon_fn" in state:
self.icon_fn = state["icon_fn"]
class BaseGear(object):
def __init__(self, obj):
obj.addProperty(
"App::PropertyString", "version", "version", "freecad.gears-version", 1
)
obj.version = __version__
self.make_attachable(obj)
def make_attachable(self, obj):
# Needed to make this object "attachable",
# aka able to attach parameterically to other objects
# cf. https://wiki.freecadweb.org/Scripted_objects_with_attachment
if int(App.Version()[1]) >= 19:
obj.addExtension("Part::AttachExtensionPython")
else:
obj.addExtension("Part::AttachExtensionPython", obj)
# unveil the "Placement" property, which seems hidden by default in PartDesign
obj.setEditorMode("Placement", 0) # non-readonly non-hidden
def execute(self, fp):
# checksbackwardcompatibility:
if not hasattr(fp, "positionBySupport"):
self.make_attachable(fp)
fp.positionBySupport()
gear_shape = self.generate_gear_shape(fp)
if hasattr(fp, "BaseFeature") and fp.BaseFeature != None:
# we're inside a PartDesign Body, thus need to fuse with the base feature
gear_shape.Placement = (
fp.Placement
) # ensure the gear is placed correctly before fusing
result_shape = fp.BaseFeature.Shape.fuse(gear_shape)
result_shape.transformShape(
fp.Placement.inverse().toMatrix(), True
) # account for setting fp.Shape below moves the shape to fp.Placement, ignoring its previous placement
fp.Shape = result_shape
else:
fp.Shape = gear_shape
def generate_gear_shape(self, fp):
"""
This method has to return the TopoShape of the gear.
"""
raise NotImplementedError("generate_gear_shape not implemented")
if sys.version_info[0] == 3 and sys.version_info[1] >= 11:
def loads(self, state):
pass
def dumps(self):
pass
else:
def __setstate__(self, state):
pass
def __getstate__(self):
pass
class LanternGear(BaseGear):
def __init__(self, obj):
super(LanternGear, self).__init__(obj)
obj.addProperty(
"App::PropertyInteger", "teeth", "gear_parameter", "number of teeth"
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty(
"App::PropertyLength",
"bolt_radius",
"base",
"the bolt radius of the rack/chain",
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyInteger",
"num_profiles",
"accuracy",
"number of profiles used for loft",
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.teeth = 15
obj.module = "1. mm"
obj.bolt_radius = "1 mm"
obj.height = "5. mm"
obj.num_profiles = 10
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
m = fp.module.Value
teeth = fp.teeth
r_r = fp.bolt_radius.Value
r_0 = m * teeth / 2
r_max = r_0 + r_r + fp.head * m
phi_max = (r_r + np.sqrt(r_max**2 - r_0**2)) / r_0
def find_phi_min(phi_min):
return r_0 * (
phi_min**2 * r_0
- 2 * phi_min * r_0 * np.sin(phi_min)
- 2 * phi_min * r_r
- 2 * r_0 * np.cos(phi_min)
+ 2 * r_0
+ 2 * r_r * np.sin(phi_min)
)
try:
import scipy.optimize
phi_min = scipy.optimize.root(
find_phi_min, (phi_max + r_r / r_0 * 4) / 5
).x[0] # , r_r / r_0, phi_max)
except ImportError:
App.Console.PrintWarning(
"scipy not available. Can't compute numerical root. Leads to a wrong bolt-radius"
)
phi_min = r_r / r_0
# phi_min = 0 # r_r / r_0
phi = np.linspace(phi_min, phi_max, fp.num_profiles)
x = r_0 * (np.cos(phi) + phi * np.sin(phi)) - r_r * np.sin(phi)
y = r_0 * (np.sin(phi) - phi * np.cos(phi)) + r_r * np.cos(phi)
xy1 = np.array([x, y]).T
p_1 = xy1[0]
p_1_end = xy1[-1]
bsp_1 = Part.BSplineCurve()
bsp_1.interpolate(list(map(fcvec, xy1)))
w_1 = bsp_1.toShape()
xy2 = xy1 * np.array([1.0, -1.0])
p_2 = xy2[0]
p_2_end = xy2[-1]
bsp_2 = Part.BSplineCurve()
bsp_2.interpolate(list(map(fcvec, xy2)))
w_2 = bsp_2.toShape()
p_12 = np.array([r_0 - r_r, 0.0])
arc = Part.Arc(
App.Vector(*p_1, 0.0), App.Vector(*p_12, 0.0), App.Vector(*p_2, 0.0)
).toShape()
rot = rotation(-np.pi * 2 / teeth)
p_3 = rot(np.array([p_2_end]))[0]
# l = Part.LineSegment(fcvec(p_1_end), fcvec(p_3)).toShape()
l = part_arc_from_points_and_center(
p_1_end, p_3, np.array([0.0, 0.0])
).toShape()
w = Part.Wire([w_2, arc, w_1, l])
wires = [w]
rot = App.Matrix()
for _ in range(teeth - 1):
rot.rotateZ(np.pi * 2 / teeth)
wires.append(w.transformGeometry(rot))
wi = Part.Wire(wires)
if fp.height.Value == 0:
return wi
else:
return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))
class HypoCycloidGear(BaseGear):
"""parameters:
pressure_angle: pressureangle, 10-30°
pitch_angle: cone angle, 0 < pitch_angle < pi/4
"""
def __init__(self, obj):
super(HypoCycloidGear, self).__init__(obj)
obj.addProperty(
"App::PropertyFloat",
"pin_circle_radius",
"gear_parameter",
"Pin ball circle radius(overrides Tooth Pitch",
)
obj.addProperty(
"App::PropertyFloat", "roller_diameter", "gear_parameter", "Roller Diameter"
)
obj.addProperty(
"App::PropertyFloat", "eccentricity", "gear_parameter", "Eccentricity"
)
obj.addProperty(
"App::PropertyAngle",
"pressure_angle_lim",
"gear_parameter",
"Pressure angle limit",
)
obj.addProperty(
"App::PropertyFloat",
"pressure_angle_offset",
"gear_parameter",
"Offset in pressure angle",
)
obj.addProperty(
"App::PropertyInteger",
"teeth_number",
"gear_parameter",
"Number of teeth in Cam",
)
obj.addProperty(
"App::PropertyInteger",
"segment_count",
"gear_parameter",
"Number of points used for spline interpolation",
)
obj.addProperty(
"App::PropertyLength",
"hole_radius",
"gear_parameter",
"Center hole's radius",
)
obj.addProperty(
"App::PropertyBool", "show_pins", "Pins", "Create pins in place"
)
obj.addProperty("App::PropertyLength", "pin_height", "Pins", "height")
obj.addProperty(
"App::PropertyBool",
"center_pins",
"Pins",
"Center pin Z axis to generated disks",
)
obj.addProperty(
"App::PropertyBool", "show_disk0", "Disks", "Show main cam disk"
)
obj.addProperty(
"App::PropertyBool",
"show_disk1",
"Disks",
"Show another reversed cam disk on top",
)
obj.addProperty("App::PropertyLength", "disk_height", "Disks", "height")
obj.pin_circle_radius = 66
obj.roller_diameter = 3
obj.eccentricity = 1.5
obj.pressure_angle_lim = "50.0 deg"
obj.pressure_angle_offset = 0.01
obj.teeth_number = 42
obj.segment_count = 42
obj.hole_radius = "30. mm"
obj.show_pins = True
obj.pin_height = "20. mm"
obj.center_pins = True
obj.show_disk0 = True
obj.show_disk1 = True
obj.disk_height = "10. mm"
self.obj = obj
obj.Proxy = self
def to_polar(self, x, y):
return (x**2 + y**2) ** 0.5, math.atan2(y, x)
def to_rect(self, r, a):
return r * math.cos(a), r * math.sin(a)
def calcyp(self, p, a, e, n):
return math.atan(math.sin(n * a) / (math.cos(n * a) + (n * p) / (e * (n + 1))))
def calc_x(self, p, d, e, n, a):
return (
(n * p) * math.cos(a)
+ e * math.cos((n + 1) * a)
- d / 2 * math.cos(self.calcyp(p, a, e, n) + a)
)
def calc_y(self, p, d, e, n, a):
return (
(n * p) * math.sin(a)
+ e * math.sin((n + 1) * a)
- d / 2 * math.sin(self.calcyp(p, a, e, n) + a)
)
def calc_pressure_angle(self, p, d, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
pp = rg * (ex**2 + 1 - 2 * ex * math.cos(a)) ** 0.5 - d / 2
return math.asin((r3 * math.cos(a) - rg) / (pp + d / 2)) * 180 / math.pi
def calc_pressure_limit(self, p, d, e, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
q = (r3**2 + rg**2 - 2 * r3 * rg * math.cos(a)) ** 0.5
x = rg - e + (q - d / 2) * (r3 * math.cos(a) - rg) / q
y = (q - d / 2) * r3 * math.sin(a) / q
return (x**2 + y**2) ** 0.5
def check_limit(self, x, y, maxrad, minrad, offset):
r, a = self.to_polar(x, y)
if (r > maxrad) or (r < minrad):
r = r - offset
x, y = self.to_rect(r, a)
return x, y
def generate_gear_shape(self, fp):
b = fp.pin_circle_radius
d = fp.roller_diameter
e = fp.eccentricity
n = fp.teeth_number
p = b / n
s = fp.segment_count
ang = fp.pressure_angle_lim
c = fp.pressure_angle_offset
q = 2 * math.pi / float(s)
# Find the pressure angle limit circles
minAngle = -1.0
maxAngle = -1.0
for i in range(0, 180):
x = self.calc_pressure_angle(p, d, n, i * math.pi / 180.0)
if (x < ang) and (minAngle < 0):
minAngle = float(i)
if (x < -ang) and (maxAngle < 0):
maxAngle = float(i - 1)
minRadius = self.calc_pressure_limit(p, d, e, n, minAngle * math.pi / 180.0)
maxRadius = self.calc_pressure_limit(p, d, e, n, maxAngle * math.pi / 180.0)
# unused
# Part.Wire(Part.makeCircle(minRadius,App.Vector(-e, 0, 0)))
# Part.Wire(Part.makeCircle(maxRadius,App.Vector(-e, 0, 0)))
App.Console.PrintMessage("Generating cam disk\r\n")
# generate the cam profile - note: shifted in -x by eccentricicy amount
i = 0
x = self.calc_x(p, d, e, n, q * i / float(n))
y = self.calc_y(p, d, e, n, q * i / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points = [App.Vector(x - e, y, 0)]
for i in range(0, s):
x = self.calc_x(p, d, e, n, q * (i + 1) / n)
y = self.calc_y(p, d, e, n, q * (i + 1) / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points.append([x - e, y, 0])
wi = make_bspline_wire([points])
wires = []
mat = App.Matrix()
mat.move(App.Vector(e, 0.0, 0.0))
mat.rotateZ(2 * np.pi / n)
mat.move(App.Vector(-e, 0.0, 0.0))
for _ in range(n):
wi = wi.transformGeometry(mat)
wires.append(wi)
cam = Part.Face(Part.Wire(wires))
# add a circle in the center of the cam
if fp.hole_radius.Value:
centerCircle = Part.Face(
Part.Wire(Part.makeCircle(fp.hole_radius.Value, App.Vector(-e, 0, 0)))
)
cam = cam.cut(centerCircle)
to_be_fused = []
if fp.show_disk0 == True:
if fp.disk_height.Value == 0:
to_be_fused.append(cam)
else:
to_be_fused.append(cam.extrude(App.Vector(0, 0, fp.disk_height.Value)))
# secondary cam disk
if fp.show_disk1 == True:
App.Console.PrintMessage("Generating secondary cam disk\r\n")
second_cam = cam.copy()
mat = App.Matrix()
mat.rotateZ(np.pi)
mat.move(App.Vector(-e, 0, 0))
if n % 2 == 0:
mat.rotateZ(np.pi / n)
mat.move(App.Vector(e, 0, 0))
second_cam = second_cam.transformGeometry(mat)
if fp.disk_height.Value == 0:
to_be_fused.append(second_cam)
else:
to_be_fused.append(
second_cam.extrude(App.Vector(0, 0, -fp.disk_height.Value))
)
# pins
if fp.show_pins == True:
App.Console.PrintMessage("Generating pins\r\n")
pins = []
for i in range(0, n + 1):
x = p * n * math.cos(2 * math.pi / (n + 1) * i)
y = p * n * math.sin(2 * math.pi / (n + 1) * i)
pins.append(Part.Wire(Part.makeCircle(d / 2, App.Vector(x, y, 0))))
pins = Part.Face(pins)
z_offset = -fp.pin_height.Value / 2
if fp.center_pins == True:
if fp.show_disk0 == True and fp.show_disk1 == False:
z_offset += fp.disk_height.Value / 2
elif fp.show_disk0 == False and fp.show_disk1 == True:
z_offset += -fp.disk_height.Value / 2
# extrude
if z_offset != 0:
pins.translate(App.Vector(0, 0, z_offset))
if fp.pin_height != 0:
pins = pins.extrude(App.Vector(0, 0, fp.pin_height.Value))
to_be_fused.append(pins)
if to_be_fused:
return Part.makeCompound(to_be_fused)
def part_arc_from_points_and_center(p_1, p_2, m):
p_1, p_12, p_2 = arc_from_points_and_center(p_1, p_2, m)
return Part.Arc(
App.Vector(*p_1, 0.0), App.Vector(*p_12, 0.0), App.Vector(*p_2, 0.0)
)
def helicalextrusion(face, height, angle, double_helix=False):
"""
A helical extrusion using the BRepOffsetAPI
face -- the face to extrude (may contain holes, i.e. more then one wires)
height -- the height of the extrusion, normal to the face
angle -- the twist angle of the extrusion in radians
returns a solid
"""
pitch = height * 2 * np.pi / abs(angle)
radius = 10.0 # as we are only interested in the "twist", we take an arbitrary constant here
cone_angle = 0
direction = bool(angle < 0)
if double_helix:
spine = Part.makeHelix(pitch, height / 2.0, radius, cone_angle, direction)
spine.translate(App.Vector(0, 0, height / 2.0))
face = face.translated(
App.Vector(0, 0, height / 2.0)
) # don't transform our argument
else:
spine = Part.makeHelix(pitch, height, radius, cone_angle, direction)
def make_pipe(path, profile):
"""
returns (shell, last_wire)
"""
mkPS = Part.BRepOffsetAPI.MakePipeShell(path)
mkPS.setFrenetMode(
True
) # otherwise, the profile's normal would follow the path
mkPS.add(profile, False, False)
mkPS.build()
return (mkPS.shape(), mkPS.lastShape())
shell_faces = []
top_wires = []
for wire in face.Wires:
pipe_shell, top_wire = make_pipe(spine, wire)
shell_faces.extend(pipe_shell.Faces)
top_wires.append(top_wire)
top_face = Part.Face(top_wires)
shell_faces.append(top_face)
if double_helix:
origin = App.Vector(0, 0, height / 2.0)
xy_normal = App.Vector(0, 0, 1)
mirror_xy = lambda f: f.mirror(origin, xy_normal)
bottom_faces = list(map(mirror_xy, shell_faces))
shell_faces.extend(bottom_faces)
# TODO: why the heck is makeShell from this empty after mirroring?
# ... and why the heck does it work when making an intermediate compound???
hacky_intermediate_compound = Part.makeCompound(shell_faces)
shell_faces = hacky_intermediate_compound.Faces
else:
shell_faces.append(face) # the bottom is what we extruded
shell = Part.makeShell(shell_faces)
# shell.sewShape() # fill gaps that may result from accumulated tolerances. Needed?
# shell = shell.removeSplitter() # refine. Needed?
return Part.makeSolid(shell)
def make_face(edge1, edge2):
v1, v2 = edge1.Vertexes
v3, v4 = edge2.Vertexes
e1 = Part.Wire(edge1)
e2 = Part.LineSegment(v1.Point, v3.Point).toShape().Edges[0]
e3 = edge2
e4 = Part.LineSegment(v4.Point, v2.Point).toShape().Edges[0]
w = Part.Wire([e3, e4, e1, e2])
return Part.Face(w)
def make_bspline_wire(pts):
wi = []
for i in pts:
out = Part.BSplineCurve()
out.interpolate(list(map(fcvec, i)))
wi.append(out.toShape())
return Part.Wire(wi)
def points_to_wire(pts):
wire = []
for i in pts:
if len(i) == 2:
# straight edge
out = Part.LineSegment(*list(map(fcvec, i)))
else:
out = Part.BSplineCurve()
out.interpolate(list(map(fcvec, i)))
wire.append(out.toShape())
return Part.Wire(wire)
def rotate_tooth(base_tooth, num_teeth):
rot = App.Matrix()
rot.rotateZ(2 * np.pi / num_teeth)
flat_shape = [base_tooth]
for t in range(num_teeth - 1):
flat_shape.append(flat_shape[-1].transformGeometry(rot))
return Part.Wire(flat_shape)
def fillet_between_edges(edge_1, edge_2, radius):
# assuming edges are in a plane
# extracting vertices
try:
from Part import ChFi2d
except ImportError:
App.Console.PrintWarning(
"Your freecad version has no python bindings for 2d-fillets"
)
return [edge_1, edge_2]
api = ChFi2d.FilletAPI()
p1 = edge_1.valueAt(edge_1.FirstParameter)
p2 = edge_1.valueAt(edge_1.LastParameter)
p3 = edge_2.valueAt(edge_2.FirstParameter)
p4 = edge_2.valueAt(edge_2.LastParameter)
t1 = p2 - p1
t2 = p4 - p3
n = t1.cross(t2)
pln = Part.Plane(edge_1.valueAt(edge_1.FirstParameter), n)
api.init(edge_1, edge_2, pln)
if api.perform(radius) > 0:
p0 = (p2 + p3) / 2
fillet, e1, e2 = api.result(p0)
return Part.Wire([e1, fillet, e2]).Edges
else:
return None
def insert_fillet(edges, pos, radius):
assert pos < (len(edges) - 1)
e1 = edges[pos]
e2 = edges[pos + 1]
if radius > 0:
fillet_edges = fillet_between_edges(e1, e2, radius)
if not fillet_edges:
raise RuntimeError("fillet not possible")
else:
fillet_edges = [e1, None, e2]
output_edges = []
for i, edge in enumerate(edges):
if i == pos:
output_edges += fillet_edges
elif i == (pos + 1):
pass
else:
output_edges.append(edge)
return output_edges

206
freecad/gears/bevelgear.py Normal file
View File

@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation3D
from .basegear import BaseGear, fcvec, make_bspline_wire
class BevelGear(BaseGear):
"""parameters:
pressure_angle: pressureangle, 10-30°
pitch_angle: cone angle, 0 < pitch_angle < pi/4
"""
def __init__(self, obj):
super(BevelGear, self).__init__(obj)
self.bevel_tooth = BevelTooth()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyAngle", "pitch_angle", "involute", "pitch_angle")
obj.addProperty(
"App::PropertyAngle",
"pressure_angle",
"involute_parameter",
"pressure_angle",
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"precision",
"number of points for spline",
)
obj.addProperty(
"App::PropertyBool",
"reset_origin",
"base",
"if value is true the gears outer face will match the z=0 plane",
)
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty("App::PropertyPythonObject", "gear", "base", "test")
obj.addProperty(
"App::PropertyAngle", "beta", "helical", "angle used for spiral bevel-gears"
)
obj.addProperty("App::PropertyLength", "dw", "computed", "The pitch diameter.")
obj.setExpression(
"dw", "teeth * module"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"dw", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.gear = self.bevel_tooth
obj.module = "1. mm"
obj.teeth = 15
obj.pressure_angle = "20. deg"
obj.pitch_angle = "45. deg"
obj.height = "5. mm"
obj.numpoints = 6
obj.backlash = "0.00 mm"
obj.clearance = 0.1
obj.beta = "0 deg"
obj.reset_origin = True
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
fp.gear.z = fp.teeth
fp.gear.module = fp.module.Value
fp.gear.pressure_angle = (90 - fp.pressure_angle.Value) * np.pi / 180.0
fp.gear.pitch_angle = fp.pitch_angle.Value * np.pi / 180
max_height = fp.gear.module * fp.teeth / 2 / np.tan(fp.gear.pitch_angle)
if fp.height >= max_height:
App.Console.PrintWarning(
"height must be smaller than {}".format(max_height)
)
fp.gear.backlash = fp.backlash.Value
scale = (
fp.module.Value * fp.gear.z / 2 / np.tan(fp.pitch_angle.Value * np.pi / 180)
)
fp.gear.clearance = fp.clearance / scale
fp.gear._update()
pts = list(fp.gear.points(num=fp.numpoints))
rot = rotation3D(2 * np.pi / fp.teeth)
# if fp.beta.Value != 0:
# pts = [np.array([self.spherical_rot(j, fp.beta.Value * np.pi / 180.) for j in i]) for i in pts]
rotated_pts = pts
for i in range(fp.gear.z - 1):
rotated_pts = list(map(rot, rotated_pts))
pts.append(np.array([pts[-1][-1], rotated_pts[0][0]]))
pts += rotated_pts
pts.append(np.array([pts[-1][-1], pts[0][0]]))
wires = []
if not "version" in fp.PropertiesList:
scale_0 = scale - fp.height.Value / 2
scale_1 = scale + fp.height.Value / 2
else: # starting with version 0.0.2
scale_0 = scale - fp.height.Value
scale_1 = scale
if fp.beta.Value == 0:
wires.append(make_bspline_wire([scale_0 * p for p in pts]))
wires.append(make_bspline_wire([scale_1 * p for p in pts]))
else:
for scale_i in np.linspace(scale_0, scale_1, 20):
# beta_i = (scale_i - scale_0) * fp.beta.Value * np.pi / 180
# rot = rotation3D(beta_i)
# points = [rot(pt) * scale_i for pt in pts]
angle = (
fp.beta.Value
* np.pi
/ 180.0
* np.sin(np.pi / 4)
/ np.sin(fp.pitch_angle.Value * np.pi / 180.0)
)
points = [
np.array([self.spherical_rot(p, angle) for p in scale_i * pt])
for pt in pts
]
wires.append(make_bspline_wire(points))
shape = Part.makeLoft(wires, True)
if fp.reset_origin:
mat = App.Matrix()
mat.A33 = -1
mat.move(fcvec([0, 0, scale_1]))
shape = shape.transformGeometry(mat)
return shape
# return self.create_teeth(pts, pos1, fp.teeth)
def create_tooth(self):
w = []
scal1 = (
self.obj.m.Value
* self.obj.gear.z
/ 2
/ np.tan(self.obj.pitch_angle.Value * np.pi / 180)
- self.obj.height.Value / 2
)
scal2 = (
self.obj.m.Value
* self.obj.gear.z
/ 2
/ np.tan(self.obj.pitch_angle.Value * np.pi / 180)
+ self.obj.height.Value / 2
)
s = [scal1, scal2]
pts = self.obj.gear.points(num=self.obj.numpoints)
for j, pos in enumerate(s):
w1 = []
def scale(x):
return fcvec(x * pos)
for i in pts:
i_scale = list(map(scale, i))
w1.append(i_scale)
w.append(w1)
surfs = []
w_t = zip(*w)
for i in w_t:
b = Part.BSplineSurface()
b.interpolate(i)
surfs.append(b)
return Part.Shape(surfs)
def spherical_rot(self, point, phi):
new_phi = np.sqrt(np.linalg.norm(point)) * phi
return rotation3D(new_phi)(point)

View File

@@ -1,29 +1,44 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import os
import FreeCAD
import FreeCADGui as Gui
from .features import ViewProviderGear, InvoluteGear, InvoluteGearRack
from .features import CycloidGear, BevelGear, CrownGear, WormGear, TimingGear, LanternGear, HypoCycloidGear
from .basegear import (
ViewProviderGear,
HypoCycloidGear,
BaseGear,
)
from .timinggear_t import TimingGearT
from .involutegear import InvoluteGear
from .internalinvolutegear import InternalInvoluteGear
from .involutegearrack import InvoluteGearRack
from .cycloidgearrack import CycloidGearRack
from .crowngear import CrownGear
from .cycloidgear import CycloidGear
from .bevelgear import BevelGear
from .wormgear import WormGear
from .timinggear import TimingGear
from .lanterngear import LanternGear
from .connector import GearConnector, ViewProviderGearConnector
class BaseCommand(object):
@@ -42,8 +57,9 @@ class BaseCommand(object):
def Activated(self):
Gui.doCommandGui("import freecad.gears.commands")
Gui.doCommandGui("freecad.gears.commands.{}.create()".format(
self.__class__.__name__))
Gui.doCommandGui(
"freecad.gears.commands.{}.create()".format(self.__class__.__name__)
)
FreeCAD.ActiveDocument.recompute()
Gui.SendMsgToActiveView("ViewFit")
@@ -56,14 +72,16 @@ class BaseCommand(object):
part = Gui.ActiveDocument.ActiveView.getActiveObject("part")
if body:
obj = FreeCAD.ActiveDocument.addObject("PartDesign::FeaturePython", cls.NAME)
obj = FreeCAD.ActiveDocument.addObject(
"PartDesign::FeaturePython", cls.NAME
)
else:
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", cls.NAME)
ViewProviderGear(obj.ViewObject)
ViewProviderGear(obj.ViewObject, cls.Pixmap)
cls.GEAR_FUNCTION(obj)
if body:
body.Group += [obj]
body.addObject(obj)
elif part:
part.Group += [obj]
else:
@@ -72,76 +90,128 @@ class BaseCommand(object):
return obj
def GetResources(self):
return {'Pixmap': self.Pixmap,
'MenuText': self.MenuText,
'ToolTip': self.ToolTip}
return {
"Pixmap": self.Pixmap,
"MenuText": self.MenuText,
"ToolTip": self.ToolTip,
}
class CreateInvoluteGear(BaseCommand):
NAME = "involutegear"
NAME = "InvoluteGear"
GEAR_FUNCTION = InvoluteGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involutegear.svg')
MenuText = 'Involute gear'
ToolTip = 'Create an Involute gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "involutegear.svg")
MenuText = "Involute Gear"
ToolTip = "Create an external involute gear"
class CreateInternalInvoluteGear(BaseCommand):
NAME = "InternalInvoluteGear"
GEAR_FUNCTION = InternalInvoluteGear
Pixmap = os.path.join(BaseCommand.ICONDIR, "internalinvolutegear.svg")
MenuText = "Internal Involute Gear"
ToolTip = "Create an internal involute gear"
class CreateInvoluteRack(BaseCommand):
NAME = "involuterack"
NAME = "InvoluteRack"
GEAR_FUNCTION = InvoluteGearRack
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involuterack.svg')
MenuText = 'Involute rack'
ToolTip = 'Create an Involute rack'
Pixmap = os.path.join(BaseCommand.ICONDIR, "involuterack.svg")
MenuText = "Involute Rack"
ToolTip = "Create an Involute rack"
class CreateCycloidRack(BaseCommand):
NAME = "CycloidRack"
GEAR_FUNCTION = CycloidGearRack
Pixmap = os.path.join(BaseCommand.ICONDIR, "cycloidrack.svg")
MenuText = "Cycloid Rack"
ToolTip = "Create an Cycloid rack"
class CreateCrownGear(BaseCommand):
NAME = "crowngear"
NAME = "CrownGear"
GEAR_FUNCTION = CrownGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'crowngear.svg')
MenuText = 'Crown gear'
ToolTip = 'Create a Crown gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "crowngear.svg")
MenuText = "Crown Gear"
ToolTip = "Create a Crown gear"
class CreateCycloidGear(BaseCommand):
NAME = "cycloidgear"
NAME = "CycloidGear"
GEAR_FUNCTION = CycloidGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'cycloidgear.svg')
MenuText = 'Cycloid gear'
ToolTip = 'Create a Cycloid gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "cycloidgear.svg")
MenuText = "Cycloid Gear"
ToolTip = "Create a Cycloid gear"
class CreateBevelGear(BaseCommand):
NAME = "bevelgear"
NAME = "BevelGear"
GEAR_FUNCTION = BevelGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'bevelgear.svg')
MenuText = 'Bevel gear'
ToolTip = 'Create a Bevel gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "bevelgear.svg")
MenuText = "Bevel Gear"
ToolTip = "Create a Bevel gear"
class CreateHypoCycloidGear(BaseCommand):
NAME = "hypocycloidgear"
NAME = "HypocycloidGear"
GEAR_FUNCTION = HypoCycloidGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'hypocycloidgear.svg')
MenuText = 'HypoCycloid gear'
ToolTip = 'Create a HypoCycloid gear with its pins'
Pixmap = os.path.join(BaseCommand.ICONDIR, "hypocycloidgear.svg")
MenuText = "HypoCycloid Gear"
ToolTip = "Create a HypoCycloid gear with its pins"
class CreateWormGear(BaseCommand):
NAME = "wormgear"
NAME = "WormGear"
GEAR_FUNCTION = WormGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'wormgear.svg')
MenuText = 'Worm gear'
ToolTip = 'Create a Worm gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "wormgear.svg")
MenuText = "Worm Gear"
ToolTip = "Create a Worm gear"
class CreateTimingGearT(BaseCommand):
NAME = "TimingGearT"
GEAR_FUNCTION = TimingGearT
Pixmap = os.path.join(BaseCommand.ICONDIR, "timinggear_t.svg")
MenuText = "Timing Gear T-shape"
ToolTip = "Create a Timing gear T-shape"
class CreateTimingGear(BaseCommand):
NAME = "timinggear"
NAME = "TimingGear"
GEAR_FUNCTION = TimingGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'timinggear.svg')
MenuText = 'Timing gear'
ToolTip = 'Create a Timing gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "timinggear.svg")
MenuText = "Timing Gear"
ToolTip = "Create a Timing gear"
class CreateLanternGear(BaseCommand):
NAME = "lanterngear"
NAME = "LanternGear"
GEAR_FUNCTION = LanternGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'lanterngear.svg')
MenuText = 'Lantern gear'
ToolTip = 'Create a Lantern gear'
Pixmap = os.path.join(BaseCommand.ICONDIR, "lanterngear.svg")
MenuText = "Lantern Gear"
ToolTip = "Create a Lantern gear"
class CreateGearConnector(BaseCommand):
NAME = "GearConnector"
GEAR_FUNCTION = GearConnector
Pixmap = os.path.join(BaseCommand.ICONDIR, "gearconnector.svg")
MenuText = "Combine two gears"
ToolTip = "Combine two gears"
def Activated(self):
gear1 = Gui.Selection.getSelection()[0]
assert isinstance(gear1.Proxy, BaseGear)
gear2 = Gui.Selection.getSelection()[1]
assert isinstance(gear2.Proxy, BaseGear)
# check if selected objects are beams
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", self.NAME)
GearConnector(obj, gear1, gear2)
ViewProviderGearConnector(obj.ViewObject)
FreeCAD.ActiveDocument.recompute()
return obj

208
freecad/gears/connector.py Normal file
View File

@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import os
import sys
import numpy as np
import FreeCAD
from pygears import __version__
from .involutegear import InvoluteGear
from .internalinvolutegear import InternalInvoluteGear
from .involutegearrack import InvoluteGearRack
from .cycloidgear import CycloidGear
from .cycloidgearrack import CycloidGearRack
from pygears.computation import compute_shifted_gears
class ViewProviderGearConnector(object):
def __init__(self, vobj, icon_fn=None):
# Set this object to the proxy object of the actual view provider
vobj.Proxy = self
dirname = os.path.dirname(__file__)
self.icon_fn = icon_fn or os.path.join(dirname, "icons", "gearconnector.svg")
def attach(self, vobj):
self.vobj = vobj
def getIcon(self):
return self.icon_fn
if sys.version_info[0] == 3 and sys.version_info[1] >= 11:
def dumps(self):
return {"icon_fn": self.icon_fn}
def loads(self, state):
self.icon_fn = state["icon_fn"]
else:
def __getstate__(self):
return {"icon_fn": self.icon_fn}
def __setstate__(self, state):
self.icon_fn = state["icon_fn"]
class GearConnector(object):
def __init__(self, obj, master_gear, slave_gear):
obj.addProperty(
"App::PropertyString", "version", "version", "freecad.gears-version", 1
)
obj.addProperty("App::PropertyLink", "master_gear", "gear", "master gear", 1)
obj.addProperty("App::PropertyLink", "slave_gear", "gear", "slave gear", 1)
obj.addProperty(
"App::PropertyAngle",
"angle1",
"gear",
"angle at which second gear is placed",
0,
)
obj.addProperty(
"App::PropertyAngle",
"angle2",
"gear",
"angle at which second gear is placed",
1,
)
obj.version = __version__
obj.master_gear = master_gear
obj.slave_gear = slave_gear
obj.angle1 = 0
obj.angle2 = 0
obj.Proxy = self
def onChanged(self, fp, prop):
# fp.angle2 = fp.master_gear.Placement.Rotation.Angle
if isinstance(fp.master_gear.Proxy, InvoluteGear) and isinstance(
fp.slave_gear.Proxy, InvoluteGear
):
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(
fp.master_gear.Placement.Rotation.Axis
)
dw_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw
dist = (dw_master + dw_slave) / 2
if fp.master_gear.shift != 0 or fp.slave_gear.shift != 0:
dist, alpha_w = compute_shifted_gears(
fp.master_gear.module,
np.deg2rad(fp.master_gear.pressure_angle.Value),
fp.master_gear.teeth,
fp.slave_gear.teeth,
fp.master_gear.shift,
fp.slave_gear.shift,
)
mat0 = FreeCAD.Matrix() # unity matrix
trans = FreeCAD.Vector(dist)
mat0.move(trans)
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), fp.angle1).toMatrix()
angle2 = dw_master / dw_slave * fp.angle1.Value
angle4 = dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1
if isinstance(fp.master_gear.Proxy, InternalInvoluteGear) and isinstance(
fp.slave_gear.Proxy, InvoluteGear
):
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(
fp.master_gear.Placement.Rotation.Axis
)
dw_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw
dist = (dw_master - dw_slave) / 2
if fp.master_gear.shift != 0 or fp.slave_gear.shift != 0:
dist, alpha_w = compute_shifted_gears(
fp.master_gear.module,
np.deg2rad(fp.master_gear.pressure_angle.Value),
fp.master_gear.teeth,
fp.slave_gear.teeth,
fp.master_gear.shift,
fp.slave_gear.shift,
)
mat0 = FreeCAD.Matrix() # unity matrix
trans = FreeCAD.Vector(dist)
mat0.move(trans)
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), fp.angle1).toMatrix()
angle2 = -dw_master / dw_slave * fp.angle1.Value
angle4 = -dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1
if (
isinstance(fp.master_gear.Proxy, InvoluteGear)
and isinstance(fp.slave_gear.Proxy, InvoluteGearRack)
) or (
isinstance(fp.master_gear.Proxy, CycloidGear)
and isinstance(fp.slave_gear.Proxy, CycloidGearRack)
):
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(
fp.master_gear.Placement.Rotation.Axis
)
dw_master = fp.master_gear.dw.Value
dw_slave = 0
dist = -(dw_master + dw_slave) / 2
mat0 = FreeCAD.Matrix() # unity matrix
mat0.move(FreeCAD.Vector(dist, 0, 0))
mat1 = FreeCAD.Matrix()
mat1.move(FreeCAD.Vector(0, np.deg2rad(fp.angle1.Value) * dw_master / 2, 0))
mat2 = FreeCAD.Matrix()
mat2.move(
FreeCAD.Vector(0, -np.deg2rad(fp.angle2.Value) * dw_master / 2, 0)
)
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), fp.angle1).toMatrix()
mat3 = rot * mat2 * mat1 * mat0
mat3.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat3
if isinstance(fp.master_gear.Proxy, CycloidGear) and isinstance(
fp.slave_gear.Proxy, CycloidGear
):
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(
fp.master_gear.Placement.Rotation.Axis
)
dw_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw
dist = (dw_master + dw_slave) / 2
mat0 = FreeCAD.Matrix() # unity matrix
trans = FreeCAD.Vector(dist, 0, 0)
mat0.move(trans)
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), fp.angle1).toMatrix()
angle2 = dw_master / dw_slave * fp.angle1.Value
angle4 = dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1
def execute(self, fp):
self.onChanged(fp, None)

143
freecad/gears/crowngear.py Normal file
View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCAD as App
import Part
import numpy as np
from .basegear import BaseGear, fcvec
class CrownGear(BaseGear):
def __init__(self, obj):
super(CrownGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyInteger",
"other_teeth",
"base",
"number of teeth of other gear",
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
self.add_accuracy_properties(obj)
obj.teeth = 15
obj.other_teeth = 15
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "2. mm"
obj.thickness = "5 mm"
obj.num_profiles = 4
obj.preview_mode = True
self.obj = obj
obj.Proxy = self
App.Console.PrintMessage(
"Gear module: Crown gear created, preview_mode = true for improved performance. "
"Set preview_mode property to false when ready to cut teeth."
)
def add_accuracy_properties(self, obj):
obj.addProperty(
"App::PropertyInteger",
"num_profiles",
"accuracy",
"number of profiles used for loft",
)
obj.addProperty(
"App::PropertyBool",
"preview_mode",
"accuracy",
"if true no boolean operation is done",
)
def profile(self, m, r, r0, t_c, t_i, alpha_w, y0, y1, y2):
r_ew = m * t_i / 2
# 1: modifizierter Waelzkreisdurchmesser:
r_e = r / r0 * r_ew
# 2: modifizierter Schraegungswinkel:
alpha = np.arccos(r0 / r * np.cos(alpha_w))
# 3: winkel phi bei senkrechter stellung eines zahns:
phi = np.pi / t_i / 2 + (alpha - alpha_w) + (np.tan(alpha_w) - np.tan(alpha))
# 4: Position des Eingriffspunktes:
x_c = r_e * np.sin(phi)
dy = -r_e * np.cos(phi) + r_ew
# 5: oberer Punkt:
b = y1 - dy
a = np.tan(alpha) * b
x1 = a + x_c
# 6: unterer Punkt
d = y2 + dy
c = np.tan(alpha) * d
x2 = x_c - c
r *= np.cos(phi)
pts = [[-x1, r, y0], [-x2, r, y0 - y1 - y2], [x2, r, y0 - y1 - y2], [x1, r, y0]]
pts.append(pts[0])
return pts
def generate_gear_shape(self, fp):
inner_diameter = fp.module.Value * fp.teeth
outer_diameter = inner_diameter + fp.height.Value * 2
inner_circle = Part.Wire(Part.makeCircle(inner_diameter / 2.0))
outer_circle = Part.Wire(Part.makeCircle(outer_diameter / 2.0))
inner_circle.reverse()
face = Part.Face([outer_circle, inner_circle])
solid = face.extrude(App.Vector([0.0, 0.0, -fp.thickness.Value]))
if fp.preview_mode:
return solid
# cutting obj
alpha_w = np.deg2rad(fp.pressure_angle.Value)
m = fp.module.Value
t = fp.teeth
t_c = t
t_i = fp.other_teeth
rm = inner_diameter / 2
y0 = m * 0.5
y1 = m + y0
y2 = m
r0 = inner_diameter / 2 - fp.height.Value * 0.1
r1 = outer_diameter / 2 + fp.height.Value * 0.3
polies = []
for r_i in np.linspace(r0, r1, fp.num_profiles):
pts = self.profile(m, r_i, rm, t_c, t_i, alpha_w, y0, y1, y2)
poly = Part.Wire(Part.makePolygon(list(map(fcvec, pts))))
polies.append(poly)
loft = Part.makeLoft(polies, True)
rot = App.Matrix()
rot.rotateZ(2 * np.pi / t)
cut_shapes = []
for _ in range(t):
loft = loft.transformGeometry(rot)
cut_shapes.append(loft)
return solid.cut(cut_shapes)

View File

@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.cycloid_tooth import CycloidTooth
from pygears._functions import rotation
from .basegear import (
BaseGear,
points_to_wire,
insert_fillet,
helicalextrusion,
rotate_tooth,
)
class CycloidGear(BaseGear):
"""FreeCAD gear"""
def __init__(self, obj):
super(CycloidGear, self).__init__(obj)
self.cycloid_tooth = CycloidTooth()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
obj.addProperty(
"App::PropertyPythonObject", "gear", "base", "the python object"
)
self.add_helical_properties(obj)
self.add_fillet_properties(obj)
self.add_tolerance_properties(obj)
self.add_cycloid_properties(obj)
self.add_computed_properties(obj)
obj.gear = self.cycloid_tooth
obj.teeth = 15
obj.module = "1. mm"
obj.setExpression(
"inner_diameter", "teeth / 2"
) # teeth/2 makes the hypocycloid a straight line to the center
obj.outer_diameter = 7.5 # we don't know the mating gear, so we just set the default to mesh with our default
obj.beta = "0. deg"
obj.height = "5. mm"
obj.clearance = 0.25
obj.numpoints = 15
obj.backlash = "0.00 mm"
obj.double_helix = False
obj.head = 0
obj.head_fillet = 0
obj.root_fillet = 0
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta")
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def add_tolerance_properties(self, obj):
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head_value * modul_value = additional length of head",
)
def add_cycloid_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"inner_diameter",
"cycloid",
"inner_diameter divided by module (hypocycloid)",
)
obj.addProperty(
"App::PropertyFloat",
"outer_diameter",
"cycloid",
"outer_diameter divided by module (epicycloid)",
)
def add_computed_properties(self, obj):
obj.addProperty("App::PropertyLength", "dw", "computed", "The pitch diameter.")
obj.setExpression(
"dw", "teeth * module"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"dw", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
def generate_gear_shape(self, fp):
fp.gear.m = fp.module.Value
fp.gear.z = fp.teeth
fp.dw = fp.module * fp.teeth
fp.gear.z1 = fp.inner_diameter
fp.gear.z2 = fp.outer_diameter
fp.gear.clearance = fp.clearance
fp.gear.head = fp.head
fp.gear.backlash = fp.backlash.Value
fp.gear._update()
pts = fp.gear.points(num=fp.numpoints)
rot = rotation(-fp.gear.phipart)
rotated_pts = list(map(rot, pts))
pts.append([pts[-1][-1], rotated_pts[0][0]])
pts += rotated_pts
tooth = points_to_wire(pts)
edges = tooth.Edges
r_head = float(fp.head_fillet * fp.module)
r_root = float(fp.root_fillet * fp.module)
pos_head = [0, 2, 6]
pos_root = [4, 6]
edge_range = [1, 9]
for pos in pos_head:
edges = insert_fillet(edges, pos, r_head)
for pos in pos_root:
edges = insert_fillet(edges, pos, r_root)
edges = edges[edge_range[0] : edge_range[1]]
edges = [e for e in edges if e is not None]
tooth = Part.Wire(edges)
profile = rotate_tooth(tooth, fp.teeth)
if fp.height.Value == 0:
return profile
base = Part.Face(profile)
if fp.beta.Value == 0:
return base.extrude(App.Vector(0, 0, fp.height.Value))
else:
twist_angle = (
fp.height.Value * np.tan(fp.beta.Value * np.pi / 180) * 2 / fp.gear.d
)
return helicalextrusion(base, fp.height.Value, twist_angle, fp.double_helix)

View File

@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCAD as App
import Part
import numpy as np
from pygears._functions import reflection
from .basegear import BaseGear, fcvec, points_to_wire, insert_fillet
class CycloidGearRack(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(CycloidGearRack, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty("App::PropertyLength", "module", "involute", "module")
obj.addProperty(
"App::PropertyBool",
"simplified",
"precision",
"if enabled the rack is drawn with a constant number of \
teeth to avoid topologic renaming.",
)
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
obj.addProperty("App::PropertyPythonObject", "rack", "base", "test")
self.add_helical_properties(obj)
self.add_computed_properties(obj)
self.add_tolerance_properties(obj)
self.add_cycloid_properties(obj)
self.add_fillet_properties(obj)
obj.teeth = 15
obj.module = "1. mm"
obj.inner_diameter = 7.5
obj.outer_diameter = 7.5
obj.height = "5. mm"
obj.thickness = "5 mm"
obj.beta = "0. deg"
obj.clearance = 0.25
obj.head = 0.0
obj.add_endings = True
obj.simplified = False
obj.numpoints = 15
self.obj = obj
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
def add_computed_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"transverse_pitch",
"computed",
"pitch in the transverse plane",
1,
)
obj.addProperty(
"App::PropertyBool",
"add_endings",
"base",
"if enabled the total length of the rack is teeth x pitch, \
otherwise the rack starts with a tooth-flank",
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
def add_cycloid_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"inner_diameter",
"cycloid",
"inner_diameter divided by module (hypocycloid)",
)
obj.addProperty(
"App::PropertyFloat",
"outer_diameter",
"cycloid",
"outer_diameter divided by module (epicycloid)",
)
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def generate_gear_shape(self, obj):
numpoints = obj.numpoints
m = obj.module.Value
t = obj.thickness.Value
r_i = obj.inner_diameter / 2 * m
r_o = obj.outer_diameter / 2 * m
c = obj.clearance
h = obj.head
head_fillet = obj.head_fillet
root_fillet = obj.root_fillet
phi_i_end = np.arccos(1 - m / r_i * (1 + c))
phi_o_end = np.arccos(1 - m / r_o * (1 + h))
phi_i = np.linspace(phi_i_end, 0, numpoints)
phi_o = np.linspace(0, phi_o_end, numpoints)
y_i = r_i * (np.cos(phi_i) - 1)
y_o = r_o * (1 - np.cos(phi_o))
x_i = r_i * (np.sin(phi_i) - phi_i) - m * np.pi / 4
x_o = r_o * (phi_o - np.sin(phi_o)) - m * np.pi / 4
x = x_i.tolist()[:-1] + x_o.tolist()
y = y_i.tolist()[:-1] + y_o.tolist()
points = np.array([y, x]).T
mirror = reflection(0)
points_1 = mirror(points)[::-1]
line_1 = [points[-1], points_1[0]]
line_2 = [points_1[-1], np.array([-(1 + c) * m, m * np.pi / 2])]
line_0 = [np.array([-(1 + c) * m, -m * np.pi / 2]), points[0]]
tooth = points_to_wire([line_0, points, line_1, points_1, line_2])
edges = tooth.Edges
edges = insert_fillet(edges, 0, m * root_fillet)
edges = insert_fillet(edges, 2, m * head_fillet)
edges = insert_fillet(edges, 4, m * head_fillet)
edges = insert_fillet(edges, 6, m * root_fillet)
tooth_edges = [e for e in edges if e is not None]
p_end = np.array(tooth_edges[-2].lastVertex().Point[:-1])
p_start = np.array(tooth_edges[1].firstVertex().Point[:-1])
p_start += np.array([0, np.pi * m])
edge = points_to_wire([[p_end, p_start]]).Edges
tooth = Part.Wire(tooth_edges[1:-1] + edge)
teeth = [tooth]
for i in range(obj.teeth - 1):
tooth = tooth.copy()
tooth.translate(App.Vector(0, np.pi * m, 0))
teeth.append(tooth)
teeth[-1] = Part.Wire(teeth[-1].Edges[:-1])
if obj.add_endings:
teeth = [Part.Wire(tooth_edges[0])] + teeth
last_edge = tooth_edges[-1]
last_edge.translate(App.Vector(0, np.pi * m * (obj.teeth - 1), 0))
teeth = teeth + [Part.Wire(last_edge)]
p_start = np.array(teeth[0].Edges[0].firstVertex().Point[:-1])
p_end = np.array(teeth[-1].Edges[-1].lastVertex().Point[:-1])
p_start_1 = p_start - np.array([obj.thickness.Value, 0.0])
p_end_1 = p_end - np.array([obj.thickness.Value, 0.0])
line6 = [p_start, p_start_1]
line7 = [p_start_1, p_end_1]
line8 = [p_end_1, p_end]
bottom = points_to_wire([line6, line7, line8])
pol = Part.Wire([bottom] + teeth)
if obj.height.Value == 0:
return pol
elif obj.beta.Value == 0:
face = Part.Face(Part.Wire(pol))
return face.extrude(fcvec([0.0, 0.0, obj.height.Value]))
elif obj.double_helix:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value / 2, obj.height.Value / 2])
)
pol3 = Part.Wire(pol)
pol3.translate(fcvec([0.0, 0.0, obj.height.Value]))
return Part.makeLoft([pol, pol2, pol3], True, True)
else:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value, obj.height.Value])
)
return Part.makeLoft([pol, pol2], True)
def __getstate__(self):
return None
def __setstate__(self, state):
return None

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import math
import numpy as np
import scipy as sp
import FreeCAD as App
import Part
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation
from .basegear import BaseGear, make_bspline_wire
class HypoCycloidGear(BaseGear):
"""parameters:
pressure_angle: pressureangle, 10-30°
pitch_angle: cone angle, 0 < pitch_angle < pi/4
"""
def __init__(self, obj):
super(HypoCycloidGear, self).__init__(obj)
obj.addProperty(
"App::PropertyFloat",
"pin_circle_radius",
"gear_parameter",
"Pin ball circle radius(overrides Tooth Pitch",
)
obj.addProperty(
"App::PropertyFloat", "roller_diameter", "gear_parameter", "Roller Diameter"
)
obj.addProperty(
"App::PropertyFloat", "eccentricity", "gear_parameter", "Eccentricity"
)
obj.addProperty(
"App::PropertyAngle",
"pressure_angle_lim",
"gear_parameter",
"Pressure angle limit",
)
obj.addProperty(
"App::PropertyFloat",
"pressure_angle_offset",
"gear_parameter",
"Offset in pressure angle",
)
obj.addProperty(
"App::PropertyInteger",
"teeth_number",
"gear_parameter",
"Number of teeth in Cam",
)
obj.addProperty(
"App::PropertyInteger",
"segment_count",
"gear_parameter",
"Number of points used for spline interpolation",
)
obj.addProperty(
"App::PropertyLength",
"hole_radius",
"gear_parameter",
"Center hole's radius",
)
obj.addProperty(
"App::PropertyBool", "show_pins", "Pins", "Create pins in place"
)
obj.addProperty("App::PropertyLength", "pin_height", "Pins", "height")
obj.addProperty(
"App::PropertyBool",
"center_pins",
"Pins",
"Center pin Z axis to generated disks",
)
obj.addProperty(
"App::PropertyBool", "show_disk0", "Disks", "Show main cam disk"
)
obj.addProperty(
"App::PropertyBool",
"show_disk1",
"Disks",
"Show another reversed cam disk on top",
)
obj.addProperty("App::PropertyLength", "disk_height", "Disks", "height")
obj.pin_circle_radius = 66
obj.roller_diameter = 3
obj.eccentricity = 1.5
obj.pressure_angle_lim = "50.0 deg"
obj.pressure_angle_offset = 0.01
obj.teeth_number = 42
obj.segment_count = 42
obj.hole_radius = "30. mm"
obj.show_pins = True
obj.pin_height = "20. mm"
obj.center_pins = True
obj.show_disk0 = True
obj.show_disk1 = True
obj.disk_height = "10. mm"
self.obj = obj
obj.Proxy = self
def to_polar(self, x, y):
return (x**2 + y**2) ** 0.5, math.atan2(y, x)
def to_rect(self, r, a):
return r * math.cos(a), r * math.sin(a)
def calcyp(self, p, a, e, n):
return math.atan(math.sin(n * a) / (math.cos(n * a) + (n * p) / (e * (n + 1))))
def calc_x(self, p, d, e, n, a):
return (
(n * p) * math.cos(a)
+ e * math.cos((n + 1) * a)
- d / 2 * math.cos(self.calcyp(p, a, e, n) + a)
)
def calc_y(self, p, d, e, n, a):
return (
(n * p) * math.sin(a)
+ e * math.sin((n + 1) * a)
- d / 2 * math.sin(self.calcyp(p, a, e, n) + a)
)
def calc_pressure_angle(self, p, d, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
pp = rg * (ex**2 + 1 - 2 * ex * math.cos(a)) ** 0.5 - d / 2
return math.asin((r3 * math.cos(a) - rg) / (pp + d / 2)) * 180 / math.pi
def calc_pressure_limit(self, p, d, e, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
q = (r3**2 + rg**2 - 2 * r3 * rg * math.cos(a)) ** 0.5
x = rg - e + (q - d / 2) * (r3 * math.cos(a) - rg) / q
y = (q - d / 2) * r3 * math.sin(a) / q
return (x**2 + y**2) ** 0.5
def check_limit(self, x, y, maxrad, minrad, offset):
r, a = self.to_polar(x, y)
if (r > maxrad) or (r < minrad):
r = r - offset
x, y = self.to_rect(r, a)
return x, y
def generate_gear_shape(self, fp):
b = fp.pin_circle_radius
d = fp.roller_diameter
e = fp.eccentricity
n = fp.teeth_number
p = b / n
s = fp.segment_count
ang = fp.pressure_angle_lim
c = fp.pressure_angle_offset
q = 2 * math.pi / float(s)
# Find the pressure angle limit circles
minAngle = -1.0
maxAngle = -1.0
for i in range(0, 180):
x = self.calc_pressure_angle(p, d, n, i * math.pi / 180.0)
if (x < ang) and (minAngle < 0):
minAngle = float(i)
if (x < -ang) and (maxAngle < 0):
maxAngle = float(i - 1)
minRadius = self.calc_pressure_limit(p, d, e, n, minAngle * math.pi / 180.0)
maxRadius = self.calc_pressure_limit(p, d, e, n, maxAngle * math.pi / 180.0)
# unused
# Part.Wire(Part.makeCircle(minRadius,App.Vector(-e, 0, 0)))
# Part.Wire(Part.makeCircle(maxRadius,App.Vector(-e, 0, 0)))
App.Console.PrintMessage("Generating cam disk\r\n")
# generate the cam profile - note: shifted in -x by eccentricicy amount
i = 0
x = self.calc_x(p, d, e, n, q * i / float(n))
y = self.calc_y(p, d, e, n, q * i / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points = [App.Vector(x - e, y, 0)]
for i in range(0, s):
x = self.calc_x(p, d, e, n, q * (i + 1) / n)
y = self.calc_y(p, d, e, n, q * (i + 1) / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points.append([x - e, y, 0])
wi = make_bspline_wire([points])
wires = []
mat = App.Matrix()
mat.move(App.Vector(e, 0.0, 0.0))
mat.rotateZ(2 * np.pi / n)
mat.move(App.Vector(-e, 0.0, 0.0))
for _ in range(n):
wi = wi.transformGeometry(mat)
wires.append(wi)
cam = Part.Face(Part.Wire(wires))
# add a circle in the center of the cam
if fp.hole_radius.Value:
centerCircle = Part.Face(
Part.Wire(Part.makeCircle(fp.hole_radius.Value, App.Vector(-e, 0, 0)))
)
cam = cam.cut(centerCircle)
to_be_fused = []
if fp.show_disk0 == True:
if fp.disk_height.Value == 0:
to_be_fused.append(cam)
else:
to_be_fused.append(cam.extrude(App.Vector(0, 0, fp.disk_height.Value)))
# secondary cam disk
if fp.show_disk1 == True:
App.Console.PrintMessage("Generating secondary cam disk\r\n")
second_cam = cam.copy()
mat = App.Matrix()
mat.rotateZ(np.pi)
mat.move(App.Vector(-e, 0, 0))
if n % 2 == 0:
mat.rotateZ(np.pi / n)
mat.move(App.Vector(e, 0, 0))
second_cam = second_cam.transformGeometry(mat)
if fp.disk_height.Value == 0:
to_be_fused.append(second_cam)
else:
to_be_fused.append(
second_cam.extrude(App.Vector(0, 0, -fp.disk_height.Value))
)
# pins
if fp.show_pins == True:
App.Console.PrintMessage("Generating pins\r\n")
pins = []
for i in range(0, n + 1):
x = p * n * math.cos(2 * math.pi / (n + 1) * i)
y = p * n * math.sin(2 * math.pi / (n + 1) * i)
pins.append(Part.Wire(Part.makeCircle(d / 2, App.Vector(x, y, 0))))
pins = Part.Face(pins)
z_offset = -fp.pin_height.Value / 2
if fp.center_pins == True:
if fp.show_disk0 == True and fp.show_disk1 == False:
z_offset += fp.disk_height.Value / 2
elif fp.show_disk0 == False and fp.show_disk1 == True:
z_offset += -fp.disk_height.Value / 2
# extrude
if z_offset != 0:
pins.translate(App.Vector(0, 0, z_offset))
if fp.pin_height != 0:
pins = pins.extrude(App.Vector(0, 0, fp.pin_height.Value))
to_be_fused.append(pins)
if to_be_fused:
return Part.makeCompound(to_be_fused)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,404 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
sodipodi:docname="timinggeart.svg"
inkscape:version="1.2 (dc2aeda, 2022-05-15)"
version="1.1"
id="svg3799"
height="64px"
width="64px"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.7781746"
inkscape:cx="37.21953"
inkscape:cy="26.870058"
inkscape:current-layer="svg3799"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:snap-center="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1388"
inkscape:window-height="872"
inkscape:window-x="52"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:object-paths="true"
inkscape:object-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-nodes="false"
inkscape:snap-others="true"
inkscape:lockguides="false"
inkscape:document-rotation="0"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<sodipodi:guide
id="guide3783"
position="32.035283,43.640454"
orientation="1,0"
inkscape:locked="false" />
<sodipodi:guide
id="guide3785"
position="32,32"
orientation="0,1"
inkscape:locked="false" />
<sodipodi:guide
id="guide9301"
position="0,0"
orientation="1,0"
inkscape:locked="false" />
<sodipodi:guide
id="guide9303"
position="0,0"
orientation="0,1"
inkscape:locked="false" />
<sodipodi:guide
id="guide9305"
position="0,64"
orientation="0,1"
inkscape:locked="false" />
<sodipodi:guide
id="guide9307"
position="64,64"
orientation="1,0"
inkscape:locked="false" />
<sodipodi:guide
position="51.168818,70.453548"
orientation="1,0"
id="guide4358"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs3801">
<inkscape:path-effect
is_visible="true"
id="path-effect9409"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect9405"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect9398"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect9390"
effect="spiro" />
<inkscape:path-effect
is_visible="true"
id="path-effect9384"
effect="spiro" />
<linearGradient
id="linearGradient9347"
inkscape:collect="always">
<stop
id="stop9349"
offset="0"
style="stop-color:#0079ff;stop-opacity:1;" />
<stop
id="stop9351"
offset="1"
style="stop-color:#0079ff;stop-opacity:0" />
</linearGradient>
<marker
style="overflow:visible"
id="Arrow2Mstart"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mstart">
<path
transform="scale(0.6) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"
id="path4317" />
</marker>
<marker
style="overflow:visible;"
id="Arrow1Mend"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Mend">
<path
transform="scale(0.4) rotate(180) translate(10,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4302" />
</marker>
<marker
style="overflow:visible;"
id="Arrow2Mend"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
id="path4320" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Mstart"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Mstart">
<path
transform="scale(0.4) translate(10,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4299" />
</marker>
<marker
style="overflow:visible;"
id="Arrow1Lend"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4296" />
</marker>
<marker
style="overflow:visible;"
id="Arrow2Lend"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Lend">
<path
transform="scale(1.1) rotate(180) translate(1,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
id="path4314" />
</marker>
<marker
style="overflow:visible"
id="EmptyTriangleOutL"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="EmptyTriangleOutL">
<path
transform="scale(0.8) translate(-6,0)"
style="fill-rule:evenodd;fill:#FFFFFF;stroke:#000000;stroke-width:1.0pt"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path4487" />
</marker>
<marker
style="overflow:visible"
id="DotS"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="DotS">
<path
transform="scale(0.2) translate(7.4, 1)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
id="path4394" />
</marker>
<marker
style="overflow:visible"
id="DotL"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="DotL">
<path
transform="scale(0.8) translate(7.4, 1)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
id="path4388" />
</marker>
<marker
style="overflow:visible"
id="Tail"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Tail">
<g
transform="scale(-1.2)"
id="g4363">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M -3.8048674,-3.9585227 L 0.54352094,0"
id="path4365" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M -1.2866832,-3.9585227 L 3.0617053,0"
id="path4367" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M 1.3053582,-3.9585227 L 5.6537466,0"
id="path4369" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M -3.8048674,4.1775838 L 0.54352094,0.21974226"
id="path4371" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M -1.2866832,4.1775838 L 3.0617053,0.21974226"
id="path4373" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.8;stroke-linecap:round"
d="M 1.3053582,4.1775838 L 5.6537466,0.21974226"
id="path4375" />
</g>
</marker>
<marker
style="overflow:visible"
id="Arrow1Sstart"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Sstart">
<path
transform="scale(0.2) translate(6,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4339" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4327" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Mstart-9"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Mstart">
<path
transform="matrix(0.4,0,0,0.4,4,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
id="path4299-1"
inkscape:connector-curvature="0" />
</marker>
<marker
style="overflow:visible"
id="Arrow2Mend-7"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(-0.6,-0.6)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
id="path4320-8"
inkscape:connector-curvature="0" />
</marker>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="47.081963"
x2="1.8271284"
y1="40.800289"
x1="-21.345743"
id="linearGradient9353"
xlink:href="#linearGradient9347"
inkscape:collect="always" />
</defs>
<metadata
id="metadata3804">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
inkscape:label="Layer 1"
id="layer1">
<rect
ry="15.401461"
y="1.4630233e-07"
x="0.18181612"
height="63.986015"
width="63.804237"
id="rect3156"
style="fill:#ffbf00;stroke:none;fill-opacity:1" />
<text
xml:space="preserve"
style="font-style:italic;font-family:sans-serif;-inkscape-font-specification:'sans-serif Italic';fill:#cccccc;fill-opacity:0;stroke:#000000;stroke-width:1.00157;stroke-opacity:0.715356"
x="53.868679"
y="46.154789"
id="text539"><tspan
sodipodi:role="line"
id="tspan537"
x="53.868679"
y="46.154789" /></text>
</g>
<g
id="g145"
transform="matrix(0.23119221,0,0,0.23119221,-46.22244,84.315861)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:173.016px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.32541"
x="216.54584"
y="-169.60706"
id="text84"><tspan
sodipodi:role="line"
id="tspan82"
x="216.54584"
y="-169.60706"
style="stroke-width:4.32541">GT</tspan></text>
<text
id="text268"
y="-117.81893"
x="271.62177"
style="font-style:normal;font-weight:normal;font-size:322.964px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:4.32541"
xml:space="preserve"><tspan
style="font-size:322.964px;fill:#ff0000;stroke-width:4.32541"
y="-117.81893"
x="271.62177"
id="tspan266"
sodipodi:role="line" /></text>
</g>
<path
style="fill:#dea600;fill-opacity:0.815686;stroke:#000000;stroke-width:1.00157;stroke-opacity:0.715356"
d="m 55.411459,40.883628 c 0,0.34284 0.04434,0.688559 0,1.028519 -0.08479,0.650049 -0.266681,1.283813 -0.385695,1.928473 -0.395262,2.141004 -0.809412,4.278999 -1.157084,6.428244 -0.12394,0.766176 -0.152263,1.545148 -0.25713,2.314167 -0.149352,1.095253 -0.128564,0.09608 -0.128564,0.77139"
id="path1211" />
<path
style="fill:#dea600;fill-opacity:0.815686;stroke:#000000;stroke-width:1.00157;stroke-opacity:0.715356"
d="m 51.040253,44.483445 c 2.100653,-0.107776 4.203401,0.128565 6.299679,0.128565"
id="path1213" />
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,75 +1,153 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCADGui as Gui
import FreeCAD as App
__dirname__ = os.path.dirname(__file__)
try:
from FreeCADGui import Workbench
except ImportError as e:
App.Console.PrintWarning(
"you are using the GearWorkbench with an old version of FreeCAD (<0.16)")
"you are using the GearWorkbench with an old version of FreeCAD (<0.16)"
)
App.Console.PrintWarning(
"the class Workbench is loaded, although not imported: magic")
"the class Workbench is loaded, although not imported: magic"
)
if sys.version_info[0] == 3 and sys.version_info[1] >= 11:
# only works with 0.21.2 and above
FC_MAJOR_VER_REQUIRED = 0
FC_MINOR_VER_REQUIRED = 21
FC_PATCH_VER_REQUIRED = 2
FC_COMMIT_REQUIRED = 33772
# Check FreeCAD version
App.Console.PrintLog("Checking FreeCAD version\n")
ver = App.Version()
major_ver = int(ver[0])
minor_vers = ver[1].split(".")
minor_ver = int(minor_vers[0])
if minor_vers[1:] and minor_vers[1]:
patch_ver = int(minor_vers[1])
else:
patch_ver = 0
gitver = ver[2].split()
if gitver:
gitver = gitver[0]
if gitver and gitver != "Unknown":
gitver = int(gitver)
else:
# If we don't have the git version, assume it's OK.
gitver = FC_COMMIT_REQUIRED
if major_ver < FC_MAJOR_VER_REQUIRED or (
major_ver == FC_MAJOR_VER_REQUIRED
and (
minor_ver < FC_MINOR_VER_REQUIRED
or (
minor_ver == FC_MINOR_VER_REQUIRED
and (
patch_ver < FC_PATCH_VER_REQUIRED
or (
patch_ver == FC_PATCH_VER_REQUIRED
and gitver < FC_COMMIT_REQUIRED
)
)
)
)
):
App.Console.PrintWarning(
"FreeCAD version (currently {}.{}.{} ({})) must be at least {}.{}.{} ({}) in order to work with Python 3.11 and above\n".format(
int(ver[0]),
minor_ver,
patch_ver,
gitver,
FC_MAJOR_VER_REQUIRED,
FC_MINOR_VER_REQUIRED,
FC_PATCH_VER_REQUIRED,
FC_COMMIT_REQUIRED,
)
)
class GearWorkbench(Workbench):
"""glider workbench"""
"""A freecad workbench aiming at gear design"""
MenuText = "Gear"
ToolTip = "Gear Workbench"
Icon = os.path.join(__dirname__, 'icons', 'gearworkbench.svg')
Icon = os.path.join(__dirname__, "icons", "gearworkbench.svg")
commands = [
"CreateInvoluteGear",
"CreateInternalInvoluteGear",
"CreateInvoluteRack",
"CreateCycloidGear",
"CreateCycloidRack",
"CreateBevelGear",
"CreateCrownGear",
"CreateWormGear",
"CreateTimingGearT",
"CreateTimingGear",
"CreateLanternGear",
"CreateHypoCycloidGear"]
"CreateHypoCycloidGear",
"CreateGearConnector",
]
def GetClassName(self):
return "Gui::PythonWorkbench"
def Initialize(self):
from .commands import CreateCycloidGear, CreateInvoluteGear
from .commands import CreateBevelGear, CreateInvoluteRack, CreateCrownGear
from .commands import CreateWormGear, CreateTimingGear, CreateLanternGear
from .commands import CreateHypoCycloidGear
from .commands import (
CreateCycloidGear,
CreateInvoluteGear,
CreateInternalInvoluteGear,
CreateBevelGear,
CreateInvoluteRack,
CreateCrownGear,
CreateWormGear,
CreateTimingGearT,
CreateTimingGear,
CreateLanternGear,
CreateHypoCycloidGear,
CreateCycloidRack,
CreateGearConnector,
)
self.appendToolbar("Gear", self.commands)
self.appendMenu("Gear", self.commands)
# Gui.addIconPath(App.getHomePath()+"Mod/gear/icons/")
Gui.addCommand('CreateInvoluteGear', CreateInvoluteGear())
Gui.addCommand('CreateCycloidGear', CreateCycloidGear())
Gui.addCommand('CreateBevelGear', CreateBevelGear())
Gui.addCommand('CreateInvoluteRack', CreateInvoluteRack())
Gui.addCommand('CreateCrownGear', CreateCrownGear())
Gui.addCommand('CreateWormGear', CreateWormGear())
Gui.addCommand('CreateTimingGear', CreateTimingGear())
Gui.addCommand('CreateLanternGear', CreateLanternGear())
Gui.addCommand('CreateHypoCycloidGear', CreateHypoCycloidGear())
Gui.addCommand("CreateInvoluteGear", CreateInvoluteGear())
Gui.addCommand("CreateInternalInvoluteGear", CreateInternalInvoluteGear())
Gui.addCommand("CreateCycloidGear", CreateCycloidGear())
Gui.addCommand("CreateCycloidRack", CreateCycloidRack())
Gui.addCommand("CreateBevelGear", CreateBevelGear())
Gui.addCommand("CreateInvoluteRack", CreateInvoluteRack())
Gui.addCommand("CreateCrownGear", CreateCrownGear())
Gui.addCommand("CreateWormGear", CreateWormGear())
Gui.addCommand("CreateTimingGearT", CreateTimingGearT())
Gui.addCommand("CreateTimingGear", CreateTimingGear())
Gui.addCommand("CreateLanternGear", CreateLanternGear())
Gui.addCommand("CreateHypoCycloidGear", CreateHypoCycloidGear())
Gui.addCommand("CreateGearConnector", CreateGearConnector())
def Activated(self):
pass

View File

@@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteTooth
from pygears._functions import rotation
from .basegear import (
BaseGear,
points_to_wire,
insert_fillet,
helicalextrusion,
rotate_tooth,
)
class InternalInvoluteGear(BaseGear):
"""FreeCAD internal involute gear
Using the same tooth as the external, just turning it inside-out:
addedum becomes dedendum, clearance becomes head, negate the backslash, ...
"""
def __init__(self, obj):
super(InternalInvoluteGear, self).__init__(obj)
self.involute_tooth = InvoluteTooth()
obj.addProperty("App::PropertyBool", "simple", "precision", "simple")
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyLength",
"module",
"base",
"normal module if properties_from_tool=True, \
else it's the transverse module.",
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
obj.addProperty("App::PropertyPythonObject", "gear", "base", "test")
self.add_involute_properties(obj)
self.add_tolerance_properties(obj)
self.add_fillet_properties(obj)
self.add_computed_properties(obj)
self.add_limiting_diameter_properties(obj)
self.add_helical_properties(obj)
obj.gear = self.involute_tooth
obj.simple = False
obj.teeth = 15
obj.module = "1. mm"
obj.shift = 0.0
obj.pressure_angle = "20. deg"
obj.beta = "0. deg"
obj.height = "5. mm"
obj.thickness = "5 mm"
obj.clearance = 0.25
obj.head = -0.4 # using head=0 and shift=0.5 may be better, but makes placeing the pinion less intuitive
obj.numpoints = 6
obj.double_helix = False
obj.backlash = "0.00 mm"
obj.reversed_backlash = False
obj.properties_from_tool = False
obj.head_fillet = 0
obj.root_fillet = 0
self.obj = obj
obj.Proxy = self
def add_limiting_diameter_properties(self, obj):
obj.addProperty("App::PropertyLength", "da", "computed", "inside diameter", 1)
obj.addProperty("App::PropertyLength", "df", "computed", "root diameter", 1)
def add_computed_properties(self, obj):
obj.addProperty("App::PropertyLength", "dw", "computed", "The pitch diameter.")
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyLength", "transverse_pitch", "computed", "transverse_pitch", 1
)
obj.addProperty(
"App::PropertyLength", "outside_diameter", "computed", "Outside diameter", 1
)
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty(
"App::PropertyBool", "reversed_backlash", "tolerance", "backlash direction"
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head_value * modul_value = additional length of head",
)
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
def add_involute_properties(self, obj):
obj.addProperty("App::PropertyFloat", "shift", "involute", "shift")
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
def add_helical_properties(self, obj):
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
obj.addProperty(
"App::PropertyBool",
"properties_from_tool",
"helical",
"if beta is given and properties_from_tool is enabled, \
gear parameters are internally recomputed for the rotated gear",
)
def generate_gear_shape(self, fp):
fp.gear.double_helix = fp.double_helix
fp.gear.m_n = fp.module.Value
fp.gear.z = fp.teeth
fp.gear.undercut = False # no undercut for internal gears
fp.gear.shift = fp.shift
fp.gear.pressure_angle = fp.pressure_angle.Value * np.pi / 180.0
fp.gear.beta = fp.beta.Value * np.pi / 180
fp.gear.clearance = fp.head # swap head and clearance to become "internal"
fp.gear.backlash = (
fp.backlash.Value * (fp.reversed_backlash - 0.5) * 2.0
) # negate "reversed_backslash", for "internal"
fp.gear.head = fp.clearance # swap head and clearance to become "internal"
fp.gear.properties_from_tool = fp.properties_from_tool
fp.gear._update()
fp.dw = "{}mm".format(fp.gear.dw)
# computed properties
fp.transverse_pitch = "{}mm".format(fp.gear.pitch)
fp.outside_diameter = fp.dw + 2 * fp.thickness
# checksbackwardcompatibility:
if not "da" in fp.PropertiesList:
self.add_limiting_diameter_properties(fp)
fp.da = "{}mm".format(fp.gear.df) # swap addednum and dedendum for "internal"
fp.df = "{}mm".format(fp.gear.da) # swap addednum and dedendum for "internal"
outer_circle = Part.Wire(Part.makeCircle(fp.outside_diameter / 2.0))
outer_circle.reverse()
if not fp.simple:
# head-fillet:
pts = fp.gear.points(num=fp.numpoints)
rot = rotation(-fp.gear.phipart)
rotated_pts = list(map(rot, pts))
pts.append([pts[-1][-1], rotated_pts[0][0]])
pts += rotated_pts
tooth = points_to_wire(pts)
r_head = float(fp.root_fillet * fp.module) # reversing head
r_root = float(fp.head_fillet * fp.module) # and foot
edges = tooth.Edges
if len(tooth.Edges) == 11:
pos_head = [1, 3, 9]
pos_root = [6, 8]
edge_range = [2, 12]
else:
pos_head = [0, 2, 6]
pos_root = [4, 6]
edge_range = [1, 9]
for pos in pos_head:
edges = insert_fillet(edges, pos, r_head)
for pos in pos_root:
try:
edges = insert_fillet(edges, pos, r_root)
except RuntimeError:
edges.pop(8)
edges.pop(6)
edge_range = [2, 10]
pos_root = [5, 7]
for pos in pos_root:
edges = insert_fillet(edges, pos, r_root)
break
edges = edges[edge_range[0] : edge_range[1]]
edges = [e for e in edges if e is not None]
tooth = Part.Wire(edges)
profile = rotate_tooth(tooth, fp.teeth)
if fp.height.Value == 0:
return Part.makeCompound([outer_circle, profile])
base = Part.Face([outer_circle, profile])
if fp.beta.Value == 0:
return base.extrude(App.Vector(0, 0, fp.height.Value))
else:
twist_angle = fp.height.Value * np.tan(fp.gear.beta) * 2 / fp.gear.d
return helicalextrusion(
base, fp.height.Value, twist_angle, fp.double_helix
)
else:
inner_circle = Part.Wire(Part.makeCircle(fp.dw / 2.0))
inner_circle.reverse()
base = Part.Face([outer_circle, inner_circle])
return base.extrude(App.Vector(0, 0, fp.height.Value))

View File

@@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteTooth
from pygears._functions import rotation
from .basegear import (
BaseGear,
points_to_wire,
insert_fillet,
helicalextrusion,
rotate_tooth,
)
class InvoluteGear(BaseGear):
"""FreeCAD gear"""
def __init__(self, obj):
super(InvoluteGear, self).__init__(obj)
self.involute_tooth = InvoluteTooth()
obj.addProperty(
"App::PropertyPythonObject", "gear", "base", "python gear object"
)
self.add_gear_properties(obj)
self.add_fillet_properties(obj)
self.add_helical_properties(obj)
self.add_computed_properties(obj)
self.add_tolerance_properties(obj)
self.add_accuracy_properties(obj)
obj.gear = self.involute_tooth
obj.simple = False
obj.undercut = False
obj.teeth = 15
obj.module = "1. mm"
obj.shift = 0.0
obj.pressure_angle = "20. deg"
obj.beta = "0. deg"
obj.height = "5. mm"
obj.clearance = 0.25
obj.head = 0.0
obj.numpoints = 6
obj.double_helix = False
obj.backlash = "0.00 mm"
obj.reversed_backlash = False
obj.properties_from_tool = False
obj.head_fillet = 0
obj.root_fillet = 0
self.obj = obj
obj.Proxy = self
self.compute_traverse_properties(obj)
def add_gear_properties(self, obj):
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyLength",
"module",
"base",
"normal module if properties_from_tool=True, \
else it's the transverse module.",
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
obj.addProperty("App::PropertyFloat", "shift", "involute", "shift")
def add_fillet_properties(self, obj):
obj.addProperty("App::PropertyBool", "undercut", "fillets", "undercut")
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def add_helical_properties(self, obj):
obj.addProperty(
"App::PropertyBool",
"properties_from_tool",
"helical",
"if beta is given and properties_from_tool is enabled, \
gear parameters are internally recomputed for the rotated gear",
)
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
def add_computed_properties(self, obj):
obj.addProperty("App::PropertyLength", "da", "computed", "outside diameter", 1)
obj.addProperty("App::PropertyLength", "df", "computed", "root diameter", 1)
self.add_traverse_module_property(obj)
obj.addProperty(
"App::PropertyLength", "dw", "computed", "The pitch diameter.", 1
)
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyLength", "transverse_pitch", "computed", "transverse_pitch", 1
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty(
"App::PropertyBool", "reversed_backlash", "tolerance", "backlash direction"
)
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head_value * modul_value = additional length of head",
)
def add_accuracy_properties(self, obj):
obj.addProperty("App::PropertyBool", "simple", "accuracy", "simple")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
def add_traverse_module_property(self, obj):
obj.addProperty(
"App::PropertyLength",
"traverse_module",
"computed",
"traverse module of the generated gear",
1,
)
def compute_traverse_properties(self, obj):
# traverse_module added recently, if old freecad doc is loaded without it, it will not exist when generate_gear_shape() is called
if not hasattr(obj, "traverse_module"):
self.add_traverse_module_property(obj)
if obj.properties_from_tool:
obj.traverse_module = obj.module / np.cos(obj.gear.beta)
else:
obj.traverse_module = obj.module
obj.transverse_pitch = "{}mm".format(obj.gear.pitch)
obj.da = "{}mm".format(obj.gear.da)
obj.df = "{}mm".format(obj.gear.df)
obj.dw = "{}mm".format(obj.gear.dw)
def generate_gear_shape(self, obj):
obj.gear.double_helix = obj.double_helix
obj.gear.m_n = obj.module.Value
obj.gear.z = obj.teeth
obj.gear.undercut = obj.undercut
obj.gear.shift = obj.shift
obj.gear.pressure_angle = obj.pressure_angle.Value * np.pi / 180.0
obj.gear.beta = obj.beta.Value * np.pi / 180
obj.gear.clearance = obj.clearance
obj.gear.backlash = obj.backlash.Value * (-obj.reversed_backlash + 0.5) * 2.0
obj.gear.head = obj.head
obj.gear.properties_from_tool = obj.properties_from_tool
obj.gear._update()
self.compute_traverse_properties(obj)
if not obj.simple:
pts = obj.gear.points(num=obj.numpoints)
rot = rotation(-obj.gear.phipart)
rotated_pts = list(map(rot, pts))
pts.append([pts[-1][-1], rotated_pts[0][0]])
pts += rotated_pts
tooth = points_to_wire(pts)
edges = tooth.Edges
# head-fillet:
r_head = float(obj.head_fillet * obj.module)
r_root = float(obj.root_fillet * obj.module)
if obj.undercut and r_root != 0.0:
r_root = 0.0
App.Console.PrintWarning(
"root fillet is not allowed if undercut is computed"
)
if len(tooth.Edges) == 11:
pos_head = [1, 3, 9]
pos_root = [6, 8]
edge_range = [2, 12]
else:
pos_head = [0, 2, 6]
pos_root = [4, 6]
edge_range = [1, 9]
for pos in pos_head:
edges = insert_fillet(edges, pos, r_head)
for pos in pos_root:
try:
edges = insert_fillet(edges, pos, r_root)
except RuntimeError:
edges.pop(8)
edges.pop(6)
edge_range = [2, 10]
pos_root = [5, 7]
for pos in pos_root:
edges = insert_fillet(edges, pos, r_root)
break
edges = edges[edge_range[0] : edge_range[1]]
edges = [e for e in edges if e is not None]
tooth = Part.Wire(edges)
profile = rotate_tooth(tooth, obj.teeth)
if obj.height.Value == 0:
return profile
base = Part.Face(profile)
if obj.beta.Value == 0:
return base.extrude(App.Vector(0, 0, obj.height.Value))
else:
twist_angle = obj.height.Value * np.tan(obj.gear.beta) * 2 / obj.gear.d
return helicalextrusion(
base, obj.height.Value, twist_angle, obj.double_helix
)
else:
rw = obj.gear.dw / 2
return Part.makeCylinder(rw, obj.height.Value)

View File

@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteRack
from .basegear import BaseGear, fcvec, points_to_wire, insert_fillet
class InvoluteGearRack(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(InvoluteGearRack, self).__init__(obj)
self.involute_rack = InvoluteRack()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty(
"App::PropertyBool",
"simplified",
"precision",
"if enabled the rack is drawn with a constant number of \
teeth to avoid topologic renaming.",
)
obj.addProperty("App::PropertyPythonObject", "rack", "base", "test")
self.add_helical_properties(obj)
self.add_computed_properties(obj)
self.add_tolerance_properties(obj)
self.add_involute_properties(obj)
self.add_fillet_properties(obj)
obj.rack = self.involute_rack
obj.teeth = 15
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "5. mm"
obj.thickness = "5 mm"
obj.beta = "0. deg"
obj.clearance = 0.25
obj.head = 0.0
obj.properties_from_tool = False
obj.add_endings = True
obj.simplified = False
self.obj = obj
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty(
"App::PropertyBool",
"properties_from_tool",
"helical",
"if beta is given and properties_from_tool is enabled, \
gear parameters are internally recomputed for the rotated gear",
)
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
def add_computed_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"transverse_pitch",
"computed",
"pitch in the transverse plane",
1,
)
obj.addProperty(
"App::PropertyBool",
"add_endings",
"base",
"if enabled the total length of the rack is teeth x pitch, \
otherwise the rack starts with a tooth-flank",
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
def add_involute_properties(self, obj):
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def generate_gear_shape(self, obj):
obj.rack.m = obj.module.Value
obj.rack.z = obj.teeth
obj.rack.pressure_angle = obj.pressure_angle.Value * np.pi / 180.0
obj.rack.thickness = obj.thickness.Value
obj.rack.beta = obj.beta.Value * np.pi / 180.0
obj.rack.head = obj.head
# checksbackwardcompatibility:
if "clearance" in obj.PropertiesList:
obj.rack.clearance = obj.clearance
if "properties_from_tool" in obj.PropertiesList:
obj.rack.properties_from_tool = obj.properties_from_tool
if "add_endings" in obj.PropertiesList:
obj.rack.add_endings = obj.add_endings
if "simplified" in obj.PropertiesList:
obj.rack.simplified = obj.simplified
obj.rack._update()
m, m_n, pitch, pressure_angle_t = obj.rack.compute_properties()
obj.transverse_pitch = "{} mm".format(pitch)
t = obj.thickness.Value
c = obj.clearance
h = obj.head
alpha = obj.pressure_angle.Value * np.pi / 180.0
head_fillet = obj.head_fillet
root_fillet = obj.root_fillet
x1 = -m * np.pi / 2
y1 = -m * (1 + c)
y2 = y1
x2 = -m * np.pi / 4 + y2 * np.tan(alpha)
y3 = m * (1 + h)
x3 = -m * np.pi / 4 + y3 * np.tan(alpha)
x4 = -x3
x5 = -x2
x6 = -x1
y4 = y3
y5 = y2
y6 = y1
p1 = np.array([y1, x1])
p2 = np.array([y2, x2])
p3 = np.array([y3, x3])
p4 = np.array([y4, x4])
p5 = np.array([y5, x5])
p6 = np.array([y6, x6])
line1 = [p1, p2]
line2 = [p2, p3]
line3 = [p3, p4]
line4 = [p4, p5]
line5 = [p5, p6]
tooth = Part.Wire(points_to_wire([line1, line2, line3, line4, line5]))
edges = tooth.Edges
edges = insert_fillet(edges, 0, m * root_fillet)
edges = insert_fillet(edges, 2, m * head_fillet)
edges = insert_fillet(edges, 4, m * head_fillet)
edges = insert_fillet(edges, 6, m * root_fillet)
tooth_edges = [e for e in edges if e is not None]
p_end = np.array(tooth_edges[-2].lastVertex().Point[:-1])
p_start = np.array(tooth_edges[1].firstVertex().Point[:-1])
p_start += np.array([0, np.pi * m])
edge = points_to_wire([[p_end, p_start]]).Edges
tooth = Part.Wire(tooth_edges[1:-1] + edge)
teeth = [tooth]
for i in range(obj.teeth - 1):
tooth = tooth.copy()
tooth.translate(App.Vector(0, np.pi * m, 0))
teeth.append(tooth)
teeth[-1] = Part.Wire(teeth[-1].Edges[:-1])
if obj.add_endings:
teeth = [Part.Wire(tooth_edges[0])] + teeth
last_edge = tooth_edges[-1]
last_edge.translate(App.Vector(0, np.pi * m * (obj.teeth - 1), 0))
teeth = teeth + [Part.Wire(last_edge)]
p_start = np.array(teeth[0].Edges[0].firstVertex().Point[:-1])
p_end = np.array(teeth[-1].Edges[-1].lastVertex().Point[:-1])
p_start_1 = p_start - np.array([obj.thickness.Value, 0.0])
p_end_1 = p_end - np.array([obj.thickness.Value, 0.0])
line6 = [p_start, p_start_1]
line7 = [p_start_1, p_end_1]
line8 = [p_end_1, p_end]
bottom = points_to_wire([line6, line7, line8])
pol = Part.Wire([bottom] + teeth)
if obj.height.Value == 0:
return pol
elif obj.beta.Value == 0:
face = Part.Face(Part.Wire(pol))
return face.extrude(fcvec([0.0, 0.0, obj.height.Value]))
elif obj.double_helix:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value / 2, obj.height.Value / 2])
)
pol3 = Part.Wire(pol)
pol3.translate(fcvec([0.0, 0.0, obj.height.Value]))
return Part.makeLoft([pol, pol2, pol3], True, True)
else:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value, obj.height.Value])
)
return Part.makeLoft([pol, pol2], True)

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
import scipy as sp
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation
from .basegear import BaseGear, fcvec, part_arc_from_points_and_center
class LanternGear(BaseGear):
def __init__(self, obj):
super(LanternGear, self).__init__(obj)
obj.addProperty(
"App::PropertyInteger", "teeth", "gear_parameter", "number of teeth"
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty(
"App::PropertyLength",
"bolt_radius",
"base",
"the bolt radius of the rack/chain",
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyInteger",
"num_profiles",
"accuracy",
"number of profiles used for loft",
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.teeth = 15
obj.module = "1. mm"
obj.bolt_radius = "1 mm"
obj.height = "5. mm"
obj.num_profiles = 10
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
m = fp.module.Value
teeth = fp.teeth
r_r = fp.bolt_radius.Value
r_0 = m * teeth / 2
r_max = r_0 + r_r + fp.head * m
phi_max = (r_r + np.sqrt(r_max**2 - r_0**2)) / r_0
def find_phi_min(phi_min):
return r_0 * (
phi_min**2 * r_0
- 2 * phi_min * r_0 * np.sin(phi_min)
- 2 * phi_min * r_r
- 2 * r_0 * np.cos(phi_min)
+ 2 * r_0
+ 2 * r_r * np.sin(phi_min)
)
phi_min = sp.optimize.root(find_phi_min, (phi_max + r_r / r_0 * 4) / 5).x[
0
] # , r_r / r_0, phi_max)
# phi_min = 0 # r_r / r_0
phi = np.linspace(phi_min, phi_max, fp.num_profiles)
x = r_0 * (np.cos(phi) + phi * np.sin(phi)) - r_r * np.sin(phi)
y = r_0 * (np.sin(phi) - phi * np.cos(phi)) + r_r * np.cos(phi)
xy1 = np.array([x, y]).T
p_1 = xy1[0]
p_1_end = xy1[-1]
bsp_1 = Part.BSplineCurve()
bsp_1.interpolate(list(map(fcvec, xy1)))
w_1 = bsp_1.toShape()
xy2 = xy1 * np.array([1.0, -1.0])
p_2 = xy2[0]
p_2_end = xy2[-1]
bsp_2 = Part.BSplineCurve()
bsp_2.interpolate(list(map(fcvec, xy2)))
w_2 = bsp_2.toShape()
p_12 = np.array([r_0 - r_r, 0.0])
arc = Part.Arc(
App.Vector(*p_1, 0.0), App.Vector(*p_12, 0.0), App.Vector(*p_2, 0.0)
).toShape()
rot = rotation(-np.pi * 2 / teeth)
p_3 = rot(np.array([p_2_end]))[0]
# l = Part.LineSegment(fcvec(p_1_end), fcvec(p_3)).toShape()
l = part_arc_from_points_and_center(
p_1_end, p_3, np.array([0.0, 0.0])
).toShape()
w = Part.Wire([w_2, arc, w_1, l])
wires = [w]
rot = App.Matrix()
for _ in range(teeth - 1):
rot.rotateZ(np.pi * 2 / teeth)
wires.append(w.transformGeometry(rot))
wi = Part.Wire(wires)
if fp.height.Value == 0:
return wi
else:
return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))

285
freecad/gears/timinggear.py Normal file
View File

@@ -0,0 +1,285 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears._functions import reflection
from .basegear import BaseGear, part_arc_from_points_and_center
class TimingGear(BaseGear):
"""FreeCAD gear rack"""
data = {
"gt2": {
"pitch": 2.0,
"u": 0.254,
"h": 0.75,
"H": 1.38,
"r0": 0.555,
"r1": 1.0,
"rs": 0.15,
"offset": 0.40,
},
"gt3": {
"pitch": 3.0,
"u": 0.381,
"h": 1.14,
"H": 2.40,
"r0": 0.85,
"r1": 1.52,
"rs": 0.25,
"offset": 0.61,
},
"gt5": {
"pitch": 5.0,
"u": 0.5715,
"h": 1.93,
"H": 3.81,
"r0": 1.44,
"r1": 2.57,
"rs": 0.416,
"offset": 1.03,
},
"gt8": {
"pitch": 8.0,
"u": 0.9144,
"h": 3.088,
"H": 6.096,
"r0": 2.304,
"r1": 4.112,
"rs": 0.6656,
"offset": 1.648,
},
"htd3": {
"pitch": 3.0,
"u": 0.381,
"h": 1.21,
"H": 2.40,
"r0": 0.89,
"r1": 0.89,
"rs": 0.26,
"offset": 0.0,
},
"htd5": {
"pitch": 5.0,
"u": 0.5715,
"h": 2.06,
"H": 3.80,
"r0": 1.49,
"r1": 1.49,
"rs": 0.43,
"offset": 0.0,
},
"htd8": {
"pitch": 8.0,
"u": 0.686,
"h": 3.45,
"H": 6.00,
"r0": 2.46,
"r1": 2.46,
"rs": 0.70,
"offset": 0.0,
},
}
def __init__(self, obj):
super(TimingGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyEnumeration", "type", "base", "type of timing-gear"
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "pitch", "computed", "pitch of gear", 1)
obj.addProperty(
"App::PropertyLength", "h", "computed", "radial height of teeth", 1
)
obj.addProperty(
"App::PropertyLength",
"u",
"computed",
"radial difference between pitch diameter and head of gear",
1,
)
obj.addProperty(
"App::PropertyLength", "r0", "computed", "radius of first arc", 1
)
obj.addProperty(
"App::PropertyLength", "r1", "computed", "radius of second arc", 1
)
obj.addProperty(
"App::PropertyLength", "rs", "computed", "radius of third arc", 1
)
obj.addProperty(
"App::PropertyLength",
"offset",
"computed",
"x-offset of second arc-midpoint",
1,
)
obj.teeth = 15
obj.type = ["gt2", "gt3", "gt5", "gt8", "htd3", "htd5", "htd8"]
obj.height = "5. mm"
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
# m ... center of arc/circle
# r ... radius of arc/circle
# x ... end-point of arc
# phi ... angle
tp = fp.type
gt_data = self.data[tp]
pitch = fp.pitch = gt_data["pitch"]
h = fp.h = gt_data["h"]
u = fp.u = gt_data["u"]
r_12 = fp.r0 = gt_data["r0"]
r_23 = fp.r1 = gt_data["r1"]
r_34 = fp.rs = gt_data["rs"]
offset = fp.offset = gt_data["offset"]
arcs = []
if offset == 0.0:
phi5 = np.pi / fp.teeth
ref = reflection(-phi5 - np.pi / 2.0)
rp = pitch * fp.teeth / np.pi / 2.0 - u
m_34 = np.array([-(r_12 + r_34), rp - h + r_12])
x2 = np.array([-r_12, m_34[1]])
x4 = np.array([m_34[0], m_34[1] + r_34])
x6 = ref(x4)
mir = np.array([-1.0, 1.0])
xn2 = mir * x2
xn4 = mir * x4
mn_34 = mir * m_34
arcs.append(part_arc_from_points_and_center(xn4, xn2, mn_34).toShape())
arcs.append(
Part.Arc(
App.Vector(*xn2, 0.0),
App.Vector(0, rp - h, 0.0),
App.Vector(*x2, 0.0),
).toShape()
)
arcs.append(part_arc_from_points_and_center(x2, x4, m_34).toShape())
arcs.append(
part_arc_from_points_and_center(x4, x6, np.array([0.0, 0.0])).toShape()
)
else:
phi_12 = np.arctan(np.sqrt(1.0 / (((r_12 - r_23) / offset) ** 2 - 1)))
rp = pitch * fp.teeth / np.pi / 2.0
r4 = r5 = rp - u
m_12 = np.array([0.0, r5 - h + r_12])
m_23 = np.array([offset, offset / np.tan(phi_12) + m_12[1]])
m_23y = m_23[1]
# solving for phi4:
# sympy.solve(
# ((r5 - r_34) * sin(phi4) + offset) ** 2 + \
# ((r5 - r_34) * cos(phi4) - m_23y) ** 2 - \
# ((r_34 + r_23) ** 2), phi4)
phi4 = 2 * np.arctan(
(
-2 * offset * r5
+ 2 * offset * r_34
+ np.sqrt(
-(m_23y**4)
- 2 * m_23y**2 * offset**2
+ 2 * m_23y**2 * r5**2
- 4 * m_23y**2 * r5 * r_34
+ 2 * m_23y**2 * r_23**2
+ 4 * m_23y**2 * r_23 * r_34
+ 4 * m_23y**2 * r_34**2
- offset**4
+ 2 * offset**2 * r5**2
- 4 * offset**2 * r5 * r_34
+ 2 * offset**2 * r_23**2
+ 4 * offset**2 * r_23 * r_34
+ 4 * offset**2 * r_34**2
- r5**4
+ 4 * r5**3 * r_34
+ 2 * r5**2 * r_23**2
+ 4 * r5**2 * r_23 * r_34
- 4 * r5**2 * r_34**2
- 4 * r5 * r_23**2 * r_34
- 8 * r5 * r_23 * r_34**2
- r_23**4
- 4 * r_23**3 * r_34
- 4 * r_23**2 * r_34**2
)
)
/ (
m_23y**2
+ 2 * m_23y * r5
- 2 * m_23y * r_34
+ offset**2
+ r5**2
- 2 * r5 * r_34
- r_23**2
- 2 * r_23 * r_34
)
)
phi5 = np.pi / fp.teeth
m_34 = (r5 - r_34) * np.array([-np.sin(phi4), np.cos(phi4)])
x2 = np.array([-r_12 * np.sin(phi_12), m_12[1] - r_12 * np.cos(phi_12)])
x3 = m_34 + r_34 / (r_34 + r_23) * (m_23 - m_34)
x4 = r4 * np.array([-np.sin(phi4), np.cos(phi4)])
ref = reflection(-phi5 - np.pi / 2)
x6 = ref(x4)
mir = np.array([-1.0, 1.0])
xn2 = mir * x2
xn3 = mir * x3
xn4 = mir * x4
mn_34 = mir * m_34
mn_23 = mir * m_23
arcs.append(part_arc_from_points_and_center(xn4, xn3, mn_34).toShape())
arcs.append(part_arc_from_points_and_center(xn3, xn2, mn_23).toShape())
arcs.append(part_arc_from_points_and_center(xn2, x2, m_12).toShape())
arcs.append(part_arc_from_points_and_center(x2, x3, m_23).toShape())
arcs.append(part_arc_from_points_and_center(x3, x4, m_34).toShape())
arcs.append(
part_arc_from_points_and_center(x4, x6, np.array([0.0, 0.0])).toShape()
)
wire = Part.Wire(arcs)
wires = [wire]
rot = App.Matrix()
rot.rotateZ(np.pi * 2 / fp.teeth)
for _ in range(fp.teeth - 1):
wire = wire.transformGeometry(rot)
wires.append(wire)
wi = Part.Wire(wires)
if fp.height.Value == 0:
return wi
else:
return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))

View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import numpy as np
import scipy as sp
import FreeCAD as App
import Part
from pygears._functions import rotation, reflection
from .basegear import BaseGear, fcvec
class TimingGearT(BaseGear):
def __init__(self, obj):
print("hello gear")
obj.addProperty("App::PropertyLength", "pitch", "base", "pitch of gear")
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyLength", "tooth_height", "base", "radial height of tooth"
)
obj.addProperty(
"App::PropertyLength",
"u",
"base",
"radial distance from tooth-head to pitch circle",
)
obj.addProperty("App::PropertyAngle", "alpha", "base", "angle of tooth flanks")
obj.addProperty("App::PropertyLength", "height", "base", "extrusion height")
obj.pitch = "5. mm"
obj.teeth = 15
obj.tooth_height = "1.2 mm"
obj.u = "0.6 mm"
obj.alpha = "40. deg"
obj.height = "5 mm"
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
print("generate gear shape")
pitch = fp.pitch.Value
teeth = fp.teeth
u = fp.u.Value
tooth_height = fp.tooth_height.Value
alpha = fp.alpha.Value / 180.0 * np.pi # we need radiant
height = fp.height.Value
r_p = pitch * teeth / 2.0 / np.pi
gamma_0 = pitch / r_p
gamma_1 = gamma_0 / 4
p_A = np.array([np.cos(-gamma_1), np.sin(-gamma_1)]) * (
r_p - u - tooth_height / 2
)
def line(s):
p = (
p_A
+ np.array([np.cos(alpha / 2 - gamma_1), np.sin(alpha / 2 - gamma_1)])
* s
)
return p
def dist_p1(s):
return (np.linalg.norm(line(s)) - (r_p - u - tooth_height)) ** 2
def dist_p2(s):
return (np.linalg.norm(line(s)) - (r_p - u)) ** 2
s1 = sp.optimize.minimize(dist_p1, 0.0).x
s2 = sp.optimize.minimize(dist_p2, 0.0).x
p_1 = line(s1)
p_2 = line(s2)
mirror = reflection(0.0) # reflect the points at the x-axis
p_3, p_4 = mirror(np.array([p_2, p_1]))
rot = rotation(-gamma_0) # why is the rotation in wrong direction ???
p_5 = rot(np.array([p_1]))[0] # the rotation expects a list of points
l1 = Part.LineSegment(fcvec(p_1), fcvec(p_2)).toShape()
l2 = Part.LineSegment(fcvec(p_2), fcvec(p_3)).toShape()
l3 = Part.LineSegment(fcvec(p_3), fcvec(p_4)).toShape()
l4 = Part.LineSegment(fcvec(p_4), fcvec(p_5)).toShape()
w = Part.Wire([l1, l2, l3, l4])
# now using a FreeCAD Matrix (this will turn in the right direction)
rot = App.Matrix()
rot.rotateZ(gamma_0)
wires = []
for i in range(teeth):
w = w.transformGeometry(rot)
wires.append(w.copy())
contour = Part.Wire(wires)
if height == 0:
return contour
else:
face = Part.Face(Part.Wire(wires))
return face.extrude(App.Vector(0.0, 0.0, height))

151
freecad/gears/wormgear.py Normal file
View File

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteTooth
from pygears._functions import rotation
from .basegear import BaseGear, helicalextrusion, fcvec
class WormGear(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(WormGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "diameter", "base", "diameter")
obj.addProperty("App::PropertyAngle", "beta", "computed", "beta ", 1)
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
obj.addProperty(
"App::PropertyBool", "reverse_pitch", "base", "reverse rotation of helix"
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
obj.teeth = 3
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "5. mm"
obj.diameter = "5. mm"
obj.clearance = 0.25
obj.head = 0
obj.reverse_pitch = False
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
m = fp.module.Value
d = fp.diameter.Value
t = fp.teeth
h = fp.height
clearance = fp.clearance
head = fp.head
alpha = fp.pressure_angle.Value
beta = np.arctan(m * t / d)
fp.beta = np.rad2deg(beta)
beta = -(fp.reverse_pitch * 2 - 1) * (np.pi / 2 - beta)
r_1 = (d - (2 + 2 * clearance) * m) / 2
r_2 = (d + (2 + 2 * head) * m) / 2
z_a = (2 + head + clearance) * m * np.tan(np.deg2rad(alpha))
z_b = (m * np.pi - 4 * m * np.tan(np.deg2rad(alpha))) / 2
z_0 = clearance * m * np.tan(np.deg2rad(alpha))
z_1 = z_b - z_0
z_2 = z_1 + z_a
z_3 = z_2 + z_b - 2 * head * m * np.tan(np.deg2rad(alpha))
z_4 = z_3 + z_a
def helical_projection(r, z):
phi = 2 * z / m / t
x = r * np.cos(phi)
y = r * np.sin(phi)
z = 0 * y
return np.array([x, y, z]).T
# create a circle from phi=0 to phi_1 with r_1
phi_0 = 2 * z_0 / m / t
phi_1 = 2 * z_1 / m / t
c1 = Part.makeCircle(
r_1,
App.Vector(0, 0, 0),
App.Vector(0, 0, 1),
np.rad2deg(phi_0),
np.rad2deg(phi_1),
)
# create first bspline
z_values = np.linspace(z_1, z_2, 10)
r_values = np.linspace(r_1, r_2, 10)
points = helical_projection(r_values, z_values)
bsp1 = Part.BSplineCurve()
bsp1.interpolate(list(map(fcvec, points)))
bsp1 = bsp1.toShape()
# create circle from phi_2 to phi_3
phi_2 = 2 * z_2 / m / t
phi_3 = 2 * z_3 / m / t
c2 = Part.makeCircle(
r_2,
App.Vector(0, 0, 0),
App.Vector(0, 0, 1),
np.rad2deg(phi_2),
np.rad2deg(phi_3),
)
# create second bspline
z_values = np.linspace(z_3, z_4, 10)
r_values = np.linspace(r_2, r_1, 10)
points = helical_projection(r_values, z_values)
bsp2 = Part.BSplineCurve()
bsp2.interpolate(list(map(fcvec, points)))
bsp2 = bsp2.toShape()
wire = Part.Wire([c1, bsp1, c2, bsp2])
w_all = [wire]
rot = App.Matrix()
rot.rotateZ(2 * np.pi / t)
for i in range(1, t):
w_all.append(w_all[-1].transformGeometry(rot))
full_wire = Part.Wire(w_all)
if h == 0:
return full_wire
else:
shape = helicalextrusion(Part.Face(full_wire), h, h * np.tan(beta) * 2 / d)
return shape

24
package.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>freecad.gears workbench</name>
<description>A gear workbench for FreeCAD</description>
<version>1.2</version>
<date>2022-02-07</date>
<maintainer email="sppedflyer@gmail.com">looooo</maintainer>
<license file="LICENSE">GPL 3</license>
<url type="repository" branch="master">https://github.com/looooo/freecad.gears</url>
<url type="bugtracker">https://github.com/looooo/freecad.gears/issues</url>
<url type="documentation">https://wiki.freecad.org/FCGear_Workbench</url>
<icon>freecad/gears/icons/gearworkbench.svg</icon>
<content>
<workbench>
<classname>GearWorkbench</classname>
<subdirectory>./</subdirectory>
<freecadmin>0.19</freecadmin>
<tag>gear</tag>
<tag>gears</tag>
</workbench>
</content>
</package>

View File

@@ -1,22 +1,19 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__version__ = "0.0.3"
__version__ = "1.2.0"

View File

@@ -1,21 +1,18 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
@@ -25,55 +22,64 @@ from numpy.linalg import solve, norm
def reflection(angle):
mat = array(
[[cos(2 * angle), -sin(2 * angle)], [-sin(2 * angle), -cos(2 * angle)]])
mat = array([[cos(2 * angle), -sin(2 * angle)],
[-sin(2 * angle), -cos(2 * angle)]])
def func(x):
return(dot(x, mat))
return(func)
# why not use mat @ x???
return dot(x, mat)
return func
def reflection3D(angle):
mat = array([[cos(2 * angle), -sin(2 * angle), 0.],
[-sin(2 * angle), -cos(2 * angle), 0.], [0., 0., 1.]])
mat = array(
[
[cos(2 * angle), -sin(2 * angle), 0.0],
[-sin(2 * angle), -cos(2 * angle), 0.0],
[0.0, 0.0, 1.0],
]
)
def func(x):
return(dot(x, mat))
return(func)
# why not use mat @ x
return dot(x, mat)
return func
def rotation(angle, midpoint=None):
midpoint = midpoint or [0., 0.]
mat = array([[cos(angle), -sin(angle)],
[sin(angle), cos(angle)]])
midpoint = midpoint or [0.0, 0.0]
mat = array([[cos(angle), -sin(angle)], [sin(angle), cos(angle)]])
midpoint = array(midpoint)
vec = midpoint - dot(midpoint, mat)
trans = translation(vec)
def func(xx):
return(trans(dot(xx, mat)))
return(func)
return trans(dot(xx, mat))
return func
def rotation3D(angle):
mat = array(
[
[cos(angle), -sin(angle), 0.],
[sin(angle), cos(angle), 0.],
[0., 0., 1.]])
[[cos(angle), -sin(angle), 0.0], [sin(angle), cos(angle), 0.0], [0.0, 0.0, 1.0]]
)
def func(xx):
return(dot(xx, mat))
return(func)
return dot(xx, mat)
return func
def translation(vec):
def trans(x):
return([x[0] + vec[0], x[1] + vec[1]])
return [x[0] + vec[0], x[1] + vec[1]]
def func(x):
return(array(list(map(trans, x))))
return(func)
return array(list(map(trans, x)))
return func
def trim(p1, p2, p3, p4):
@@ -83,31 +89,31 @@ def trim(p1, p2, p3, p4):
a4 = array(p4)
if all(a1 == a2) or all(a3 == a4):
if all(a1 == a3):
return(a1)
return a1
else:
return(False)
return False
elif all(a1 == a3):
if all(a2 == a4):
return((a1 + a2) / 2)
return (a1 + a2) / 2
else:
return(a1)
return a1
elif all(a1 == a4):
if all(a2 == a3):
return((a1 + a2) / 2)
return (a1 + a2) / 2
else:
return(a1)
return a1
elif all(a2 == a3) or all(a2 == a4):
return(p2)
return p2
try:
g, h = solve(transpose([-a2 + a1, a4 - a3]), a1 - a3)
except Exception as e:
print(e)
return(False)
return False
else:
if 0. < g < 1. and 0. < h < 1.:
return(a1 + g * (a2 - a1))
if 0.0 < g < 1.0 and 0.0 < h < 1.0:
return a1 + g * (a2 - a1)
else:
return(False)
return False
def trimfunc(l1, l2):
@@ -127,12 +133,12 @@ def trimfunc(l1, l2):
l2 == [l2[0]]
else:
l2 = l2[jk::-1]
return([vstack([l1, [s]]), vstack([[s], l2])])
return [vstack([l1, [s]]), vstack([[s], l2])]
j0 = j
jk += 1
i0 = i
ik += 1
return(False)
return False
def diff_norm(vec1, vec2):
@@ -144,7 +150,7 @@ def nearestpts(evolv, underc):
ik = 0
iout = 0
jout = 0
outmin = 1000.
outmin = 1000.0
for i in array(evolv[1:]):
jk = 0
for j in array(underc[1:]):
@@ -157,17 +163,17 @@ def nearestpts(evolv, underc):
iout, jout = [ik, jk]
jk += 1
ik += 1
return([vstack([underc[:jout], evolv[iout]]), evolv[iout:]])
return [vstack([underc[:jout], evolv[iout]]), evolv[iout:]]
def intersection_line_circle(p1, p2, r):
"""return the intersection point of a line from p1 to p2 and a sphere of radius 1 and
"""return the intersection point of a line from p1 to p2 and a sphere of radius 1 and
midpoint 0,0,0"""
d = p2 - p1
d /= norm(d)
p_half = d.dot(p1)
q = p1.dot(p1) - r ** 2
t = -p_half + sqrt(p_half ** 2 - q)
q = p1.dot(p1) - r**2
t = -p_half + sqrt(p_half**2 - q)
return p1 + d * t
@@ -179,5 +185,3 @@ def arc_from_points_and_center(p_1, p_2, m):
v /= norm(v)
p_12 = m + v * r
return (p_1, p_12, p_2)

View File

@@ -1,93 +1,198 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
from __future__ import division
from __future__ import division
from numpy import cos, sin, tan, arccos, arctan, pi, array, linspace, transpose, vstack, sqrt
from numpy import (
cos,
sin,
tan,
arccos,
arctan,
pi,
array,
linspace,
transpose,
vstack,
sqrt,
)
from ._functions import rotation3D, reflection3D, intersection_line_circle
class BevelTooth(object):
def __init__(self, pressure_angle=70 * pi / 180, pitch_angle=pi / 4, clearance=0.1,
z=21, backlash=0.00, module=0.25):
def __init__(
self,
pressure_angle=70 * pi / 180,
pitch_angle=pi / 4,
clearance=0.1,
z=21,
backlash=0.00,
module=0.25,
):
self.pressure_angle = pressure_angle
self.pitch_angle = pitch_angle
self.z = z
self.clearance = clearance
self.backlash = backlash
self.angular_backlash = backlash / (z * module / 2)
self.module = module
self.involute_end = arccos(
1 / sqrt(2) * sqrt((42. + 16.*cos(2.*self.pressure_angle) +
6.*cos(4.*self.pressure_angle) + cos(4.*self.pressure_angle - 4.*self.pitch_angle) - 8.*cos(2.*self.pressure_angle - 2.*self.pitch_angle) -
4.*cos(4.*self.pressure_angle - 2.*self.pitch_angle) + 24.*cos(2.*self.pitch_angle) - 2.*cos(4.*self.pitch_angle) -
8.*cos(2.*(self.pressure_angle + self.pitch_angle)) + cos(4.*(self.pressure_angle + self.pitch_angle)) -
4.*cos(4.*self.pressure_angle + 2.*self.pitch_angle) + 24.*cos((4.*sin(self.pitch_angle))/self.z) +
4.*cos(2.*self.pressure_angle - (4.*sin(self.pitch_angle))/self.z) + 4.*cos(2.*self.pressure_angle -
4.*self.pitch_angle - (4.*sin(self.pitch_angle))/self.z) - 8.*cos(2.*self.pressure_angle - 2.*self.pitch_angle -
(4.*sin(self.pitch_angle))/self.z) + 24.*cos(4.*(self.pitch_angle + sin(self.pitch_angle)/self.z)) -
8.*cos(2.*(self.pressure_angle + self.pitch_angle + (2.*sin(self.pitch_angle))/self.z)) + 4.*cos(2.*self.pressure_angle +
(4.*sin(self.pitch_angle))/self.z) + 16.*cos(2.*self.pitch_angle + (4.*sin(self.pitch_angle))/self.z) +
4.*cos(2.*self.pressure_angle + 4.*self.pitch_angle + (4.*sin(self.pitch_angle))/self.z) + 32.*abs(cos(self.pitch_angle +
(2.*sin(self.pitch_angle))/self.z))*cos(self.pressure_angle)*sqrt(4.*cos(2.*self.pressure_angle) -
2.*(-2. + cos(2.*self.pressure_angle - 2.*self.pitch_angle) - 2.*cos(2.*self.pitch_angle) + cos(2.*(self.pressure_angle + self.pitch_angle)) +
4.*cos(2.*self.pitch_angle + (4.*sin(self.pitch_angle))/self.z)))*sin(2.*self.pitch_angle))/(-6. - 2.*cos(2.*self.pressure_angle) +
cos(2.*self.pressure_angle - 2.*self.pitch_angle) - 2.*cos(2.*self.pitch_angle) + cos(2.*(self.pressure_angle + self.pitch_angle)))**2))
1
/ sqrt(2)
* sqrt(
(
42.0
+ 16.0 * cos(2.0 * self.pressure_angle)
+ 6.0 * cos(4.0 * self.pressure_angle)
+ cos(4.0 * self.pressure_angle - 4.0 * self.pitch_angle)
- 8.0 * cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
- 4.0 * cos(4.0 * self.pressure_angle - 2.0 * self.pitch_angle)
+ 24.0 * cos(2.0 * self.pitch_angle)
- 2.0 * cos(4.0 * self.pitch_angle)
- 8.0 * cos(2.0 * (self.pressure_angle + self.pitch_angle))
+ cos(4.0 * (self.pressure_angle + self.pitch_angle))
- 4.0 * cos(4.0 * self.pressure_angle + 2.0 * self.pitch_angle)
+ 24.0 * cos((4.0 * sin(self.pitch_angle)) / self.z)
+ 4.0
* cos(
2.0 * self.pressure_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
+ 4.0
* cos(
2.0 * self.pressure_angle
- 4.0 * self.pitch_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
- 8.0
* cos(
2.0 * self.pressure_angle
- 2.0 * self.pitch_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
+ 24.0
* cos(4.0 * (self.pitch_angle + sin(self.pitch_angle) / self.z))
- 8.0
* cos(
2.0
* (
self.pressure_angle
+ self.pitch_angle
+ (2.0 * sin(self.pitch_angle)) / self.z
)
)
+ 4.0
* cos(
2.0 * self.pressure_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
+ 16.0
* cos(
2.0 * self.pitch_angle + (4.0 * sin(self.pitch_angle)) / self.z
)
+ 4.0
* cos(
2.0 * self.pressure_angle
+ 4.0 * self.pitch_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
+ 32.0
* abs(
cos(self.pitch_angle + (2.0 * sin(self.pitch_angle)) / self.z)
)
* cos(self.pressure_angle)
* sqrt(
4.0 * cos(2.0 * self.pressure_angle)
- 2.0
* (
-2.0
+ cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
- 2.0 * cos(2.0 * self.pitch_angle)
+ cos(2.0 * (self.pressure_angle + self.pitch_angle))
+ 4.0
* cos(
2.0 * self.pitch_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
)
)
* sin(2.0 * self.pitch_angle)
)
/ (
-6.0
- 2.0 * cos(2.0 * self.pressure_angle)
+ cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
- 2.0 * cos(2.0 * self.pitch_angle)
+ cos(2.0 * (self.pressure_angle + self.pitch_angle))
)
** 2
)
)
self.involute_start = -pi/2. + \
arctan(1/tan(self.pitch_angle)*1/cos(self.pressure_angle))
self.involute_start = -pi / 2.0 + arctan(
1 / tan(self.pitch_angle) * 1 / cos(self.pressure_angle)
)
self.involute_start_radius = self.get_radius(self.involute_start)
self.r_f = sin(self.pitch_angle - sin(pitch_angle) * 2 /
self.z) - self.clearance * sin(self.pitch_angle)
self.r_f = sin(
self.pitch_angle - sin(pitch_angle) * 2 / self.z
) - self.clearance * sin(self.pitch_angle)
self.z_f = cos(self.pitch_angle - sin(pitch_angle) * 2 / self.z)
self.add_foot = True
def involute_function_x(self):
def func(s):
return((
-(cos(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))*sin(self.pressure_angle)*sin(s)) +
(cos(s)*sin(self.pitch_angle) + cos(self.pressure_angle)*cos(self.pitch_angle)*sin(s)) *
sin(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))))
return(func)
return -(
cos(s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle))
* sin(self.pressure_angle)
* sin(s)
) + (
cos(s) * sin(self.pitch_angle)
+ cos(self.pressure_angle) * cos(self.pitch_angle) * sin(s)
) * sin(s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle))
return func
def involute_function_y(self):
def func(s):
return((
cos(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))*(cos(s)*sin(self.pitch_angle) +
cos(self.pressure_angle)*cos(self.pitch_angle)*sin(s)) + sin(self.pressure_angle)*sin(s) *
sin(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))))
return(func)
return cos(s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle)) * (
cos(s) * sin(self.pitch_angle)
+ cos(self.pressure_angle) * cos(self.pitch_angle) * sin(s)
) + sin(self.pressure_angle) * sin(s) * sin(
s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle)
)
return func
def involute_function_z(self):
def func(s):
return((
cos(self.pitch_angle)*cos(s) - cos(self.pressure_angle)*sin(self.pitch_angle)*sin(s)))
return(func)
return cos(self.pitch_angle) * cos(s) - cos(self.pressure_angle) * sin(
self.pitch_angle
) * sin(s)
return func
def get_radius(self, s):
x = self.involute_function_x()
y = self.involute_function_y()
rx = x(s)
ry = y(s)
return(sqrt(rx**2 + ry**2))
return sqrt(rx**2 + ry**2)
def involute_points(self, num=10):
pts = linspace(self.involute_start, self.involute_end, num=num)
@@ -104,36 +209,40 @@ class BevelTooth(object):
r_cut = self.r_f / self.z_f
for i, point in enumerate(xy[1:]):
if point.dot(point) >= r_cut ** 2:
if point.dot(point) >= r_cut**2:
break
if i > 0:
self.add_foot = False
intersection_point = intersection_line_circle(xy[i], point, r_cut)
xy = array([intersection_point] + list(xy[i+1:]))
xy = array([intersection_point] + list(xy[i + 1 :]))
xyz = [[p[0], p[1], 1] for p in xy]
backlash_rot = rotation3D(self.backlash / 4)
backlash_rot = rotation3D(self.angular_backlash / 2)
xyz = backlash_rot(xyz)
return(xyz)
return xyz
def points(self, num=10):
pts = self.involute_points(num=num)
rot = rotation3D(-pi/self.z/2)
rot = rotation3D(-pi / self.z / 2)
pts = rot(pts)
ref = reflection3D(pi/2)
ref = reflection3D(pi / 2)
pts1 = ref(pts)[::-1]
if self.add_foot:
return([
return [
array([pts[0], pts[1]]),
array(pts[1:]),
array([pts[-1], pts1[0]]),
array(pts1[:-1]),
array([pts1[-2], pts1[-1]])
])
array([pts1[-2], pts1[-1]]),
]
else:
return([pts, array([pts[-1], pts1[0]]), pts1])
return [pts, array([pts[-1], pts1[0]]), pts1]
def _update(self):
self.__init__(z=self.z, clearance=self.clearance,
pressure_angle=self.pressure_angle,
pitch_angle=self.pitch_angle,
backlash=self.backlash, module=self.module)
self.__init__(
z=self.z,
clearance=self.clearance,
pressure_angle=self.pressure_angle,
pitch_angle=self.pitch_angle,
backlash=self.backlash,
module=self.module,
)

View File

@@ -1,26 +1,22 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import numpy as np
from scipy import optimize as opt
def compute_shifted_gears(m, alpha, t1, t2, x1, x2):
@@ -37,10 +33,33 @@ def compute_shifted_gears(m, alpha, t1, t2, x1, x2):
Returns:
(float, float): distance between gears [length], pressure angle of the assembly [rad]
"""
def inv(x): return np.tan(x) - x
def inv(x):
return np.tan(x) - x
inv_alpha_w = inv(alpha) + 2 * np.tan(alpha) * (x1 + x2) / (t1 + t2)
def root_inv(x): return inv(x) - inv_alpha_w
alpha_w = opt.fsolve(root_inv, 0.)
def root_inv(x):
return inv(x) - inv_alpha_w
def d_root_inv(x):
return 1.0 / np.cos(x) - 1
alpha_w = find_root(alpha, root_inv, d_root_inv)
dist = m * (t1 + t2) / 2 * np.cos(alpha) / np.cos(alpha_w)
return dist, alpha_w
def find_root(x0, f, df, epsilon=2e-10, max_iter=100):
x_n = x0
for i in range(max_iter):
f_xn = f(x_n)
if abs(f_xn) < epsilon:
return x_n
else:
df_xn = df(x_n)
if df_xn == 0:
return None
else:
x_n = x_n - f_xn / df_xn / 2 # adding (/ 2) to avoid oscillation
return None

View File

@@ -1,21 +1,18 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
@@ -24,14 +21,15 @@ from numpy import cos, sin, arccos, pi, array, linspace, transpose, vstack
from ._functions import rotation, reflection
class CycloidTooth():
def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.12, backlash=0.00):
class CycloidTooth:
def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.25, backlash=0.00, head=0.0):
self.m = m
self.z = z
self.clearance = clearance
self.backlash = backlash
self.z1 = z1
self.z2 = z2
self.head = head
self._calc_gear_factors()
def _calc_gear_factors(self):
@@ -39,46 +37,65 @@ class CycloidTooth():
self.d2 = self.z2 * self.m
self.phi = self.m * pi
self.d = self.z * self.m
self.da = self.d + 2*self.m
self.di = self.d - 2*self.m - self.clearance * self.m
self.da = self.d + 2 * (1 + self.head) * self.m
self.di = self.d - 2 * (1 + self.clearance) * self.m
self.phipart = 2 * pi / self.z
self.angular_backlash = self.backlash / (self.d / 2)
def epicycloid_x(self):
def func(t):
return(((self.d2 + self.d) * cos(t))/2. - (self.d2 * cos((1 + self.d / self.d2) * t))/2.)
return(func)
return ((self.d2 + self.d) * cos(t)) / 2.0 - (
self.d2 * cos((1 + self.d / self.d2) * t)
) / 2.0
return func
def epicycloid_y(self):
def func(t):
return(((self.d2 + self.d) * sin(t))/2. - (self.d2 * sin((1 + self.d / self.d2) * t))/2.)
return(func)
return ((self.d2 + self.d) * sin(t)) / 2.0 - (
self.d2 * sin((1 + self.d / self.d2) * t)
) / 2.0
return func
def hypocycloid_x(self):
def func(t):
return((self.d - self.d1)*cos(t)/2 + self.d1/2 * cos((self.d / self.d1 - 1) * t))
return(func)
return (self.d - self.d1) * cos(t) / 2 + self.d1 / 2 * cos(
(self.d / self.d1 - 1) * t
)
return func
def hypocycloid_y(self):
def func(t):
return((self.d - self.d1)*sin(t)/2 - self.d1/2 * sin((self.d/self.d1 - 1)*t))
return(func)
return (self.d - self.d1) * sin(t) / 2 - self.d1 / 2 * sin(
(self.d / self.d1 - 1) * t
)
return func
def inner_end(self):
return(
-((self.d1*arccos((2*self.d1**2 - self.di**2 -
2*self.d1*self.d + self.d**2)/(2.*self.d1 *
(self.d1 - self.d))))/self.d)
return -(
(
self.d1
* arccos(
(2 * self.d1**2 - self.di**2 - 2 * self.d1 * self.d + self.d**2)
/ (2.0 * self.d1 * (self.d1 - self.d))
)
)
/ self.d
)
def outer_end(self):
return(
(self.d2*arccos((2*self.d2**2 - self.da**2 +
2*self.d2*self.d + self.d**2) /
(2.*self.d2*(self.d2 + self.d))))/self.d
)
return (
self.d2
* arccos(
(2 * self.d2**2 - self.da**2 + 2 * self.d2 * self.d + self.d**2)
/ (2.0 * self.d2 * (self.d2 + self.d))
)
) / self.d
def points(self, num=10):
inner_x = self.hypocycloid_x()
inner_y = self.hypocycloid_y()
outer_x = self.epicycloid_x()
@@ -94,14 +111,20 @@ class CycloidTooth():
pts_outer = transpose([pts_outer_x, pts_outer_y])
pts_inner = transpose([pts_inner_x, pts_inner_y])
pts1 = vstack([pts_inner[:-2], pts_outer])
rot = rotation(self.phipart / 4 - self.backlash)
rot = rotation(self.phipart / 4 - self.angular_backlash / 2)
pts1 = rot(pts1)
ref = reflection(0.)
ref = reflection(0.0)
pts2 = ref(pts1)[::-1]
one_tooth = [pts1, array([pts1[-1], pts2[0]]), pts2]
return(one_tooth)
return one_tooth
def _update(self):
self.__init__(m=self.m, z=self.z, z1=self.z1, z2=self.z2,
clearance=self.clearance, backlash=self.backlash)
self.__init__(
m=self.m,
z=self.z,
z1=self.z1,
z2=self.z2,
clearance=self.clearance,
backlash=self.backlash,
head=self.head,
)

View File

@@ -1,32 +1,59 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * 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 Library General Public License for more details. *
# * 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 Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
from __future__ import division
from numpy import tan, cos, sin, sqrt, arctan, pi, array, linspace, transpose, vstack, ndarray
from ._functions import nearestpts, rotation, reflection, trimfunc, diff_norm, translation
from numpy import (
tan,
cos,
sin,
sqrt,
arctan,
pi,
array,
linspace,
transpose,
vstack,
ndarray,
)
from ._functions import (
nearestpts,
rotation,
reflection,
trimfunc,
diff_norm,
translation,
)
class InvoluteTooth():
def __init__(self, m=5, z=15, pressure_angle=20 * pi / 180., clearance=0.12, shift=0.5, beta=0.,
undercut=False, backlash=0.00, head=0.00, properties_from_tool=False):
class InvoluteTooth:
def __init__(
self,
m=5,
z=15,
pressure_angle=20 * pi / 180.0,
clearance=0.12,
shift=0.5,
beta=0.0,
undercut=False,
backlash=0.00,
head=0.00,
properties_from_tool=False,
):
self.pressure_angle = pressure_angle
self.beta = beta
self.m_n = m
@@ -35,14 +62,13 @@ class InvoluteTooth():
self.shift = shift
self.clearance = clearance
self.backlash = backlash
self.head = head # factor, rename!!!
self.head = head # factor, rename!!!
self.properties_from_tool = properties_from_tool
self._calc_gear_factors()
def _calc_gear_factors(self):
if self.properties_from_tool:
self.pressure_angle_t = arctan(
tan(self.pressure_angle) / cos(self.beta))
self.pressure_angle_t = arctan(tan(self.pressure_angle) / cos(self.beta))
self.m = self.m_n / cos(self.beta)
else:
self.pressure_angle_t = self.pressure_angle
@@ -50,31 +76,47 @@ class InvoluteTooth():
self.pitch = self.m * pi
self.c = self.clearance * self.m_n
self.midpoint = [0., 0.]
self.midpoint = [0.0, 0.0]
self.d = self.z * self.m
self.dw = self.m * self.z
self.da = self.dw + 2. * self.m_n + 2. * \
(self.shift + self.head) * self.m_n
self.df = self.dw - 2. * self.m_n - \
2 * self.c + 2. * self.shift * self.m_n
self.da = self.dw + 2.0 * self.m_n + 2.0 * (self.shift + self.head) * self.m_n
self.df = self.dw - 2.0 * self.m_n - 2 * self.c + 2.0 * self.shift * self.m_n
self.dg = self.d * cos(self.pressure_angle_t)
self.phipart = 2 * pi / self.z
self.undercut_end = sqrt(-self.df ** 2 + self.da ** 2) / self.da
self.undercut_rot = (-self.df / self.dw * tan(arctan((2 * ((self.m * pi) / 4. -
(self.c + self.m_n) * tan(self.pressure_angle_t))) / self.df)))
self.undercut_end = sqrt(-(self.df**2) + self.da**2) / self.da
self.undercut_rot = (
-self.df
/ self.dw
* tan(
arctan(
(
2
* (
(self.m * pi) / 4.0
- (self.c + self.m_n) * tan(self.pressure_angle_t)
)
)
/ self.df
)
)
)
self.involute_end = sqrt(self.da ** 2 - self.dg ** 2) / self.dg
self.involute_rot1 = sqrt(-self.dg ** 2 + (self.dw) ** 2) / self.dg - arctan(
sqrt(-self.dg ** 2 + (self.dw) ** 2) / self.dg)
self.involute_rot2 = self.m / \
(self.d) * (pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
self.involute_rot2 = 1 / self.z * \
(pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
self.involute_end = sqrt(self.da**2 - self.dg**2) / self.dg
self.involute_rot1 = sqrt(-(self.dg**2) + (self.dw) ** 2) / self.dg - arctan(
sqrt(-(self.dg**2) + (self.dw) ** 2) / self.dg
)
self.involute_rot2 = (
self.m / (self.d) * (pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
)
self.involute_rot2 = (
1 / self.z * (pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
)
self.involute_rot = self.involute_rot1 + self.involute_rot2
self.involute_start = 0.
self.angular_backlash = self.backlash / (self.d / 2)
self.involute_start = 0.0
if self.dg <= self.df:
self.involute_start = sqrt(self.df ** 2 - self.dg ** 2) / self.dg
self.involute_start = sqrt(self.df**2 - self.dg**2) / self.dg
def undercut_points(self, num=10):
pts = linspace(0, self.undercut_end, num=num)
@@ -84,9 +126,10 @@ class InvoluteTooth():
y = array(list(map(fy, pts)))
xy = transpose([x, y])
rotate = rotation(
self.undercut_rot + self.phipart / 2 - self.backlash / 4)
self.undercut_rot + self.phipart / 2 - self.angular_backlash / 2
)
xy = rotate(xy)
return(array(xy))
return array(xy)
def involute_points(self, num=10):
pts = linspace(self.involute_start, self.involute_end, num=num)
@@ -94,9 +137,9 @@ class InvoluteTooth():
x = array(list(map(fx, pts)))
fy = self.involute_function_y()
y = array(list(map(fy, pts)))
rot = rotation(self.involute_rot - self.backlash / 4)
rot = rotation(self.involute_rot - self.angular_backlash / 2)
xy = rot(transpose(array([x, y])))
return(xy)
return xy
def points(self, num=10):
l1 = self.undercut_points(num=num)
@@ -111,7 +154,8 @@ class InvoluteTooth():
u1 = False
if self.dg > self.df:
u1 = vstack(
[[l2[0] * self.df / (diff_norm(l2[0], [0, 0]) * 2)], [l2[0]]])
[[l2[0] * self.df / (diff_norm(l2[0], [0, 0]) * 2)], [l2[0]]]
)
e1 = l2
else:
e1 = l2
@@ -123,35 +167,39 @@ class InvoluteTooth():
else:
u2 = reflect(u1)[::-1]
one_tooth = [u1, e1, [e1[-1], e2[0]], e2, u2]
return(one_tooth)
return one_tooth
def gearfunc(self, x):
rot = rotation(2 * x / self.dw, self.midpoint)
return(rot)
return rot
def undercut_function_x(self):
def func(psi):
return(
cos(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 +
(self.df ** 2 * tan(psi) ** 2) / 4.))
return(func)
return cos(psi - (self.df * tan(psi)) / self.dw) * sqrt(
self.df**2 / 4 + (self.df**2 * tan(psi) ** 2) / 4.0
)
return func
def undercut_function_y(self):
def func(psi):
return(
sin(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 +
(self.df ** 2 * tan(psi) ** 2) / 4.))
return(func)
return sin(psi - (self.df * tan(psi)) / self.dw) * sqrt(
self.df**2 / 4 + (self.df**2 * tan(psi) ** 2) / 4.0
)
return func
def involute_function_x(self):
def func(phi):
return(self.dg / 2 * cos(phi) + phi * self.dg / 2 * sin(phi))
return(func)
return self.dg / 2 * cos(phi) + phi * self.dg / 2 * sin(phi)
return func
def involute_function_y(self):
def func(phi):
return(self.dg / 2 * sin(phi) - phi * self.dg / 2 * cos(phi))
return(func)
return self.dg / 2 * sin(phi) - phi * self.dg / 2 * cos(phi)
return func
def _update(self):
if not hasattr(self, "properties_from_tool"):
@@ -160,8 +208,19 @@ class InvoluteTooth():
class InvoluteRack(object):
def __init__(self, m=5, z=15, pressure_angle=20 * pi / 180., thickness=5, beta=0, head=0, clearance=0.25,
properties_from_tool=False, add_endings=False, simplified=False):
def __init__(
self,
m=5,
z=15,
pressure_angle=20 * pi / 180.0,
thickness=5,
beta=0,
head=0,
clearance=0.25,
properties_from_tool=False,
add_endings=False,
simplified=False,
):
self.pressure_angle = pressure_angle
self.thickness = thickness
self.m = m
@@ -173,8 +232,7 @@ class InvoluteRack(object):
self.add_endings = add_endings
self.simplified = simplified
# this is not good. Find better way to stay backward compatible -> versions
# this is not good. Find better way to stay backward compatible -> versions
def _update(self):
if not hasattr(self, "add_endings"):
self.add_endings = True
@@ -182,7 +240,6 @@ class InvoluteRack(object):
self.simplified = False
def points(self, num=10):
import copy
m, m_n, pitch, pressure_angle_t = self.compute_properties()
a = (2 + self.head + self.clearance) * m_n * tan(pressure_angle_t)
@@ -191,21 +248,21 @@ class InvoluteRack(object):
[-m_n * (1 + self.clearance), -a - b],
[m_n * (1 + self.head), -b],
[m_n * (1 + self.head), b],
[-m_n * (1 + self.clearance), a + b]
[-m_n * (1 + self.clearance), a + b],
]
teeth = [tooth]
trans = translation([0., pitch, 0.])
trans = translation([0.0, pitch, 0.0])
for i in range(self.z - 1):
if self.simplified and i > 3 and i < (self.z - 6):
tooth = trans(tooth).tolist()
else:
tooth = trans(tooth).tolist()
teeth.append(copy.deepcopy(tooth))
teeth.append(tooth.copy())
if self.simplified and (i == 3):
teeth[-1].pop()
teeth[-1].pop()
teeth[-1][-1][0] = 0
teeth[-1][-1][1] -= a / 2
teeth[-1][-1][1] -= a / 2
if self.simplified and (i == self.z - 6):
teeth[-1].pop(0)
teeth[-1].pop(0)
@@ -214,14 +271,18 @@ class InvoluteRack(object):
teeth = array([v for t in teeth for v in t]) # flattening
if self.add_endings:
ext1 = teeth[0] + array([0., a + b - pitch / 2])
ext2 = teeth[-1] - array([0., a + b - pitch / 2])
teeth = [ext1.tolist(), ext1.tolist()] + teeth.tolist() + [ext2.tolist(), ext2.tolist()]
ext1 = teeth[0] + array([0.0, a + b - pitch / 2])
ext2 = teeth[-1] - array([0.0, a + b - pitch / 2])
teeth = (
[ext1.tolist(), ext1.tolist()]
+ teeth.tolist()
+ [ext2.tolist(), ext2.tolist()]
)
else:
teeth = [teeth[0].tolist()] + teeth.tolist() + [teeth[-1].tolist()]
#teeth.append(list(teeth[-1]))
# teeth.append(list(teeth[-1]))
teeth[0][0] -= self.thickness
#teeth.append(list(teeth[0]))
# teeth.append(list(teeth[0]))
teeth[-1][0] -= self.thickness
teeth.append(teeth[0])
return array(teeth)
@@ -234,7 +295,7 @@ class InvoluteRack(object):
else:
pressure_angle_t = self.pressure_angle
m = self.m
m_n = self.m
m_n = self.m
pitch = m * pi
return m, m_n, pitch, pressure_angle_t

View File

@@ -7,13 +7,14 @@ from ._functions import rotation, rotation3D
class _GearProfile(object):
rot3D = False
def profile(self, num=10):
tooth = self.points(num=num)
tooth = [list(point) for wire in tooth for point in wire]
if self.rot3D:
rot = rotation3D( np.pi * 2 / self.z)
rot = rotation3D(np.pi * 2 / self.z)
else:
rot = rotation(- np.pi * 2 / self.z)
rot = rotation(-np.pi * 2 / self.z)
profile = tooth
for i in range(self.z - 1):
tooth = rot(tooth).tolist()
@@ -21,19 +22,19 @@ class _GearProfile(object):
profile.append(profile[0])
return np.array(profile)
class InvoluteProfile(InvoluteTooth, _GearProfile):
pass
class CycloidProfile(CycloidTooth, _GearProfile):
pass
class BevelProfile(BevelTooth, _GearProfile):
rot3D = True
class InvoluteRackProfile(InvoluteRack):
def profile(self):
return self.points()

View File

@@ -1,15 +1,14 @@
from setuptools import setup
from pygears import __version__
setup(name='freecad.gears',
version=str(__version__),
packages=['freecad',
'freecad.gears',
'pygears'],
maintainer="looooo",
maintainer_email="sppedflyer@gmail.com",
url="https://github.com/looooo/FCGear",
description="gears for FreeCAD",
install_requires=['numpy'],
include_package_data=True
setup(
name="freecad.gears",
version=str(__version__),
packages=["freecad", "freecad.gears", "pygears"],
maintainer="looooo",
maintainer_email="sppedflyer@gmail.com",
url="https://github.com/looooo/FCGear",
description="gears for FreeCAD",
install_requires=["numpy", "scipy"],
include_package_data=True,
)