10 Commits

Author SHA1 Message Date
lorenz
e1c80bd963 add spicy dependency 2022-06-11 18:25:41 +02:00
lorenz
109df352a9 InvoluteGearRack: properties_from_tool default = False 2022-05-03 14:48:05 +02:00
looooo
165f1a0aea add package.xml 2022-02-24 08:39:28 +01:00
Jonas Bähr
8258477645 add head parameter for cycloide gear
This is a backport of commit 9ddd493b from develop, with two changes:
- unrelated changes have not been back ported
- backwards compatibility was added

Note that the actual usage of the head value in pygear is wrong, though:
As it is added to the diameter (not the radius) it should have been added
twice. However, as the clearance suffers from the same bug, I choose to
stay consistent. A fix should address both, head and clearance, but this
is out of scope here.
2022-01-04 20:08:35 +01:00
Jonas Bähr
6ce203ea8f Make gear creation "undoable"
Right now, the tool tip is used as transaction name, which matches the
wording "Create a new sketch". There doesn't seem to be consistency
though FreeCAD's other workbenches, though.
2021-10-23 01:43:24 +02:00
Jonas Bähr
69ff30d65e gitignore VIM swap files 2021-10-23 01:43:24 +02:00
lorenz
3c86d12dc7 fix lgtm allert 2021-09-30 18:12:29 +02:00
Jonas Bähr
d489dfb841 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-25 16:33:58 +02:00
Jonas Bähr
1b19d16264 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-25 16:33:58 +02:00
Jonas Bähr
d5e12ef116 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-25 16:33:58 +02:00
45 changed files with 1661 additions and 6458 deletions

View File

@@ -1,23 +0,0 @@
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')

3
.gitignore vendored
View File

@@ -59,3 +59,6 @@ target/
.ipynb_checkpoints/
results/
*.vtk
# VIM
*.swp

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)
[![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)
## Requirements
FreeCAD > v0.16
@@ -26,10 +26,6 @@ __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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -1,434 +0,0 @@
{
"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.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,21 +1,23 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
import pygears
__version__ = pygears.__version__

View File

@@ -1,672 +0,0 @@
# -*- 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

View File

@@ -1,206 +0,0 @@
# -*- 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,44 +1,29 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
import os
import FreeCAD
import FreeCADGui as Gui
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
from .features import ViewProviderGear, InvoluteGear, InvoluteGearRack
from .features import CycloidGear, BevelGear, CrownGear, WormGear, TimingGear, LanternGear, HypoCycloidGear
class BaseCommand(object):
@@ -56,11 +41,13 @@ class BaseCommand(object):
return True
def Activated(self):
doc = FreeCAD.ActiveDocument
Gui.doCommandGui("import freecad.gears.commands")
Gui.doCommandGui(
"freecad.gears.commands.{}.create()".format(self.__class__.__name__)
)
FreeCAD.ActiveDocument.recompute()
doc.openTransaction(self.ToolTip)
Gui.doCommandGui("freecad.gears.commands.{}.create()".format(
self.__class__.__name__))
doc.commitTransaction()
doc.recompute()
Gui.SendMsgToActiveView("ViewFit")
@classmethod
@@ -72,12 +59,10 @@ 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, cls.Pixmap)
ViewProviderGear(obj.ViewObject)
cls.GEAR_FUNCTION(obj)
if body:
@@ -90,128 +75,76 @@ 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 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"
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involutegear.svg')
MenuText = 'Involute gear'
ToolTip = 'Create an 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"
class CreateCycloidRack(BaseCommand):
NAME = "CycloidRack"
GEAR_FUNCTION = CycloidGearRack
Pixmap = os.path.join(BaseCommand.ICONDIR, "cycloidrack.svg")
MenuText = "Cycloid Rack"
ToolTip = "Create an Cycloid rack"
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involuterack.svg')
MenuText = 'Involute rack'
ToolTip = 'Create an Involute 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"
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"
Pixmap = os.path.join(BaseCommand.ICONDIR, 'wormgear.svg')
MenuText = 'Worm gear'
ToolTip = 'Create a Worm gear'
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"
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
Pixmap = os.path.join(BaseCommand.ICONDIR, 'lanterngear.svg')
MenuText = 'Lantern gear'
ToolTip = 'Create a Lantern gear'

View File

@@ -1,208 +0,0 @@
# -*- 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)

View File

@@ -1,143 +0,0 @@
# -*- 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

@@ -1,193 +0,0 @@
# -*- 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

@@ -1,232 +0,0 @@
# -*- 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

@@ -1,283 +0,0 @@
# -*- 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

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,404 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,153 +1,75 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
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"
)
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,
)
)
"the class Workbench is loaded, although not imported: magic")
class GearWorkbench(Workbench):
"""A freecad workbench aiming at gear design"""
"""glider workbench"""
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",
"CreateGearConnector",
]
"CreateHypoCycloidGear"]
def GetClassName(self):
return "Gui::PythonWorkbench"
def Initialize(self):
from .commands import (
CreateCycloidGear,
CreateInvoluteGear,
CreateInternalInvoluteGear,
CreateBevelGear,
CreateInvoluteRack,
CreateCrownGear,
CreateWormGear,
CreateTimingGearT,
CreateTimingGear,
CreateLanternGear,
CreateHypoCycloidGear,
CreateCycloidRack,
CreateGearConnector,
)
from .commands import CreateCycloidGear, CreateInvoluteGear
from .commands import CreateBevelGear, CreateInvoluteRack, CreateCrownGear
from .commands import CreateWormGear, CreateTimingGear, CreateLanternGear
from .commands import CreateHypoCycloidGear
self.appendToolbar("Gear", self.commands)
self.appendMenu("Gear", self.commands)
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())
# 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())
def Activated(self):
pass

View File

@@ -1,248 +0,0 @@
# -*- 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

@@ -1,264 +0,0 @@
# -*- 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

@@ -1,239 +0,0 @@
# -*- 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

@@ -1,133 +0,0 @@
# -*- 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))

View File

@@ -1,285 +0,0 @@
# -*- 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

@@ -1,117 +0,0 @@
# -*- 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))

View File

@@ -1,151 +0,0 @@
# -*- 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

View File

@@ -2,11 +2,12 @@
<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>
<version>1.0</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>
<dep>scipy</dep>
<url type="repository" branch="develop">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>

View File

@@ -1,19 +1,22 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
__version__ = "1.2.0"
__version__ = "0.0.3"

View File

@@ -1,18 +1,21 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
@@ -22,64 +25,55 @@ 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):
# why not use mat @ x???
return dot(x, mat)
return func
return(dot(x, mat))
return(func)
def reflection3D(angle):
mat = array(
[
[cos(2 * angle), -sin(2 * angle), 0.0],
[-sin(2 * angle), -cos(2 * angle), 0.0],
[0.0, 0.0, 1.0],
]
)
mat = array([[cos(2 * angle), -sin(2 * angle), 0.],
[-sin(2 * angle), -cos(2 * angle), 0.], [0., 0., 1.]])
def func(x):
# why not use mat @ x
return dot(x, mat)
return func
return(dot(x, mat))
return(func)
def rotation(angle, midpoint=None):
midpoint = midpoint or [0.0, 0.0]
mat = array([[cos(angle), -sin(angle)], [sin(angle), cos(angle)]])
midpoint = midpoint or [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.0], [sin(angle), cos(angle), 0.0], [0.0, 0.0, 1.0]]
)
[
[cos(angle), -sin(angle), 0.],
[sin(angle), cos(angle), 0.],
[0., 0., 1.]])
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):
@@ -89,31 +83,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.0 < g < 1.0 and 0.0 < h < 1.0:
return a1 + g * (a2 - a1)
if 0. < g < 1. and 0. < h < 1.:
return(a1 + g * (a2 - a1))
else:
return False
return(False)
def trimfunc(l1, l2):
@@ -133,12 +127,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):
@@ -150,7 +144,7 @@ def nearestpts(evolv, underc):
ik = 0
iout = 0
jout = 0
outmin = 1000.0
outmin = 1000.
for i in array(evolv[1:]):
jk = 0
for j in array(underc[1:]):
@@ -163,17 +157,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
@@ -185,3 +179,5 @@ 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,198 +1,93 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
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.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
)
)
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))
self.involute_start = -pi / 2.0 + arctan(
1 / tan(self.pitch_angle) * 1 / cos(self.pressure_angle)
)
self.involute_start = -pi/2. + \
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)
@@ -209,40 +104,36 @@ 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.angular_backlash / 2)
backlash_rot = rotation3D(self.backlash / 4)
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,22 +1,26 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
import numpy as np
from scipy import optimize as opt
def compute_shifted_gears(m, alpha, t1, t2, x1, x2):
@@ -33,33 +37,10 @@ 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
def d_root_inv(x):
return 1.0 / np.cos(x) - 1
alpha_w = find_root(alpha, root_inv, d_root_inv)
def root_inv(x): return inv(x) - inv_alpha_w
alpha_w = opt.fsolve(root_inv, 0.)
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,18 +1,21 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
@@ -21,15 +24,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.25, backlash=0.00, head=0.0):
class CycloidTooth():
def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.12, backlash=0.00, head=0.0):
self.m = m
self.z = z
self.clearance = clearance
self.head = head
self.backlash = backlash
self.z1 = z1
self.z2 = z2
self.head = head
self._calc_gear_factors()
def _calc_gear_factors(self):
@@ -37,65 +40,46 @@ class CycloidTooth:
self.d2 = self.z2 * self.m
self.phi = self.m * pi
self.d = self.z * self.m
self.da = self.d + 2 * (1 + self.head) * self.m
self.di = self.d - 2 * (1 + self.clearance) * self.m
self.da = self.d + 2*self.m + self.head * self.m
self.di = self.d - 2*self.m - 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.0 - (
self.d2 * cos((1 + self.d / self.d2) * t)
) / 2.0
return func
return(((self.d2 + self.d) * cos(t))/2. - (self.d2 * cos((1 + self.d / self.d2) * t))/2.)
return(func)
def epicycloid_y(self):
def func(t):
return ((self.d2 + self.d) * sin(t)) / 2.0 - (
self.d2 * sin((1 + self.d / self.d2) * t)
) / 2.0
return func
return(((self.d2 + self.d) * sin(t))/2. - (self.d2 * sin((1 + self.d / self.d2) * t))/2.)
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.0 * 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.*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.0 * 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.*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()
@@ -111,20 +95,14 @@ 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.angular_backlash / 2)
rot = rotation(self.phipart / 4 - self.backlash)
pts1 = rot(pts1)
ref = reflection(0.0)
ref = reflection(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,
head=self.head,
)
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,59 +1,32 @@
# -*- 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 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 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. *
# * 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. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * 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 *
# * *
# ***************************************************************************
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.0,
clearance=0.12,
shift=0.5,
beta=0.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., clearance=0.12, shift=0.5, beta=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
@@ -62,13 +35,14 @@ 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
@@ -76,47 +50,31 @@ class InvoluteTooth:
self.pitch = self.m * pi
self.c = self.clearance * self.m_n
self.midpoint = [0.0, 0.0]
self.midpoint = [0., 0.]
self.d = self.z * self.m
self.dw = self.m * self.z
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.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.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.0
- (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. -
(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.angular_backlash = self.backlash / (self.d / 2)
self.involute_start = 0.0
self.involute_start = 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)
@@ -126,10 +84,9 @@ class InvoluteTooth:
y = array(list(map(fy, pts)))
xy = transpose([x, y])
rotate = rotation(
self.undercut_rot + self.phipart / 2 - self.angular_backlash / 2
)
self.undercut_rot + self.phipart / 2 - self.backlash / 4)
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)
@@ -137,9 +94,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.angular_backlash / 2)
rot = rotation(self.involute_rot - self.backlash / 4)
xy = rot(transpose(array([x, y])))
return xy
return(xy)
def points(self, num=10):
l1 = self.undercut_points(num=num)
@@ -154,8 +111,7 @@ 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
@@ -167,39 +123,35 @@ 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.0
)
return func
return(
cos(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 +
(self.df ** 2 * tan(psi) ** 2) / 4.))
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.0
)
return func
return(
sin(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 +
(self.df ** 2 * tan(psi) ** 2) / 4.))
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"):
@@ -208,19 +160,8 @@ class InvoluteTooth:
class InvoluteRack(object):
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,
):
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):
self.pressure_angle = pressure_angle
self.thickness = thickness
self.m = m
@@ -232,7 +173,8 @@ 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
@@ -240,6 +182,7 @@ 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)
@@ -248,21 +191,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.0, pitch, 0.0])
trans = translation([0., pitch, 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(tooth.copy())
teeth.append(copy.deepcopy(tooth))
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)
@@ -271,18 +214,14 @@ class InvoluteRack(object):
teeth = array([v for t in teeth for v in t]) # flattening
if self.add_endings:
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()]
)
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()]
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)
@@ -295,7 +234,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,14 +7,13 @@ 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()
@@ -22,19 +21,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,14 +1,15 @@
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", "scipy"],
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'],
include_package_data=True
)