33 Commits

Author SHA1 Message Date
looooo
85ae781b73 add basegear and keep feature.py 2024-01-02 02:02:20 +01:00
looooo
0ea2f229b7 Revert "rename features to basegear"
This reverts commit 878811ae54.
2024-01-02 01:53:29 +01:00
looooo
878811ae54 rename features to basegear 2024-01-02 01:37:03 +01:00
looooo
0d10df1997 ruff formating 2024-01-02 01:28:04 +01:00
looooo
7c747bf151 refactoring (split in different files)
make explicitly dependent on scipy
2024-01-02 01:25:46 +01:00
looooo
9e14aac76b add timing gear t-shape 2024-01-02 00:02:42 +01:00
looooo
17715c0754 update notebook 2024-01-01 16:07:33 +01:00
looooo
cb22a878a5 add example for worm cutting 2023-12-29 15:38:18 +01:00
lorenz
37e2b04c7e add theory for a arbitrary rack 2023-12-28 12:47:13 +01:00
lorenz
a3dfa1229e Update init_gui.py 2023-12-26 13:14:03 +01:00
lorenz
7076d2ff4b Update __init__.py 2023-12-26 13:14:03 +01:00
Syres916
6197bfdc16 Update package.xml 2023-12-26 13:14:03 +01:00
Syres916
48f3f4a7d3 [Gears] Allow for both Python 3.11 and above as...
well as 3.10 and below as they are all currently supported by FreeCAD core.
2023-12-26 13:14:03 +01:00
Syres916
6d04fb1031 [Gears] Check if Python 3.11 or above is in use
and if so is the version of FreeCAD sufficient to properly handle pickle of JSON data
2023-12-26 13:14:03 +01:00
looooo
0d183f691c ruff format 2023-12-25 23:13:36 +01:00
looooo
17f04bfde4 ruff format pygears 2023-12-25 23:11:41 +01:00
looooo
22baac93bd __getstate__, __setstate__ -> loads, dumps 2023-12-25 14:15:31 +01:00
lorenz
681ad0a222 Create pylint.yml 2023-12-24 19:03:53 +01:00
Syres916
7af3806181 [Gears] Fix AddonManager not updating to...
...most recent master commit
2023-11-27 17:16:57 +01:00
Syres916
fe626176a3 [Gears] Remove unused import 2023-11-27 13:07:37 +01:00
Syres916
c6f09bd864 [Gears] Remove Deepcopy dependency 2023-11-27 13:07:37 +01:00
Syres916
e6e6e9b7d4 [Gears] Remove dependency on Deepcopy 2023-11-27 13:07:37 +01:00
Scott Mudge
0eb9913420 fixed issue loading old document without traverse_module existing in gear XML 2023-10-15 11:11:12 +02:00
BearTM
2def33833a Update features.py to support HTD Timing Gear Profiles
Added 3M, 5M, 8M HTD Timing Gear Profiles
Corrected GT8 Profile naming from "htd8" to "gt8"
2023-09-24 13:14:48 +02:00
luzpaz
2f21661462 Remove LGTM badges from README.md
LGTM was folded into Gitub (https://github.blog/2022-08-15-the-next-step-for-lgtm-com-github-code-scanning/)
2023-09-11 17:14:04 +02:00
looooo
80fbd3d49e internal gear: don't use expression for dw 2023-01-22 20:58:36 +01:00
looooo
b38bfda072 remove expression for involute gear dw, it doesn't update correctly 2023-01-22 19:42:41 +01:00
looooo
23a649cd23 add gear-shaft example 2023-01-17 20:03:13 +01:00
looooo
bc41c864a1 traverse pitch computation 2022-12-28 13:32:51 +01:00
looooo
84026d912b fix involute-gear computation 2022-12-15 19:15:46 +01:00
lorenz
ba87611235 fix computed property dw 2022-12-15 18:25:44 +01:00
looooo
29e45aa613 fix rack traverse_pitch 2022-10-23 17:34:23 +02:00
Benjamin Vedder
60db69f302 Added HTD8 timing gear 2022-09-08 13:36:58 +02:00
39 changed files with 4865 additions and 2151 deletions

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

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

View File

@@ -1,5 +1,5 @@
## A Gear module for FreeCAD ## A Gear module for FreeCAD
[![Liberapay](http://img.shields.io/liberapay/patrons/looooo.svg?logo=liberapay)](https://liberapay.com/looooo/donate) [![Total alerts](https://img.shields.io/lgtm/alerts/g/looooo/freecad.gears.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/looooo/freecad.gears/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/looooo/freecad.gears.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/looooo/freecad.gears/context:python) [![Liberapay](http://img.shields.io/liberapay/patrons/looooo.svg?logo=liberapay)](https://liberapay.com/looooo/donate)
## Requirements ## Requirements
FreeCAD > v0.16 FreeCAD > v0.16

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
examples/gear-shaft.FCStd Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
examples/worm_cutting_tool/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -17,4 +17,5 @@
# *************************************************************************** # ***************************************************************************
import pygears import pygears
__version__ = pygears.__version__ __version__ = pygears.__version__

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

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

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

@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation3D
from .basegear import BaseGear, fcvec, make_bspline_wire
class BevelGear(BaseGear):
"""parameters:
pressure_angle: pressureangle, 10-30°
pitch_angle: cone angle, 0 < pitch_angle < pi/4
"""
def __init__(self, obj):
super(BevelGear, self).__init__(obj)
self.bevel_tooth = BevelTooth()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyAngle", "pitch_angle", "involute", "pitch_angle")
obj.addProperty(
"App::PropertyAngle",
"pressure_angle",
"involute_parameter",
"pressure_angle",
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"precision",
"number of points for spline",
)
obj.addProperty(
"App::PropertyBool",
"reset_origin",
"base",
"if value is true the gears outer face will match the z=0 plane",
)
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty("App::PropertyPythonObject", "gear", "base", "test")
obj.addProperty(
"App::PropertyAngle", "beta", "helical", "angle used for spiral bevel-gears"
)
obj.addProperty("App::PropertyLength", "dw", "computed", "The pitch diameter.")
obj.setExpression(
"dw", "teeth * module"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"dw", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.gear = self.bevel_tooth
obj.module = "1. mm"
obj.teeth = 15
obj.pressure_angle = "20. deg"
obj.pitch_angle = "45. deg"
obj.height = "5. mm"
obj.numpoints = 6
obj.backlash = "0.00 mm"
obj.clearance = 0.1
obj.beta = "0 deg"
obj.reset_origin = True
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
fp.gear.z = fp.teeth
fp.gear.module = fp.module.Value
fp.gear.pressure_angle = (90 - fp.pressure_angle.Value) * np.pi / 180.0
fp.gear.pitch_angle = fp.pitch_angle.Value * np.pi / 180
max_height = fp.gear.module * fp.teeth / 2 / np.tan(fp.gear.pitch_angle)
if fp.height >= max_height:
App.Console.PrintWarning(
"height must be smaller than {}".format(max_height)
)
fp.gear.backlash = fp.backlash.Value
scale = (
fp.module.Value * fp.gear.z / 2 / np.tan(fp.pitch_angle.Value * np.pi / 180)
)
fp.gear.clearance = fp.clearance / scale
fp.gear._update()
pts = list(fp.gear.points(num=fp.numpoints))
rot = rotation3D(2 * np.pi / fp.teeth)
# if fp.beta.Value != 0:
# pts = [np.array([self.spherical_rot(j, fp.beta.Value * np.pi / 180.) for j in i]) for i in pts]
rotated_pts = pts
for i in range(fp.gear.z - 1):
rotated_pts = list(map(rot, rotated_pts))
pts.append(np.array([pts[-1][-1], rotated_pts[0][0]]))
pts += rotated_pts
pts.append(np.array([pts[-1][-1], pts[0][0]]))
wires = []
if not "version" in fp.PropertiesList:
scale_0 = scale - fp.height.Value / 2
scale_1 = scale + fp.height.Value / 2
else: # starting with version 0.0.2
scale_0 = scale - fp.height.Value
scale_1 = scale
if fp.beta.Value == 0:
wires.append(make_bspline_wire([scale_0 * p for p in pts]))
wires.append(make_bspline_wire([scale_1 * p for p in pts]))
else:
for scale_i in np.linspace(scale_0, scale_1, 20):
# beta_i = (scale_i - scale_0) * fp.beta.Value * np.pi / 180
# rot = rotation3D(beta_i)
# points = [rot(pt) * scale_i for pt in pts]
angle = (
fp.beta.Value
* np.pi
/ 180.0
* np.sin(np.pi / 4)
/ np.sin(fp.pitch_angle.Value * np.pi / 180.0)
)
points = [
np.array([self.spherical_rot(p, angle) for p in scale_i * pt])
for pt in pts
]
wires.append(make_bspline_wire(points))
shape = Part.makeLoft(wires, True)
if fp.reset_origin:
mat = App.Matrix()
mat.A33 = -1
mat.move(fcvec([0, 0, scale_1]))
shape = shape.transformGeometry(mat)
return shape
# return self.create_teeth(pts, pos1, fp.teeth)
def create_tooth(self):
w = []
scal1 = (
self.obj.m.Value
* self.obj.gear.z
/ 2
/ np.tan(self.obj.pitch_angle.Value * np.pi / 180)
- self.obj.height.Value / 2
)
scal2 = (
self.obj.m.Value
* self.obj.gear.z
/ 2
/ np.tan(self.obj.pitch_angle.Value * np.pi / 180)
+ self.obj.height.Value / 2
)
s = [scal1, scal2]
pts = self.obj.gear.points(num=self.obj.numpoints)
for j, pos in enumerate(s):
w1 = []
def scale(x):
return fcvec(x * pos)
for i in pts:
i_scale = list(map(scale, i))
w1.append(i_scale)
w.append(w1)
surfs = []
w_t = zip(*w)
for i in w_t:
b = Part.BSplineSurface()
b.interpolate(i)
surfs.append(b)
return Part.Shape(surfs)
def spherical_rot(self, point, phi):
new_phi = np.sqrt(np.linalg.norm(point)) * phi
return rotation3D(new_phi)(point)

View File

@@ -19,8 +19,25 @@
import os import os
import FreeCAD import FreeCAD
import FreeCADGui as Gui import FreeCADGui as Gui
from .features import ViewProviderGear, InvoluteGear, InternalInvoluteGear, InvoluteGearRack, CycloidGearRack
from .features import CycloidGear, BevelGear, CrownGear, WormGear, TimingGear, LanternGear, HypoCycloidGear, BaseGear 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 .connector import GearConnector, ViewProviderGearConnector
@@ -40,8 +57,9 @@ class BaseCommand(object):
def Activated(self): def Activated(self):
Gui.doCommandGui("import freecad.gears.commands") Gui.doCommandGui("import freecad.gears.commands")
Gui.doCommandGui("freecad.gears.commands.{}.create()".format( Gui.doCommandGui(
self.__class__.__name__)) "freecad.gears.commands.{}.create()".format(self.__class__.__name__)
)
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
Gui.SendMsgToActiveView("ViewFit") Gui.SendMsgToActiveView("ViewFit")
@@ -54,7 +72,9 @@ class BaseCommand(object):
part = Gui.ActiveDocument.ActiveView.getActiveObject("part") part = Gui.ActiveDocument.ActiveView.getActiveObject("part")
if body: if body:
obj = FreeCAD.ActiveDocument.addObject("PartDesign::FeaturePython", cls.NAME) obj = FreeCAD.ActiveDocument.addObject(
"PartDesign::FeaturePython", cls.NAME
)
else: else:
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", cls.NAME) obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", cls.NAME)
ViewProviderGear(obj.ViewObject, cls.Pixmap) ViewProviderGear(obj.ViewObject, cls.Pixmap)
@@ -70,101 +90,115 @@ class BaseCommand(object):
return obj return obj
def GetResources(self): def GetResources(self):
return {'Pixmap': self.Pixmap, return {
'MenuText': self.MenuText, "Pixmap": self.Pixmap,
'ToolTip': self.ToolTip} "MenuText": self.MenuText,
"ToolTip": self.ToolTip,
}
class CreateInvoluteGear(BaseCommand): class CreateInvoluteGear(BaseCommand):
NAME = "InvoluteGear" NAME = "InvoluteGear"
GEAR_FUNCTION = InvoluteGear GEAR_FUNCTION = InvoluteGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involutegear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "involutegear.svg")
MenuText = 'Involute Gear' MenuText = "Involute Gear"
ToolTip = 'Create an external involute gear' ToolTip = "Create an external involute gear"
class CreateInternalInvoluteGear(BaseCommand): class CreateInternalInvoluteGear(BaseCommand):
NAME = "InternalInvoluteGear" NAME = "InternalInvoluteGear"
GEAR_FUNCTION = InternalInvoluteGear GEAR_FUNCTION = InternalInvoluteGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'internalinvolutegear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "internalinvolutegear.svg")
MenuText = 'Internal Involute Gear' MenuText = "Internal Involute Gear"
ToolTip = 'Create an internal involute gear' ToolTip = "Create an internal involute gear"
class CreateInvoluteRack(BaseCommand): class CreateInvoluteRack(BaseCommand):
NAME = "InvoluteRack" NAME = "InvoluteRack"
GEAR_FUNCTION = InvoluteGearRack GEAR_FUNCTION = InvoluteGearRack
Pixmap = os.path.join(BaseCommand.ICONDIR, 'involuterack.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "involuterack.svg")
MenuText = 'Involute Rack' MenuText = "Involute Rack"
ToolTip = 'Create an Involute rack' ToolTip = "Create an Involute rack"
class CreateCycloidRack(BaseCommand): class CreateCycloidRack(BaseCommand):
NAME = "CycloidRack" NAME = "CycloidRack"
GEAR_FUNCTION = CycloidGearRack GEAR_FUNCTION = CycloidGearRack
Pixmap = os.path.join(BaseCommand.ICONDIR, 'cycloidrack.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "cycloidrack.svg")
MenuText = 'Cycloid Rack' MenuText = "Cycloid Rack"
ToolTip = 'Create an Cycloid rack' ToolTip = "Create an Cycloid rack"
class CreateCrownGear(BaseCommand): class CreateCrownGear(BaseCommand):
NAME = "CrownGear" NAME = "CrownGear"
GEAR_FUNCTION = CrownGear GEAR_FUNCTION = CrownGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'crowngear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "crowngear.svg")
MenuText = 'Crown Gear' MenuText = "Crown Gear"
ToolTip = 'Create a Crown gear' ToolTip = "Create a Crown gear"
class CreateCycloidGear(BaseCommand): class CreateCycloidGear(BaseCommand):
NAME = "CycloidGear" NAME = "CycloidGear"
GEAR_FUNCTION = CycloidGear GEAR_FUNCTION = CycloidGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'cycloidgear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "cycloidgear.svg")
MenuText = 'Cycloid Gear' MenuText = "Cycloid Gear"
ToolTip = 'Create a Cycloid gear' ToolTip = "Create a Cycloid gear"
class CreateBevelGear(BaseCommand): class CreateBevelGear(BaseCommand):
NAME = "BevelGear" NAME = "BevelGear"
GEAR_FUNCTION = BevelGear GEAR_FUNCTION = BevelGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'bevelgear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "bevelgear.svg")
MenuText = 'Bevel Gear' MenuText = "Bevel Gear"
ToolTip = 'Create a Bevel gear' ToolTip = "Create a Bevel gear"
class CreateHypoCycloidGear(BaseCommand): class CreateHypoCycloidGear(BaseCommand):
NAME = "HypocycloidGear" NAME = "HypocycloidGear"
GEAR_FUNCTION = HypoCycloidGear GEAR_FUNCTION = HypoCycloidGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'hypocycloidgear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "hypocycloidgear.svg")
MenuText = 'HypoCycloid Gear' MenuText = "HypoCycloid Gear"
ToolTip = 'Create a HypoCycloid gear with its pins' ToolTip = "Create a HypoCycloid gear with its pins"
class CreateWormGear(BaseCommand): class CreateWormGear(BaseCommand):
NAME = "WormGear" NAME = "WormGear"
GEAR_FUNCTION = WormGear GEAR_FUNCTION = WormGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'wormgear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "wormgear.svg")
MenuText = 'Worm Gear' MenuText = "Worm Gear"
ToolTip = 'Create a Worm gear' ToolTip = "Create a Worm gear"
class CreateTimingGearT(BaseCommand):
NAME = "TimingGearT"
GEAR_FUNCTION = TimingGearT
Pixmap = os.path.join(BaseCommand.ICONDIR, "timinggear_t.svg")
MenuText = "Timing Gear T-shape"
ToolTip = "Create a Timing gear T-shape"
class CreateTimingGear(BaseCommand): class CreateTimingGear(BaseCommand):
NAME = "TimingGear" NAME = "TimingGear"
GEAR_FUNCTION = TimingGear GEAR_FUNCTION = TimingGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'timinggear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "timinggear.svg")
MenuText = 'Timing Gear' MenuText = "Timing Gear"
ToolTip = 'Create a Timing gear' ToolTip = "Create a Timing gear"
class CreateLanternGear(BaseCommand): class CreateLanternGear(BaseCommand):
NAME = "LanternGear" NAME = "LanternGear"
GEAR_FUNCTION = LanternGear GEAR_FUNCTION = LanternGear
Pixmap = os.path.join(BaseCommand.ICONDIR, 'lanterngear.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "lanterngear.svg")
MenuText = 'Lantern Gear' MenuText = "Lantern Gear"
ToolTip = 'Create a Lantern gear' ToolTip = "Create a Lantern gear"
class CreateGearConnector(BaseCommand): class CreateGearConnector(BaseCommand):
NAME = "GearConnector" NAME = "GearConnector"
GEAR_FUNCTION = GearConnector GEAR_FUNCTION = GearConnector
Pixmap = os.path.join(BaseCommand.ICONDIR, 'gearconnector.svg') Pixmap = os.path.join(BaseCommand.ICONDIR, "gearconnector.svg")
MenuText = 'Combine two gears' MenuText = "Combine two gears"
ToolTip = 'Combine two gears' ToolTip = "Combine two gears"
def Activated(self): def Activated(self):
gear1 = Gui.Selection.getSelection()[0] gear1 = Gui.Selection.getSelection()[0]

View File

@@ -17,13 +17,19 @@
# *************************************************************************** # ***************************************************************************
import os import os
import copy import sys
import numpy as np import numpy as np
import FreeCAD import FreeCAD
from pygears import __version__ from pygears import __version__
from .features import InvoluteGear, CycloidGear, InvoluteGearRack, CycloidGearRack, InternalInvoluteGear
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 from pygears.computation import compute_shifted_gears
class ViewProviderGearConnector(object): class ViewProviderGearConnector(object):
def __init__(self, vobj, icon_fn=None): def __init__(self, vobj, icon_fn=None):
# Set this object to the proxy object of the actual view provider # Set this object to the proxy object of the actual view provider
@@ -37,6 +43,15 @@ class ViewProviderGearConnector(object):
def getIcon(self): def getIcon(self):
return self.icon_fn 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): def __getstate__(self):
return {"icon_fn": self.icon_fn} return {"icon_fn": self.icon_fn}
@@ -46,11 +61,25 @@ class ViewProviderGearConnector(object):
class GearConnector(object): class GearConnector(object):
def __init__(self, obj, master_gear, slave_gear): def __init__(self, obj, master_gear, slave_gear):
obj.addProperty("App::PropertyString", "version", "version", "freecad.gears-version", 1) obj.addProperty(
"App::PropertyString", "version", "version", "freecad.gears-version", 1
)
obj.addProperty("App::PropertyLink", "master_gear", "gear", "master gear", 1) obj.addProperty("App::PropertyLink", "master_gear", "gear", "master gear", 1)
obj.addProperty("App::PropertyLink", "slave_gear", "gear", "slave 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(
obj.addProperty("App::PropertyAngle", "angle2", "gear", "angle at which second gear is placed", 1) "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.version = __version__
obj.master_gear = master_gear obj.master_gear = master_gear
obj.slave_gear = slave_gear obj.slave_gear = slave_gear
@@ -60,8 +89,12 @@ class GearConnector(object):
def onChanged(self, fp, prop): def onChanged(self, fp, prop):
# fp.angle2 = fp.master_gear.Placement.Rotation.Angle # fp.angle2 = fp.master_gear.Placement.Rotation.Angle
if isinstance(fp.master_gear.Proxy, InvoluteGear) and isinstance(fp.slave_gear.Proxy, InvoluteGear): if isinstance(fp.master_gear.Proxy, InvoluteGear) and isinstance(
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(fp.master_gear.Placement.Rotation.Axis) 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_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw dw_slave = fp.slave_gear.dw
dist = (dw_master + dw_slave) / 2 dist = (dw_master + dw_slave) / 2
@@ -72,7 +105,8 @@ class GearConnector(object):
fp.master_gear.teeth, fp.master_gear.teeth,
fp.slave_gear.teeth, fp.slave_gear.teeth,
fp.master_gear.shift, fp.master_gear.shift,
fp.slave_gear.shift) fp.slave_gear.shift,
)
mat0 = FreeCAD.Matrix() # unity matrix mat0 = FreeCAD.Matrix() # unity matrix
trans = FreeCAD.Vector(dist) trans = FreeCAD.Vector(dist)
@@ -81,15 +115,19 @@ class GearConnector(object):
angle2 = dw_master / dw_slave * fp.angle1.Value angle2 = dw_master / dw_slave * fp.angle1.Value
angle4 = dw_master / dw_slave * np.rad2deg(angle_master) angle4 = dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix() rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180. / fp.slave_gear.teeth angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix() rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix() rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4 mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base) mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1 fp.slave_gear.Placement = mat1
if isinstance(fp.master_gear.Proxy, InternalInvoluteGear) and isinstance(fp.slave_gear.Proxy, InvoluteGear): if isinstance(fp.master_gear.Proxy, InternalInvoluteGear) and isinstance(
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(fp.master_gear.Placement.Rotation.Axis) 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_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw dw_slave = fp.slave_gear.dw
dist = (dw_master - dw_slave) / 2 dist = (dw_master - dw_slave) / 2
@@ -100,7 +138,8 @@ class GearConnector(object):
fp.master_gear.teeth, fp.master_gear.teeth,
fp.slave_gear.teeth, fp.slave_gear.teeth,
fp.master_gear.shift, fp.master_gear.shift,
fp.slave_gear.shift) fp.slave_gear.shift,
)
mat0 = FreeCAD.Matrix() # unity matrix mat0 = FreeCAD.Matrix() # unity matrix
trans = FreeCAD.Vector(dist) trans = FreeCAD.Vector(dist)
@@ -109,16 +148,23 @@ class GearConnector(object):
angle2 = -dw_master / dw_slave * fp.angle1.Value angle2 = -dw_master / dw_slave * fp.angle1.Value
angle4 = -dw_master / dw_slave * np.rad2deg(angle_master) angle4 = -dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix() rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180. / fp.slave_gear.teeth angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix() rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix() rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4 mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base) mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1 fp.slave_gear.Placement = mat1
if ((isinstance(fp.master_gear.Proxy, InvoluteGear) and isinstance(fp.slave_gear.Proxy, InvoluteGearRack)) if (
or (isinstance(fp.master_gear.Proxy, CycloidGear) and isinstance(fp.slave_gear.Proxy, CycloidGearRack))): isinstance(fp.master_gear.Proxy, InvoluteGear)
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(fp.master_gear.Placement.Rotation.Axis) 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_master = fp.master_gear.dw.Value
dw_slave = 0 dw_slave = 0
dist = -(dw_master + dw_slave) / 2 dist = -(dw_master + dw_slave) / 2
@@ -127,14 +173,20 @@ class GearConnector(object):
mat1 = FreeCAD.Matrix() mat1 = FreeCAD.Matrix()
mat1.move(FreeCAD.Vector(0, np.deg2rad(fp.angle1.Value) * dw_master / 2, 0)) mat1.move(FreeCAD.Vector(0, np.deg2rad(fp.angle1.Value) * dw_master / 2, 0))
mat2 = FreeCAD.Matrix() mat2 = FreeCAD.Matrix()
mat2.move(FreeCAD.Vector(0, -np.deg2rad(fp.angle2.Value) * dw_master / 2, 0)) 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() rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), fp.angle1).toMatrix()
mat3 = rot * mat2 * mat1 * mat0 mat3 = rot * mat2 * mat1 * mat0
mat3.move(fp.master_gear.Placement.Base) mat3.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat3 fp.slave_gear.Placement = mat3
if isinstance(fp.master_gear.Proxy, CycloidGear) and isinstance(fp.slave_gear.Proxy, CycloidGear): if isinstance(fp.master_gear.Proxy, CycloidGear) and isinstance(
angle_master = fp.master_gear.Placement.Rotation.Angle * sum(fp.master_gear.Placement.Rotation.Axis) 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_master = fp.master_gear.dw
dw_slave = fp.slave_gear.dw dw_slave = fp.slave_gear.dw
dist = (dw_master + dw_slave) / 2 dist = (dw_master + dw_slave) / 2
@@ -145,7 +197,7 @@ class GearConnector(object):
angle2 = dw_master / dw_slave * fp.angle1.Value angle2 = dw_master / dw_slave * fp.angle1.Value
angle4 = dw_master / dw_slave * np.rad2deg(angle_master) angle4 = dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix() rot2 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180. / fp.slave_gear.teeth angle3 = abs(fp.slave_gear.teeth % 2 - 1) * 180.0 / fp.slave_gear.teeth
rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix() rot3 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), angle3).toMatrix()
rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix() rot4 = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4 mat1 = rot * mat0 * rot2 * rot3 * rot4
@@ -154,8 +206,3 @@ class GearConnector(object):
def execute(self, fp): def execute(self, fp):
self.onChanged(fp, None) self.onChanged(fp, None)

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

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCAD as App
import Part
import numpy as np
from .basegear import BaseGear, fcvec
class CrownGear(BaseGear):
def __init__(self, obj):
super(CrownGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyInteger",
"other_teeth",
"base",
"number of teeth of other gear",
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
self.add_accuracy_properties(obj)
obj.teeth = 15
obj.other_teeth = 15
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "2. mm"
obj.thickness = "5 mm"
obj.num_profiles = 4
obj.preview_mode = True
self.obj = obj
obj.Proxy = self
App.Console.PrintMessage(
"Gear module: Crown gear created, preview_mode = true for improved performance. "
"Set preview_mode property to false when ready to cut teeth."
)
def add_accuracy_properties(self, obj):
obj.addProperty(
"App::PropertyInteger",
"num_profiles",
"accuracy",
"number of profiles used for loft",
)
obj.addProperty(
"App::PropertyBool",
"preview_mode",
"accuracy",
"if true no boolean operation is done",
)
def profile(self, m, r, r0, t_c, t_i, alpha_w, y0, y1, y2):
r_ew = m * t_i / 2
# 1: modifizierter Waelzkreisdurchmesser:
r_e = r / r0 * r_ew
# 2: modifizierter Schraegungswinkel:
alpha = np.arccos(r0 / r * np.cos(alpha_w))
# 3: winkel phi bei senkrechter stellung eines zahns:
phi = np.pi / t_i / 2 + (alpha - alpha_w) + (np.tan(alpha_w) - np.tan(alpha))
# 4: Position des Eingriffspunktes:
x_c = r_e * np.sin(phi)
dy = -r_e * np.cos(phi) + r_ew
# 5: oberer Punkt:
b = y1 - dy
a = np.tan(alpha) * b
x1 = a + x_c
# 6: unterer Punkt
d = y2 + dy
c = np.tan(alpha) * d
x2 = x_c - c
r *= np.cos(phi)
pts = [[-x1, r, y0], [-x2, r, y0 - y1 - y2], [x2, r, y0 - y1 - y2], [x1, r, y0]]
pts.append(pts[0])
return pts
def generate_gear_shape(self, fp):
inner_diameter = fp.module.Value * fp.teeth
outer_diameter = inner_diameter + fp.height.Value * 2
inner_circle = Part.Wire(Part.makeCircle(inner_diameter / 2.0))
outer_circle = Part.Wire(Part.makeCircle(outer_diameter / 2.0))
inner_circle.reverse()
face = Part.Face([outer_circle, inner_circle])
solid = face.extrude(App.Vector([0.0, 0.0, -fp.thickness.Value]))
if fp.preview_mode:
return solid
# cutting obj
alpha_w = np.deg2rad(fp.pressure_angle.Value)
m = fp.module.Value
t = fp.teeth
t_c = t
t_i = fp.other_teeth
rm = inner_diameter / 2
y0 = m * 0.5
y1 = m + y0
y2 = m
r0 = inner_diameter / 2 - fp.height.Value * 0.1
r1 = outer_diameter / 2 + fp.height.Value * 0.3
polies = []
for r_i in np.linspace(r0, r1, fp.num_profiles):
pts = self.profile(m, r_i, rm, t_c, t_i, alpha_w, y0, y1, y2)
poly = Part.Wire(Part.makePolygon(list(map(fcvec, pts))))
polies.append(poly)
loft = Part.makeLoft(polies, True)
rot = App.Matrix()
rot.rotateZ(2 * np.pi / t)
cut_shapes = []
for _ in range(t):
loft = loft.transformGeometry(rot)
cut_shapes.append(loft)
return solid.cut(cut_shapes)

View File

@@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.cycloid_tooth import CycloidTooth
from pygears._functions import rotation
from .basegear import (
BaseGear,
points_to_wire,
insert_fillet,
helicalextrusion,
rotate_tooth,
)
class CycloidGear(BaseGear):
"""FreeCAD gear"""
def __init__(self, obj):
super(CycloidGear, self).__init__(obj)
self.cycloid_tooth = CycloidTooth()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
obj.addProperty(
"App::PropertyPythonObject", "gear", "base", "the python object"
)
self.add_helical_properties(obj)
self.add_fillet_properties(obj)
self.add_tolerance_properties(obj)
self.add_cycloid_properties(obj)
self.add_computed_properties(obj)
obj.gear = self.cycloid_tooth
obj.teeth = 15
obj.module = "1. mm"
obj.setExpression(
"inner_diameter", "teeth / 2"
) # teeth/2 makes the hypocycloid a straight line to the center
obj.outer_diameter = 7.5 # we don't know the mating gear, so we just set the default to mesh with our default
obj.beta = "0. deg"
obj.height = "5. mm"
obj.clearance = 0.25
obj.numpoints = 15
obj.backlash = "0.00 mm"
obj.double_helix = False
obj.head = 0
obj.head_fillet = 0
obj.root_fillet = 0
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta")
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def add_tolerance_properties(self, obj):
obj.addProperty("App::PropertyFloat", "clearance", "tolerance", "clearance")
obj.addProperty(
"App::PropertyLength",
"backlash",
"tolerance",
"The arc length on the pitch circle by which the tooth thicknes is reduced.",
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head_value * modul_value = additional length of head",
)
def add_cycloid_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"inner_diameter",
"cycloid",
"inner_diameter divided by module (hypocycloid)",
)
obj.addProperty(
"App::PropertyFloat",
"outer_diameter",
"cycloid",
"outer_diameter divided by module (epicycloid)",
)
def add_computed_properties(self, obj):
obj.addProperty("App::PropertyLength", "dw", "computed", "The pitch diameter.")
obj.setExpression(
"dw", "teeth * module"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"dw", 1
) # set read-only after setting the expression, else it won't be visible. bug?
obj.addProperty(
"App::PropertyAngle",
"angular_backlash",
"computed",
"The angle by which this gear can turn without moving the mating gear.",
)
obj.setExpression(
"angular_backlash", "backlash / dw * 360° / pi"
) # calculate via expression to ease usage for placement
obj.setEditorMode(
"angular_backlash", 1
) # set read-only after setting the expression, else it won't be visible. bug?
def generate_gear_shape(self, fp):
fp.gear.m = fp.module.Value
fp.gear.z = fp.teeth
fp.dw = fp.module * fp.teeth
fp.gear.z1 = fp.inner_diameter
fp.gear.z2 = fp.outer_diameter
fp.gear.clearance = fp.clearance
fp.gear.head = fp.head
fp.gear.backlash = fp.backlash.Value
fp.gear._update()
pts = fp.gear.points(num=fp.numpoints)
rot = rotation(-fp.gear.phipart)
rotated_pts = list(map(rot, pts))
pts.append([pts[-1][-1], rotated_pts[0][0]])
pts += rotated_pts
tooth = points_to_wire(pts)
edges = tooth.Edges
r_head = float(fp.head_fillet * fp.module)
r_root = float(fp.root_fillet * fp.module)
pos_head = [0, 2, 6]
pos_root = [4, 6]
edge_range = [1, 9]
for pos in pos_head:
edges = insert_fillet(edges, pos, r_head)
for pos in pos_root:
edges = insert_fillet(edges, pos, r_root)
edges = edges[edge_range[0] : edge_range[1]]
edges = [e for e in edges if e is not None]
tooth = Part.Wire(edges)
profile = rotate_tooth(tooth, fp.teeth)
if fp.height.Value == 0:
return profile
base = Part.Face(profile)
if fp.beta.Value == 0:
return base.extrude(App.Vector(0, 0, fp.height.Value))
else:
twist_angle = (
fp.height.Value * np.tan(fp.beta.Value * np.pi / 180) * 2 / fp.gear.d
)
return helicalextrusion(base, fp.height.Value, twist_angle, fp.double_helix)

View File

@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import os
import sys
import FreeCAD as App
import Part
import numpy as np
from pygears._functions import reflection
from .basegear import BaseGear, fcvec, points_to_wire, insert_fillet
class CycloidGearRack(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(CycloidGearRack, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty("App::PropertyLength", "module", "involute", "module")
obj.addProperty(
"App::PropertyBool",
"simplified",
"precision",
"if enabled the rack is drawn with a constant number of \
teeth to avoid topologic renaming.",
)
obj.addProperty(
"App::PropertyInteger",
"numpoints",
"accuracy",
"number of points for spline",
)
obj.addProperty("App::PropertyPythonObject", "rack", "base", "test")
self.add_helical_properties(obj)
self.add_computed_properties(obj)
self.add_tolerance_properties(obj)
self.add_cycloid_properties(obj)
self.add_fillet_properties(obj)
obj.teeth = 15
obj.module = "1. mm"
obj.inner_diameter = 7.5
obj.outer_diameter = 7.5
obj.height = "5. mm"
obj.thickness = "5 mm"
obj.beta = "0. deg"
obj.clearance = 0.25
obj.head = 0.0
obj.add_endings = True
obj.simplified = False
obj.numpoints = 15
self.obj = obj
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
def add_computed_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"transverse_pitch",
"computed",
"pitch in the transverse plane",
1,
)
obj.addProperty(
"App::PropertyBool",
"add_endings",
"base",
"if enabled the total length of the rack is teeth x pitch, \
otherwise the rack starts with a tooth-flank",
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
def add_cycloid_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"inner_diameter",
"cycloid",
"inner_diameter divided by module (hypocycloid)",
)
obj.addProperty(
"App::PropertyFloat",
"outer_diameter",
"cycloid",
"outer_diameter divided by module (epicycloid)",
)
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def generate_gear_shape(self, obj):
numpoints = obj.numpoints
m = obj.module.Value
t = obj.thickness.Value
r_i = obj.inner_diameter / 2 * m
r_o = obj.outer_diameter / 2 * m
c = obj.clearance
h = obj.head
head_fillet = obj.head_fillet
root_fillet = obj.root_fillet
phi_i_end = np.arccos(1 - m / r_i * (1 + c))
phi_o_end = np.arccos(1 - m / r_o * (1 + h))
phi_i = np.linspace(phi_i_end, 0, numpoints)
phi_o = np.linspace(0, phi_o_end, numpoints)
y_i = r_i * (np.cos(phi_i) - 1)
y_o = r_o * (1 - np.cos(phi_o))
x_i = r_i * (np.sin(phi_i) - phi_i) - m * np.pi / 4
x_o = r_o * (phi_o - np.sin(phi_o)) - m * np.pi / 4
x = x_i.tolist()[:-1] + x_o.tolist()
y = y_i.tolist()[:-1] + y_o.tolist()
points = np.array([y, x]).T
mirror = reflection(0)
points_1 = mirror(points)[::-1]
line_1 = [points[-1], points_1[0]]
line_2 = [points_1[-1], np.array([-(1 + c) * m, m * np.pi / 2])]
line_0 = [np.array([-(1 + c) * m, -m * np.pi / 2]), points[0]]
tooth = points_to_wire([line_0, points, line_1, points_1, line_2])
edges = tooth.Edges
edges = insert_fillet(edges, 0, m * root_fillet)
edges = insert_fillet(edges, 2, m * head_fillet)
edges = insert_fillet(edges, 4, m * head_fillet)
edges = insert_fillet(edges, 6, m * root_fillet)
tooth_edges = [e for e in edges if e is not None]
p_end = np.array(tooth_edges[-2].lastVertex().Point[:-1])
p_start = np.array(tooth_edges[1].firstVertex().Point[:-1])
p_start += np.array([0, np.pi * m])
edge = points_to_wire([[p_end, p_start]]).Edges
tooth = Part.Wire(tooth_edges[1:-1] + edge)
teeth = [tooth]
for i in range(obj.teeth - 1):
tooth = tooth.copy()
tooth.translate(App.Vector(0, np.pi * m, 0))
teeth.append(tooth)
teeth[-1] = Part.Wire(teeth[-1].Edges[:-1])
if obj.add_endings:
teeth = [Part.Wire(tooth_edges[0])] + teeth
last_edge = tooth_edges[-1]
last_edge.translate(App.Vector(0, np.pi * m * (obj.teeth - 1), 0))
teeth = teeth + [Part.Wire(last_edge)]
p_start = np.array(teeth[0].Edges[0].firstVertex().Point[:-1])
p_end = np.array(teeth[-1].Edges[-1].lastVertex().Point[:-1])
p_start_1 = p_start - np.array([obj.thickness.Value, 0.0])
p_end_1 = p_end - np.array([obj.thickness.Value, 0.0])
line6 = [p_start, p_start_1]
line7 = [p_start_1, p_end_1]
line8 = [p_end_1, p_end]
bottom = points_to_wire([line6, line7, line8])
pol = Part.Wire([bottom] + teeth)
if obj.height.Value == 0:
return pol
elif obj.beta.Value == 0:
face = Part.Face(Part.Wire(pol))
return face.extrude(fcvec([0.0, 0.0, obj.height.Value]))
elif obj.double_helix:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value / 2, obj.height.Value / 2])
)
pol3 = Part.Wire(pol)
pol3.translate(fcvec([0.0, 0.0, obj.height.Value]))
return Part.makeLoft([pol, pol2, pol3], True, True)
else:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value, obj.height.Value])
)
return Part.makeLoft([pol, pol2], True)
def __getstate__(self):
return None
def __setstate__(self, state):
return None

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import math
import numpy as np
import scipy as sp
import FreeCAD as App
import Part
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation
from .basegear import BaseGear, make_bspline_wire
class HypoCycloidGear(BaseGear):
"""parameters:
pressure_angle: pressureangle, 10-30°
pitch_angle: cone angle, 0 < pitch_angle < pi/4
"""
def __init__(self, obj):
super(HypoCycloidGear, self).__init__(obj)
obj.addProperty(
"App::PropertyFloat",
"pin_circle_radius",
"gear_parameter",
"Pin ball circle radius(overrides Tooth Pitch",
)
obj.addProperty(
"App::PropertyFloat", "roller_diameter", "gear_parameter", "Roller Diameter"
)
obj.addProperty(
"App::PropertyFloat", "eccentricity", "gear_parameter", "Eccentricity"
)
obj.addProperty(
"App::PropertyAngle",
"pressure_angle_lim",
"gear_parameter",
"Pressure angle limit",
)
obj.addProperty(
"App::PropertyFloat",
"pressure_angle_offset",
"gear_parameter",
"Offset in pressure angle",
)
obj.addProperty(
"App::PropertyInteger",
"teeth_number",
"gear_parameter",
"Number of teeth in Cam",
)
obj.addProperty(
"App::PropertyInteger",
"segment_count",
"gear_parameter",
"Number of points used for spline interpolation",
)
obj.addProperty(
"App::PropertyLength",
"hole_radius",
"gear_parameter",
"Center hole's radius",
)
obj.addProperty(
"App::PropertyBool", "show_pins", "Pins", "Create pins in place"
)
obj.addProperty("App::PropertyLength", "pin_height", "Pins", "height")
obj.addProperty(
"App::PropertyBool",
"center_pins",
"Pins",
"Center pin Z axis to generated disks",
)
obj.addProperty(
"App::PropertyBool", "show_disk0", "Disks", "Show main cam disk"
)
obj.addProperty(
"App::PropertyBool",
"show_disk1",
"Disks",
"Show another reversed cam disk on top",
)
obj.addProperty("App::PropertyLength", "disk_height", "Disks", "height")
obj.pin_circle_radius = 66
obj.roller_diameter = 3
obj.eccentricity = 1.5
obj.pressure_angle_lim = "50.0 deg"
obj.pressure_angle_offset = 0.01
obj.teeth_number = 42
obj.segment_count = 42
obj.hole_radius = "30. mm"
obj.show_pins = True
obj.pin_height = "20. mm"
obj.center_pins = True
obj.show_disk0 = True
obj.show_disk1 = True
obj.disk_height = "10. mm"
self.obj = obj
obj.Proxy = self
def to_polar(self, x, y):
return (x**2 + y**2) ** 0.5, math.atan2(y, x)
def to_rect(self, r, a):
return r * math.cos(a), r * math.sin(a)
def calcyp(self, p, a, e, n):
return math.atan(math.sin(n * a) / (math.cos(n * a) + (n * p) / (e * (n + 1))))
def calc_x(self, p, d, e, n, a):
return (
(n * p) * math.cos(a)
+ e * math.cos((n + 1) * a)
- d / 2 * math.cos(self.calcyp(p, a, e, n) + a)
)
def calc_y(self, p, d, e, n, a):
return (
(n * p) * math.sin(a)
+ e * math.sin((n + 1) * a)
- d / 2 * math.sin(self.calcyp(p, a, e, n) + a)
)
def calc_pressure_angle(self, p, d, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
pp = rg * (ex**2 + 1 - 2 * ex * math.cos(a)) ** 0.5 - d / 2
return math.asin((r3 * math.cos(a) - rg) / (pp + d / 2)) * 180 / math.pi
def calc_pressure_limit(self, p, d, e, n, a):
ex = 2**0.5
r3 = p * n
rg = r3 / ex
q = (r3**2 + rg**2 - 2 * r3 * rg * math.cos(a)) ** 0.5
x = rg - e + (q - d / 2) * (r3 * math.cos(a) - rg) / q
y = (q - d / 2) * r3 * math.sin(a) / q
return (x**2 + y**2) ** 0.5
def check_limit(self, x, y, maxrad, minrad, offset):
r, a = self.to_polar(x, y)
if (r > maxrad) or (r < minrad):
r = r - offset
x, y = self.to_rect(r, a)
return x, y
def generate_gear_shape(self, fp):
b = fp.pin_circle_radius
d = fp.roller_diameter
e = fp.eccentricity
n = fp.teeth_number
p = b / n
s = fp.segment_count
ang = fp.pressure_angle_lim
c = fp.pressure_angle_offset
q = 2 * math.pi / float(s)
# Find the pressure angle limit circles
minAngle = -1.0
maxAngle = -1.0
for i in range(0, 180):
x = self.calc_pressure_angle(p, d, n, i * math.pi / 180.0)
if (x < ang) and (minAngle < 0):
minAngle = float(i)
if (x < -ang) and (maxAngle < 0):
maxAngle = float(i - 1)
minRadius = self.calc_pressure_limit(p, d, e, n, minAngle * math.pi / 180.0)
maxRadius = self.calc_pressure_limit(p, d, e, n, maxAngle * math.pi / 180.0)
# unused
# Part.Wire(Part.makeCircle(minRadius,App.Vector(-e, 0, 0)))
# Part.Wire(Part.makeCircle(maxRadius,App.Vector(-e, 0, 0)))
App.Console.PrintMessage("Generating cam disk\r\n")
# generate the cam profile - note: shifted in -x by eccentricicy amount
i = 0
x = self.calc_x(p, d, e, n, q * i / float(n))
y = self.calc_y(p, d, e, n, q * i / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points = [App.Vector(x - e, y, 0)]
for i in range(0, s):
x = self.calc_x(p, d, e, n, q * (i + 1) / n)
y = self.calc_y(p, d, e, n, q * (i + 1) / n)
x, y = self.check_limit(x, y, maxRadius, minRadius, c)
points.append([x - e, y, 0])
wi = make_bspline_wire([points])
wires = []
mat = App.Matrix()
mat.move(App.Vector(e, 0.0, 0.0))
mat.rotateZ(2 * np.pi / n)
mat.move(App.Vector(-e, 0.0, 0.0))
for _ in range(n):
wi = wi.transformGeometry(mat)
wires.append(wi)
cam = Part.Face(Part.Wire(wires))
# add a circle in the center of the cam
if fp.hole_radius.Value:
centerCircle = Part.Face(
Part.Wire(Part.makeCircle(fp.hole_radius.Value, App.Vector(-e, 0, 0)))
)
cam = cam.cut(centerCircle)
to_be_fused = []
if fp.show_disk0 == True:
if fp.disk_height.Value == 0:
to_be_fused.append(cam)
else:
to_be_fused.append(cam.extrude(App.Vector(0, 0, fp.disk_height.Value)))
# secondary cam disk
if fp.show_disk1 == True:
App.Console.PrintMessage("Generating secondary cam disk\r\n")
second_cam = cam.copy()
mat = App.Matrix()
mat.rotateZ(np.pi)
mat.move(App.Vector(-e, 0, 0))
if n % 2 == 0:
mat.rotateZ(np.pi / n)
mat.move(App.Vector(e, 0, 0))
second_cam = second_cam.transformGeometry(mat)
if fp.disk_height.Value == 0:
to_be_fused.append(second_cam)
else:
to_be_fused.append(
second_cam.extrude(App.Vector(0, 0, -fp.disk_height.Value))
)
# pins
if fp.show_pins == True:
App.Console.PrintMessage("Generating pins\r\n")
pins = []
for i in range(0, n + 1):
x = p * n * math.cos(2 * math.pi / (n + 1) * i)
y = p * n * math.sin(2 * math.pi / (n + 1) * i)
pins.append(Part.Wire(Part.makeCircle(d / 2, App.Vector(x, y, 0))))
pins = Part.Face(pins)
z_offset = -fp.pin_height.Value / 2
if fp.center_pins == True:
if fp.show_disk0 == True and fp.show_disk1 == False:
z_offset += fp.disk_height.Value / 2
elif fp.show_disk0 == False and fp.show_disk1 == True:
z_offset += -fp.disk_height.Value / 2
# extrude
if z_offset != 0:
pins.translate(App.Vector(0, 0, z_offset))
if fp.pin_height != 0:
pins = pins.extrude(App.Vector(0, 0, fp.pin_height.Value))
to_be_fused.append(pins)
if to_be_fused:
return Part.makeCompound(to_be_fused)

View File

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

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -17,24 +17,86 @@
# *************************************************************************** # ***************************************************************************
import os import os
import sys
import FreeCADGui as Gui import FreeCADGui as Gui
import FreeCAD as App import FreeCAD as App
__dirname__ = os.path.dirname(__file__) __dirname__ = os.path.dirname(__file__)
try: try:
from FreeCADGui import Workbench from FreeCADGui import Workbench
except ImportError as e: except ImportError as e:
App.Console.PrintWarning( 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( App.Console.PrintWarning(
"the class Workbench is loaded, although not imported: magic") "the class Workbench is loaded, although not imported: magic"
)
if sys.version_info[0] == 3 and sys.version_info[1] >= 11:
# only works with 0.21.2 and above
FC_MAJOR_VER_REQUIRED = 0
FC_MINOR_VER_REQUIRED = 21
FC_PATCH_VER_REQUIRED = 2
FC_COMMIT_REQUIRED = 33772
# Check FreeCAD version
App.Console.PrintLog("Checking FreeCAD version\n")
ver = App.Version()
major_ver = int(ver[0])
minor_vers = ver[1].split(".")
minor_ver = int(minor_vers[0])
if minor_vers[1:] and minor_vers[1]:
patch_ver = int(minor_vers[1])
else:
patch_ver = 0
gitver = ver[2].split()
if gitver:
gitver = gitver[0]
if gitver and gitver != "Unknown":
gitver = int(gitver)
else:
# If we don't have the git version, assume it's OK.
gitver = FC_COMMIT_REQUIRED
if major_ver < FC_MAJOR_VER_REQUIRED or (
major_ver == FC_MAJOR_VER_REQUIRED
and (
minor_ver < FC_MINOR_VER_REQUIRED
or (
minor_ver == FC_MINOR_VER_REQUIRED
and (
patch_ver < FC_PATCH_VER_REQUIRED
or (
patch_ver == FC_PATCH_VER_REQUIRED
and gitver < FC_COMMIT_REQUIRED
)
)
)
)
):
App.Console.PrintWarning(
"FreeCAD version (currently {}.{}.{} ({})) must be at least {}.{}.{} ({}) in order to work with Python 3.11 and above\n".format(
int(ver[0]),
minor_ver,
patch_ver,
gitver,
FC_MAJOR_VER_REQUIRED,
FC_MINOR_VER_REQUIRED,
FC_PATCH_VER_REQUIRED,
FC_COMMIT_REQUIRED,
)
)
class GearWorkbench(Workbench): class GearWorkbench(Workbench):
"""glider workbench""" """A freecad workbench aiming at gear design"""
MenuText = "Gear" MenuText = "Gear"
ToolTip = "Gear Workbench" ToolTip = "Gear Workbench"
Icon = os.path.join(__dirname__, 'icons', 'gearworkbench.svg') Icon = os.path.join(__dirname__, "icons", "gearworkbench.svg")
commands = [ commands = [
"CreateInvoluteGear", "CreateInvoluteGear",
"CreateInternalInvoluteGear", "CreateInternalInvoluteGear",
@@ -44,36 +106,48 @@ class GearWorkbench(Workbench):
"CreateBevelGear", "CreateBevelGear",
"CreateCrownGear", "CreateCrownGear",
"CreateWormGear", "CreateWormGear",
"CreateTimingGearT",
"CreateTimingGear", "CreateTimingGear",
"CreateLanternGear", "CreateLanternGear",
"CreateHypoCycloidGear", "CreateHypoCycloidGear",
"CreateGearConnector"] "CreateGearConnector",
]
def GetClassName(self): def GetClassName(self):
return "Gui::PythonWorkbench" return "Gui::PythonWorkbench"
def Initialize(self): def Initialize(self):
from .commands import CreateCycloidGear, CreateInvoluteGear, CreateInternalInvoluteGear from .commands import (
from .commands import CreateBevelGear, CreateInvoluteRack, CreateCrownGear CreateCycloidGear,
from .commands import CreateWormGear, CreateTimingGear, CreateLanternGear CreateInvoluteGear,
from .commands import CreateHypoCycloidGear, CreateCycloidRack CreateInternalInvoluteGear,
from .commands import CreateGearConnector CreateBevelGear,
CreateInvoluteRack,
CreateCrownGear,
CreateWormGear,
CreateTimingGearT,
CreateTimingGear,
CreateLanternGear,
CreateHypoCycloidGear,
CreateCycloidRack,
CreateGearConnector,
)
self.appendToolbar("Gear", self.commands) self.appendToolbar("Gear", self.commands)
self.appendMenu("Gear", self.commands) self.appendMenu("Gear", self.commands)
# Gui.addIconPath(App.getHomePath()+"Mod/gear/icons/") Gui.addCommand("CreateInvoluteGear", CreateInvoluteGear())
Gui.addCommand('CreateInvoluteGear', CreateInvoluteGear()) Gui.addCommand("CreateInternalInvoluteGear", CreateInternalInvoluteGear())
Gui.addCommand('CreateInternalInvoluteGear', CreateInternalInvoluteGear()) Gui.addCommand("CreateCycloidGear", CreateCycloidGear())
Gui.addCommand('CreateCycloidGear', CreateCycloidGear()) Gui.addCommand("CreateCycloidRack", CreateCycloidRack())
Gui.addCommand('CreateCycloidRack', CreateCycloidRack()) Gui.addCommand("CreateBevelGear", CreateBevelGear())
Gui.addCommand('CreateBevelGear', CreateBevelGear()) Gui.addCommand("CreateInvoluteRack", CreateInvoluteRack())
Gui.addCommand('CreateInvoluteRack', CreateInvoluteRack()) Gui.addCommand("CreateCrownGear", CreateCrownGear())
Gui.addCommand('CreateCrownGear', CreateCrownGear()) Gui.addCommand("CreateWormGear", CreateWormGear())
Gui.addCommand('CreateWormGear', CreateWormGear()) Gui.addCommand("CreateTimingGearT", CreateTimingGearT())
Gui.addCommand('CreateTimingGear', CreateTimingGear()) Gui.addCommand("CreateTimingGear", CreateTimingGear())
Gui.addCommand('CreateLanternGear', CreateLanternGear()) Gui.addCommand("CreateLanternGear", CreateLanternGear())
Gui.addCommand('CreateHypoCycloidGear', CreateHypoCycloidGear()) Gui.addCommand("CreateHypoCycloidGear", CreateHypoCycloidGear())
Gui.addCommand('CreateGearConnector', CreateGearConnector()) Gui.addCommand("CreateGearConnector", CreateGearConnector())
def Activated(self): def Activated(self):
pass pass

View File

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

View File

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

View File

@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteRack
from .basegear import BaseGear, fcvec, points_to_wire, insert_fillet
class InvoluteGearRack(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(InvoluteGearRack, self).__init__(obj)
self.involute_rack = InvoluteRack()
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "thickness", "base", "thickness")
obj.addProperty(
"App::PropertyBool",
"simplified",
"precision",
"if enabled the rack is drawn with a constant number of \
teeth to avoid topologic renaming.",
)
obj.addProperty("App::PropertyPythonObject", "rack", "base", "test")
self.add_helical_properties(obj)
self.add_computed_properties(obj)
self.add_tolerance_properties(obj)
self.add_involute_properties(obj)
self.add_fillet_properties(obj)
obj.rack = self.involute_rack
obj.teeth = 15
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "5. mm"
obj.thickness = "5 mm"
obj.beta = "0. deg"
obj.clearance = 0.25
obj.head = 0.0
obj.properties_from_tool = False
obj.add_endings = True
obj.simplified = False
self.obj = obj
obj.Proxy = self
def add_helical_properties(self, obj):
obj.addProperty(
"App::PropertyBool",
"properties_from_tool",
"helical",
"if beta is given and properties_from_tool is enabled, \
gear parameters are internally recomputed for the rotated gear",
)
obj.addProperty("App::PropertyAngle", "beta", "helical", "beta ")
obj.addProperty("App::PropertyBool", "double_helix", "helical", "double helix")
def add_computed_properties(self, obj):
obj.addProperty(
"App::PropertyLength",
"transverse_pitch",
"computed",
"pitch in the transverse plane",
1,
)
obj.addProperty(
"App::PropertyBool",
"add_endings",
"base",
"if enabled the total length of the rack is teeth x pitch, \
otherwise the rack starts with a tooth-flank",
)
def add_tolerance_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
def add_involute_properties(self, obj):
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
def add_fillet_properties(self, obj):
obj.addProperty(
"App::PropertyFloat",
"head_fillet",
"fillets",
"a fillet for the tooth-head, radius = head_fillet x module",
)
obj.addProperty(
"App::PropertyFloat",
"root_fillet",
"fillets",
"a fillet for the tooth-root, radius = root_fillet x module",
)
def generate_gear_shape(self, obj):
obj.rack.m = obj.module.Value
obj.rack.z = obj.teeth
obj.rack.pressure_angle = obj.pressure_angle.Value * np.pi / 180.0
obj.rack.thickness = obj.thickness.Value
obj.rack.beta = obj.beta.Value * np.pi / 180.0
obj.rack.head = obj.head
# checksbackwardcompatibility:
if "clearance" in obj.PropertiesList:
obj.rack.clearance = obj.clearance
if "properties_from_tool" in obj.PropertiesList:
obj.rack.properties_from_tool = obj.properties_from_tool
if "add_endings" in obj.PropertiesList:
obj.rack.add_endings = obj.add_endings
if "simplified" in obj.PropertiesList:
obj.rack.simplified = obj.simplified
obj.rack._update()
m, m_n, pitch, pressure_angle_t = obj.rack.compute_properties()
obj.transverse_pitch = "{} mm".format(pitch)
t = obj.thickness.Value
c = obj.clearance
h = obj.head
alpha = obj.pressure_angle.Value * np.pi / 180.0
head_fillet = obj.head_fillet
root_fillet = obj.root_fillet
x1 = -m * np.pi / 2
y1 = -m * (1 + c)
y2 = y1
x2 = -m * np.pi / 4 + y2 * np.tan(alpha)
y3 = m * (1 + h)
x3 = -m * np.pi / 4 + y3 * np.tan(alpha)
x4 = -x3
x5 = -x2
x6 = -x1
y4 = y3
y5 = y2
y6 = y1
p1 = np.array([y1, x1])
p2 = np.array([y2, x2])
p3 = np.array([y3, x3])
p4 = np.array([y4, x4])
p5 = np.array([y5, x5])
p6 = np.array([y6, x6])
line1 = [p1, p2]
line2 = [p2, p3]
line3 = [p3, p4]
line4 = [p4, p5]
line5 = [p5, p6]
tooth = Part.Wire(points_to_wire([line1, line2, line3, line4, line5]))
edges = tooth.Edges
edges = insert_fillet(edges, 0, m * root_fillet)
edges = insert_fillet(edges, 2, m * head_fillet)
edges = insert_fillet(edges, 4, m * head_fillet)
edges = insert_fillet(edges, 6, m * root_fillet)
tooth_edges = [e for e in edges if e is not None]
p_end = np.array(tooth_edges[-2].lastVertex().Point[:-1])
p_start = np.array(tooth_edges[1].firstVertex().Point[:-1])
p_start += np.array([0, np.pi * m])
edge = points_to_wire([[p_end, p_start]]).Edges
tooth = Part.Wire(tooth_edges[1:-1] + edge)
teeth = [tooth]
for i in range(obj.teeth - 1):
tooth = tooth.copy()
tooth.translate(App.Vector(0, np.pi * m, 0))
teeth.append(tooth)
teeth[-1] = Part.Wire(teeth[-1].Edges[:-1])
if obj.add_endings:
teeth = [Part.Wire(tooth_edges[0])] + teeth
last_edge = tooth_edges[-1]
last_edge.translate(App.Vector(0, np.pi * m * (obj.teeth - 1), 0))
teeth = teeth + [Part.Wire(last_edge)]
p_start = np.array(teeth[0].Edges[0].firstVertex().Point[:-1])
p_end = np.array(teeth[-1].Edges[-1].lastVertex().Point[:-1])
p_start_1 = p_start - np.array([obj.thickness.Value, 0.0])
p_end_1 = p_end - np.array([obj.thickness.Value, 0.0])
line6 = [p_start, p_start_1]
line7 = [p_start_1, p_end_1]
line8 = [p_end_1, p_end]
bottom = points_to_wire([line6, line7, line8])
pol = Part.Wire([bottom] + teeth)
if obj.height.Value == 0:
return pol
elif obj.beta.Value == 0:
face = Part.Face(Part.Wire(pol))
return face.extrude(fcvec([0.0, 0.0, obj.height.Value]))
elif obj.double_helix:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value / 2, obj.height.Value / 2])
)
pol3 = Part.Wire(pol)
pol3.translate(fcvec([0.0, 0.0, obj.height.Value]))
return Part.makeLoft([pol, pol2, pol3], True, True)
else:
beta = obj.beta.Value * np.pi / 180.0
pol2 = Part.Wire(pol)
pol2.translate(
fcvec([0.0, np.tan(beta) * obj.height.Value, obj.height.Value])
)
return Part.makeLoft([pol, pol2], True)

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
import scipy as sp
from pygears.bevel_tooth import BevelTooth
from pygears._functions import rotation
from .basegear import BaseGear, fcvec, part_arc_from_points_and_center
class LanternGear(BaseGear):
def __init__(self, obj):
super(LanternGear, self).__init__(obj)
obj.addProperty(
"App::PropertyInteger", "teeth", "gear_parameter", "number of teeth"
)
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty(
"App::PropertyLength",
"bolt_radius",
"base",
"the bolt radius of the rack/chain",
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty(
"App::PropertyInteger",
"num_profiles",
"accuracy",
"number of profiles used for loft",
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.teeth = 15
obj.module = "1. mm"
obj.bolt_radius = "1 mm"
obj.height = "5. mm"
obj.num_profiles = 10
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
m = fp.module.Value
teeth = fp.teeth
r_r = fp.bolt_radius.Value
r_0 = m * teeth / 2
r_max = r_0 + r_r + fp.head * m
phi_max = (r_r + np.sqrt(r_max**2 - r_0**2)) / r_0
def find_phi_min(phi_min):
return r_0 * (
phi_min**2 * r_0
- 2 * phi_min * r_0 * np.sin(phi_min)
- 2 * phi_min * r_r
- 2 * r_0 * np.cos(phi_min)
+ 2 * r_0
+ 2 * r_r * np.sin(phi_min)
)
phi_min = sp.optimize.root(find_phi_min, (phi_max + r_r / r_0 * 4) / 5).x[
0
] # , r_r / r_0, phi_max)
# phi_min = 0 # r_r / r_0
phi = np.linspace(phi_min, phi_max, fp.num_profiles)
x = r_0 * (np.cos(phi) + phi * np.sin(phi)) - r_r * np.sin(phi)
y = r_0 * (np.sin(phi) - phi * np.cos(phi)) + r_r * np.cos(phi)
xy1 = np.array([x, y]).T
p_1 = xy1[0]
p_1_end = xy1[-1]
bsp_1 = Part.BSplineCurve()
bsp_1.interpolate(list(map(fcvec, xy1)))
w_1 = bsp_1.toShape()
xy2 = xy1 * np.array([1.0, -1.0])
p_2 = xy2[0]
p_2_end = xy2[-1]
bsp_2 = Part.BSplineCurve()
bsp_2.interpolate(list(map(fcvec, xy2)))
w_2 = bsp_2.toShape()
p_12 = np.array([r_0 - r_r, 0.0])
arc = Part.Arc(
App.Vector(*p_1, 0.0), App.Vector(*p_12, 0.0), App.Vector(*p_2, 0.0)
).toShape()
rot = rotation(-np.pi * 2 / teeth)
p_3 = rot(np.array([p_2_end]))[0]
# l = Part.LineSegment(fcvec(p_1_end), fcvec(p_3)).toShape()
l = part_arc_from_points_and_center(
p_1_end, p_3, np.array([0.0, 0.0])
).toShape()
w = Part.Wire([w_2, arc, w_1, l])
wires = [w]
rot = App.Matrix()
for _ in range(teeth - 1):
rot.rotateZ(np.pi * 2 / teeth)
wires.append(w.transformGeometry(rot))
wi = Part.Wire(wires)
if fp.height.Value == 0:
return wi
else:
return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))

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

@@ -0,0 +1,285 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears._functions import reflection
from .basegear import BaseGear, part_arc_from_points_and_center
class TimingGear(BaseGear):
"""FreeCAD gear rack"""
data = {
"gt2": {
"pitch": 2.0,
"u": 0.254,
"h": 0.75,
"H": 1.38,
"r0": 0.555,
"r1": 1.0,
"rs": 0.15,
"offset": 0.40,
},
"gt3": {
"pitch": 3.0,
"u": 0.381,
"h": 1.14,
"H": 2.40,
"r0": 0.85,
"r1": 1.52,
"rs": 0.25,
"offset": 0.61,
},
"gt5": {
"pitch": 5.0,
"u": 0.5715,
"h": 1.93,
"H": 3.81,
"r0": 1.44,
"r1": 2.57,
"rs": 0.416,
"offset": 1.03,
},
"gt8": {
"pitch": 8.0,
"u": 0.9144,
"h": 3.088,
"H": 6.096,
"r0": 2.304,
"r1": 4.112,
"rs": 0.6656,
"offset": 1.648,
},
"htd3": {
"pitch": 3.0,
"u": 0.381,
"h": 1.21,
"H": 2.40,
"r0": 0.89,
"r1": 0.89,
"rs": 0.26,
"offset": 0.0,
},
"htd5": {
"pitch": 5.0,
"u": 0.5715,
"h": 2.06,
"H": 3.80,
"r0": 1.49,
"r1": 1.49,
"rs": 0.43,
"offset": 0.0,
},
"htd8": {
"pitch": 8.0,
"u": 0.686,
"h": 3.45,
"H": 6.00,
"r0": 2.46,
"r1": 2.46,
"rs": 0.70,
"offset": 0.0,
},
}
def __init__(self, obj):
super(TimingGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyEnumeration", "type", "base", "type of timing-gear"
)
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "pitch", "computed", "pitch of gear", 1)
obj.addProperty(
"App::PropertyLength", "h", "computed", "radial height of teeth", 1
)
obj.addProperty(
"App::PropertyLength",
"u",
"computed",
"radial difference between pitch diameter and head of gear",
1,
)
obj.addProperty(
"App::PropertyLength", "r0", "computed", "radius of first arc", 1
)
obj.addProperty(
"App::PropertyLength", "r1", "computed", "radius of second arc", 1
)
obj.addProperty(
"App::PropertyLength", "rs", "computed", "radius of third arc", 1
)
obj.addProperty(
"App::PropertyLength",
"offset",
"computed",
"x-offset of second arc-midpoint",
1,
)
obj.teeth = 15
obj.type = ["gt2", "gt3", "gt5", "gt8", "htd3", "htd5", "htd8"]
obj.height = "5. mm"
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
# m ... center of arc/circle
# r ... radius of arc/circle
# x ... end-point of arc
# phi ... angle
tp = fp.type
gt_data = self.data[tp]
pitch = fp.pitch = gt_data["pitch"]
h = fp.h = gt_data["h"]
u = fp.u = gt_data["u"]
r_12 = fp.r0 = gt_data["r0"]
r_23 = fp.r1 = gt_data["r1"]
r_34 = fp.rs = gt_data["rs"]
offset = fp.offset = gt_data["offset"]
arcs = []
if offset == 0.0:
phi5 = np.pi / fp.teeth
ref = reflection(-phi5 - np.pi / 2.0)
rp = pitch * fp.teeth / np.pi / 2.0 - u
m_34 = np.array([-(r_12 + r_34), rp - h + r_12])
x2 = np.array([-r_12, m_34[1]])
x4 = np.array([m_34[0], m_34[1] + r_34])
x6 = ref(x4)
mir = np.array([-1.0, 1.0])
xn2 = mir * x2
xn4 = mir * x4
mn_34 = mir * m_34
arcs.append(part_arc_from_points_and_center(xn4, xn2, mn_34).toShape())
arcs.append(
Part.Arc(
App.Vector(*xn2, 0.0),
App.Vector(0, rp - h, 0.0),
App.Vector(*x2, 0.0),
).toShape()
)
arcs.append(part_arc_from_points_and_center(x2, x4, m_34).toShape())
arcs.append(
part_arc_from_points_and_center(x4, x6, np.array([0.0, 0.0])).toShape()
)
else:
phi_12 = np.arctan(np.sqrt(1.0 / (((r_12 - r_23) / offset) ** 2 - 1)))
rp = pitch * fp.teeth / np.pi / 2.0
r4 = r5 = rp - u
m_12 = np.array([0.0, r5 - h + r_12])
m_23 = np.array([offset, offset / np.tan(phi_12) + m_12[1]])
m_23y = m_23[1]
# solving for phi4:
# sympy.solve(
# ((r5 - r_34) * sin(phi4) + offset) ** 2 + \
# ((r5 - r_34) * cos(phi4) - m_23y) ** 2 - \
# ((r_34 + r_23) ** 2), phi4)
phi4 = 2 * np.arctan(
(
-2 * offset * r5
+ 2 * offset * r_34
+ np.sqrt(
-(m_23y**4)
- 2 * m_23y**2 * offset**2
+ 2 * m_23y**2 * r5**2
- 4 * m_23y**2 * r5 * r_34
+ 2 * m_23y**2 * r_23**2
+ 4 * m_23y**2 * r_23 * r_34
+ 4 * m_23y**2 * r_34**2
- offset**4
+ 2 * offset**2 * r5**2
- 4 * offset**2 * r5 * r_34
+ 2 * offset**2 * r_23**2
+ 4 * offset**2 * r_23 * r_34
+ 4 * offset**2 * r_34**2
- r5**4
+ 4 * r5**3 * r_34
+ 2 * r5**2 * r_23**2
+ 4 * r5**2 * r_23 * r_34
- 4 * r5**2 * r_34**2
- 4 * r5 * r_23**2 * r_34
- 8 * r5 * r_23 * r_34**2
- r_23**4
- 4 * r_23**3 * r_34
- 4 * r_23**2 * r_34**2
)
)
/ (
m_23y**2
+ 2 * m_23y * r5
- 2 * m_23y * r_34
+ offset**2
+ r5**2
- 2 * r5 * r_34
- r_23**2
- 2 * r_23 * r_34
)
)
phi5 = np.pi / fp.teeth
m_34 = (r5 - r_34) * np.array([-np.sin(phi4), np.cos(phi4)])
x2 = np.array([-r_12 * np.sin(phi_12), m_12[1] - r_12 * np.cos(phi_12)])
x3 = m_34 + r_34 / (r_34 + r_23) * (m_23 - m_34)
x4 = r4 * np.array([-np.sin(phi4), np.cos(phi4)])
ref = reflection(-phi5 - np.pi / 2)
x6 = ref(x4)
mir = np.array([-1.0, 1.0])
xn2 = mir * x2
xn3 = mir * x3
xn4 = mir * x4
mn_34 = mir * m_34
mn_23 = mir * m_23
arcs.append(part_arc_from_points_and_center(xn4, xn3, mn_34).toShape())
arcs.append(part_arc_from_points_and_center(xn3, xn2, mn_23).toShape())
arcs.append(part_arc_from_points_and_center(xn2, x2, m_12).toShape())
arcs.append(part_arc_from_points_and_center(x2, x3, m_23).toShape())
arcs.append(part_arc_from_points_and_center(x3, x4, m_34).toShape())
arcs.append(
part_arc_from_points_and_center(x4, x6, np.array([0.0, 0.0])).toShape()
)
wire = Part.Wire(arcs)
wires = [wire]
rot = App.Matrix()
rot.rotateZ(np.pi * 2 / fp.teeth)
for _ in range(fp.teeth - 1):
wire = wire.transformGeometry(rot)
wires.append(wire)
wi = Part.Wire(wires)
if fp.height.Value == 0:
return wi
else:
return Part.Face(wi).extrude(App.Vector(0, 0, fp.height))

View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import numpy as np
import scipy as sp
import FreeCAD as App
import Part
from pygears._functions import rotation, reflection
from .basegear import BaseGear, fcvec
class TimingGearT(BaseGear):
def __init__(self, obj):
print("hello gear")
obj.addProperty("App::PropertyLength", "pitch", "base", "pitch of gear")
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty(
"App::PropertyLength", "tooth_height", "base", "radial height of tooth"
)
obj.addProperty(
"App::PropertyLength",
"u",
"base",
"radial distance from tooth-head to pitch circle",
)
obj.addProperty("App::PropertyAngle", "alpha", "base", "angle of tooth flanks")
obj.addProperty("App::PropertyLength", "height", "base", "extrusion height")
obj.pitch = "5. mm"
obj.teeth = 15
obj.tooth_height = "1.2 mm"
obj.u = "0.6 mm"
obj.alpha = "40. deg"
obj.height = "5 mm"
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
print("generate gear shape")
pitch = fp.pitch.Value
teeth = fp.teeth
u = fp.u.Value
tooth_height = fp.tooth_height.Value
alpha = fp.alpha.Value / 180.0 * np.pi # we need radiant
height = fp.height.Value
r_p = pitch * teeth / 2.0 / np.pi
gamma_0 = pitch / r_p
gamma_1 = gamma_0 / 4
p_A = np.array([np.cos(-gamma_1), np.sin(-gamma_1)]) * (
r_p - u - tooth_height / 2
)
def line(s):
p = (
p_A
+ np.array([np.cos(alpha / 2 - gamma_1), np.sin(alpha / 2 - gamma_1)])
* s
)
return p
def dist_p1(s):
return (np.linalg.norm(line(s)) - (r_p - u - tooth_height)) ** 2
def dist_p2(s):
return (np.linalg.norm(line(s)) - (r_p - u)) ** 2
s1 = sp.optimize.minimize(dist_p1, 0.0).x
s2 = sp.optimize.minimize(dist_p2, 0.0).x
p_1 = line(s1)
p_2 = line(s2)
mirror = reflection(0.0) # reflect the points at the x-axis
p_3, p_4 = mirror(np.array([p_2, p_1]))
rot = rotation(-gamma_0) # why is the rotation in wrong direction ???
p_5 = rot(np.array([p_1]))[0] # the rotation expects a list of points
l1 = Part.LineSegment(fcvec(p_1), fcvec(p_2)).toShape()
l2 = Part.LineSegment(fcvec(p_2), fcvec(p_3)).toShape()
l3 = Part.LineSegment(fcvec(p_3), fcvec(p_4)).toShape()
l4 = Part.LineSegment(fcvec(p_4), fcvec(p_5)).toShape()
w = Part.Wire([l1, l2, l3, l4])
# now using a FreeCAD Matrix (this will turn in the right direction)
rot = App.Matrix()
rot.rotateZ(gamma_0)
wires = []
for i in range(teeth):
w = w.transformGeometry(rot)
wires.append(w.copy())
contour = Part.Wire(wires)
if height == 0:
return contour
else:
face = Part.Face(Part.Wire(wires))
return face.extrude(App.Vector(0.0, 0.0, height))

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

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * This program is free software: you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation, either version 3 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program. If not, see <http://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Part
import numpy as np
from pygears.involute_tooth import InvoluteTooth
from pygears._functions import rotation
from .basegear import BaseGear, helicalextrusion, fcvec
class WormGear(BaseGear):
"""FreeCAD gear rack"""
def __init__(self, obj):
super(WormGear, self).__init__(obj)
obj.addProperty("App::PropertyInteger", "teeth", "base", "number of teeth")
obj.addProperty("App::PropertyLength", "module", "base", "module")
obj.addProperty("App::PropertyLength", "height", "base", "height")
obj.addProperty("App::PropertyLength", "diameter", "base", "diameter")
obj.addProperty("App::PropertyAngle", "beta", "computed", "beta ", 1)
obj.addProperty(
"App::PropertyAngle", "pressure_angle", "involute", "pressure angle"
)
obj.addProperty(
"App::PropertyBool", "reverse_pitch", "base", "reverse rotation of helix"
)
obj.addProperty(
"App::PropertyFloat",
"head",
"tolerance",
"head * module = additional length of head",
)
obj.addProperty(
"App::PropertyFloat",
"clearance",
"tolerance",
"clearance * module = additional length of root",
)
obj.teeth = 3
obj.module = "1. mm"
obj.pressure_angle = "20. deg"
obj.height = "5. mm"
obj.diameter = "5. mm"
obj.clearance = 0.25
obj.head = 0
obj.reverse_pitch = False
self.obj = obj
obj.Proxy = self
def generate_gear_shape(self, fp):
m = fp.module.Value
d = fp.diameter.Value
t = fp.teeth
h = fp.height
clearance = fp.clearance
head = fp.head
alpha = fp.pressure_angle.Value
beta = np.arctan(m * t / d)
fp.beta = np.rad2deg(beta)
beta = -(fp.reverse_pitch * 2 - 1) * (np.pi / 2 - beta)
r_1 = (d - (2 + 2 * clearance) * m) / 2
r_2 = (d + (2 + 2 * head) * m) / 2
z_a = (2 + head + clearance) * m * np.tan(np.deg2rad(alpha))
z_b = (m * np.pi - 4 * m * np.tan(np.deg2rad(alpha))) / 2
z_0 = clearance * m * np.tan(np.deg2rad(alpha))
z_1 = z_b - z_0
z_2 = z_1 + z_a
z_3 = z_2 + z_b - 2 * head * m * np.tan(np.deg2rad(alpha))
z_4 = z_3 + z_a
def helical_projection(r, z):
phi = 2 * z / m / t
x = r * np.cos(phi)
y = r * np.sin(phi)
z = 0 * y
return np.array([x, y, z]).T
# create a circle from phi=0 to phi_1 with r_1
phi_0 = 2 * z_0 / m / t
phi_1 = 2 * z_1 / m / t
c1 = Part.makeCircle(
r_1,
App.Vector(0, 0, 0),
App.Vector(0, 0, 1),
np.rad2deg(phi_0),
np.rad2deg(phi_1),
)
# create first bspline
z_values = np.linspace(z_1, z_2, 10)
r_values = np.linspace(r_1, r_2, 10)
points = helical_projection(r_values, z_values)
bsp1 = Part.BSplineCurve()
bsp1.interpolate(list(map(fcvec, points)))
bsp1 = bsp1.toShape()
# create circle from phi_2 to phi_3
phi_2 = 2 * z_2 / m / t
phi_3 = 2 * z_3 / m / t
c2 = Part.makeCircle(
r_2,
App.Vector(0, 0, 0),
App.Vector(0, 0, 1),
np.rad2deg(phi_2),
np.rad2deg(phi_3),
)
# create second bspline
z_values = np.linspace(z_3, z_4, 10)
r_values = np.linspace(r_2, r_1, 10)
points = helical_projection(r_values, z_values)
bsp2 = Part.BSplineCurve()
bsp2.interpolate(list(map(fcvec, points)))
bsp2 = bsp2.toShape()
wire = Part.Wire([c1, bsp1, c2, bsp2])
w_all = [wire]
rot = App.Matrix()
rot.rotateZ(2 * np.pi / t)
for i in range(1, t):
w_all.append(w_all[-1].transformGeometry(rot))
full_wire = Part.Wire(w_all)
if h == 0:
return full_wire
else:
shape = helicalextrusion(Part.Face(full_wire), h, h * np.tan(beta) * 2 / d)
return shape

View File

@@ -2,11 +2,11 @@
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata"> <package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>freecad.gears workbench</name> <name>freecad.gears workbench</name>
<description>A gear workbench for FreeCAD</description> <description>A gear workbench for FreeCAD</description>
<version>1.0</version> <version>1.2</version>
<date>2022-02-07</date> <date>2022-02-07</date>
<maintainer email="sppedflyer@gmail.com">looooo</maintainer> <maintainer email="sppedflyer@gmail.com">looooo</maintainer>
<license file="LICENSE">GPL 3</license> <license file="LICENSE">GPL 3</license>
<url type="repository" branch="develop">https://github.com/looooo/freecad.gears</url> <url type="repository" branch="master">https://github.com/looooo/freecad.gears</url>
<url type="bugtracker">https://github.com/looooo/freecad.gears/issues</url> <url type="bugtracker">https://github.com/looooo/freecad.gears/issues</url>
<url type="documentation">https://wiki.freecad.org/FCGear_Workbench</url> <url type="documentation">https://wiki.freecad.org/FCGear_Workbench</url>
<icon>freecad/gears/icons/gearworkbench.svg</icon> <icon>freecad/gears/icons/gearworkbench.svg</icon>

View File

@@ -16,4 +16,4 @@
# * * # * *
# *************************************************************************** # ***************************************************************************
__version__ = "0.0.4" __version__ = "1.2.0"

View File

@@ -22,55 +22,64 @@ from numpy.linalg import solve, norm
def reflection(angle): def reflection(angle):
mat = array( mat = array([[cos(2 * angle), -sin(2 * angle)],
[[cos(2 * angle), -sin(2 * angle)], [-sin(2 * angle), -cos(2 * angle)]]) [-sin(2 * angle), -cos(2 * angle)]])
def func(x): def func(x):
return(dot(x, mat)) # why not use mat @ x???
return(func) return dot(x, mat)
return func
def reflection3D(angle): def reflection3D(angle):
mat = array([[cos(2 * angle), -sin(2 * angle), 0.], mat = array(
[-sin(2 * angle), -cos(2 * angle), 0.], [0., 0., 1.]]) [
[cos(2 * angle), -sin(2 * angle), 0.0],
[-sin(2 * angle), -cos(2 * angle), 0.0],
[0.0, 0.0, 1.0],
]
)
def func(x): def func(x):
return(dot(x, mat)) # why not use mat @ x
return(func) return dot(x, mat)
return func
def rotation(angle, midpoint=None): def rotation(angle, midpoint=None):
midpoint = midpoint or [0., 0.] midpoint = midpoint or [0.0, 0.0]
mat = array([[cos(angle), -sin(angle)], mat = array([[cos(angle), -sin(angle)], [sin(angle), cos(angle)]])
[sin(angle), cos(angle)]])
midpoint = array(midpoint) midpoint = array(midpoint)
vec = midpoint - dot(midpoint, mat) vec = midpoint - dot(midpoint, mat)
trans = translation(vec) trans = translation(vec)
def func(xx): def func(xx):
return(trans(dot(xx, mat))) return trans(dot(xx, mat))
return(func)
return func
def rotation3D(angle): def rotation3D(angle):
mat = array( 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): def func(xx):
return(dot(xx, mat)) return dot(xx, mat)
return(func)
return func
def translation(vec): def translation(vec):
def trans(x): def trans(x):
return([x[0] + vec[0], x[1] + vec[1]]) return [x[0] + vec[0], x[1] + vec[1]]
def func(x): def func(x):
return(array(list(map(trans, x)))) return array(list(map(trans, x)))
return(func)
return func
def trim(p1, p2, p3, p4): def trim(p1, p2, p3, p4):
@@ -80,31 +89,31 @@ def trim(p1, p2, p3, p4):
a4 = array(p4) a4 = array(p4)
if all(a1 == a2) or all(a3 == a4): if all(a1 == a2) or all(a3 == a4):
if all(a1 == a3): if all(a1 == a3):
return(a1) return a1
else: else:
return(False) return False
elif all(a1 == a3): elif all(a1 == a3):
if all(a2 == a4): if all(a2 == a4):
return((a1 + a2) / 2) return (a1 + a2) / 2
else: else:
return(a1) return a1
elif all(a1 == a4): elif all(a1 == a4):
if all(a2 == a3): if all(a2 == a3):
return((a1 + a2) / 2) return (a1 + a2) / 2
else: else:
return(a1) return a1
elif all(a2 == a3) or all(a2 == a4): elif all(a2 == a3) or all(a2 == a4):
return(p2) return p2
try: try:
g, h = solve(transpose([-a2 + a1, a4 - a3]), a1 - a3) g, h = solve(transpose([-a2 + a1, a4 - a3]), a1 - a3)
except Exception as e: except Exception as e:
print(e) print(e)
return(False) return False
else: else:
if 0. < g < 1. and 0. < h < 1.: if 0.0 < g < 1.0 and 0.0 < h < 1.0:
return(a1 + g * (a2 - a1)) return a1 + g * (a2 - a1)
else: else:
return(False) return False
def trimfunc(l1, l2): def trimfunc(l1, l2):
@@ -124,12 +133,12 @@ def trimfunc(l1, l2):
l2 == [l2[0]] l2 == [l2[0]]
else: else:
l2 = l2[jk::-1] l2 = l2[jk::-1]
return([vstack([l1, [s]]), vstack([[s], l2])]) return [vstack([l1, [s]]), vstack([[s], l2])]
j0 = j j0 = j
jk += 1 jk += 1
i0 = i i0 = i
ik += 1 ik += 1
return(False) return False
def diff_norm(vec1, vec2): def diff_norm(vec1, vec2):
@@ -141,7 +150,7 @@ def nearestpts(evolv, underc):
ik = 0 ik = 0
iout = 0 iout = 0
jout = 0 jout = 0
outmin = 1000. outmin = 1000.0
for i in array(evolv[1:]): for i in array(evolv[1:]):
jk = 0 jk = 0
for j in array(underc[1:]): for j in array(underc[1:]):
@@ -154,7 +163,7 @@ def nearestpts(evolv, underc):
iout, jout = [ik, jk] iout, jout = [ik, jk]
jk += 1 jk += 1
ik += 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): def intersection_line_circle(p1, p2, r):
@@ -176,5 +185,3 @@ def arc_from_points_and_center(p_1, p_2, m):
v /= norm(v) v /= norm(v)
p_12 = m + v * r p_12 = m + v * r
return (p_1, p_12, p_2) return (p_1, p_12, p_2)

View File

@@ -18,13 +18,32 @@
from __future__ import division from __future__ import division
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 from ._functions import rotation3D, reflection3D, intersection_line_circle
class BevelTooth(object): class BevelTooth(object):
def __init__(self, pressure_angle=70 * pi / 180, pitch_angle=pi / 4, clearance=0.1, def __init__(
z=21, backlash=0.00, module=0.25): 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.pressure_angle = pressure_angle
self.pitch_angle = pitch_angle self.pitch_angle = pitch_angle
self.z = z self.z = z
@@ -34,58 +53,146 @@ class BevelTooth(object):
self.module = module self.module = module
self.involute_end = arccos( self.involute_end = arccos(
1 / sqrt(2) * sqrt((42. + 16.*cos(2.*self.pressure_angle) + 1
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) - / sqrt(2)
4.*cos(4.*self.pressure_angle - 2.*self.pitch_angle) + 24.*cos(2.*self.pitch_angle) - 2.*cos(4.*self.pitch_angle) - * sqrt(
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) + 42.0
4.*cos(2.*self.pressure_angle - (4.*sin(self.pitch_angle))/self.z) + 4.*cos(2.*self.pressure_angle - + 16.0 * cos(2.0 * self.pressure_angle)
4.*self.pitch_angle - (4.*sin(self.pitch_angle))/self.z) - 8.*cos(2.*self.pressure_angle - 2.*self.pitch_angle - + 6.0 * cos(4.0 * self.pressure_angle)
(4.*sin(self.pitch_angle))/self.z) + 24.*cos(4.*(self.pitch_angle + sin(self.pitch_angle)/self.z)) - + cos(4.0 * self.pressure_angle - 4.0 * self.pitch_angle)
8.*cos(2.*(self.pressure_angle + self.pitch_angle + (2.*sin(self.pitch_angle))/self.z)) + 4.*cos(2.*self.pressure_angle + - 8.0 * cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
(4.*sin(self.pitch_angle))/self.z) + 16.*cos(2.*self.pitch_angle + (4.*sin(self.pitch_angle))/self.z) + - 4.0 * cos(4.0 * self.pressure_angle - 2.0 * self.pitch_angle)
4.*cos(2.*self.pressure_angle + 4.*self.pitch_angle + (4.*sin(self.pitch_angle))/self.z) + 32.*abs(cos(self.pitch_angle + + 24.0 * cos(2.0 * self.pitch_angle)
(2.*sin(self.pitch_angle))/self.z))*cos(self.pressure_angle)*sqrt(4.*cos(2.*self.pressure_angle) - - 2.0 * cos(4.0 * self.pitch_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)) + - 8.0 * cos(2.0 * (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(4.0 * (self.pressure_angle + self.pitch_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)) - 4.0 * cos(4.0 * self.pressure_angle + 2.0 * self.pitch_angle)
+ 24.0 * cos((4.0 * sin(self.pitch_angle)) / self.z)
+ 4.0
* cos(
2.0 * self.pressure_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
+ 4.0
* cos(
2.0 * self.pressure_angle
- 4.0 * self.pitch_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
- 8.0
* cos(
2.0 * self.pressure_angle
- 2.0 * self.pitch_angle
- (4.0 * sin(self.pitch_angle)) / self.z
)
+ 24.0
* cos(4.0 * (self.pitch_angle + sin(self.pitch_angle) / self.z))
- 8.0
* cos(
2.0
* (
self.pressure_angle
+ self.pitch_angle
+ (2.0 * sin(self.pitch_angle)) / self.z
)
)
+ 4.0
* cos(
2.0 * self.pressure_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
+ 16.0
* cos(
2.0 * self.pitch_angle + (4.0 * sin(self.pitch_angle)) / self.z
)
+ 4.0
* cos(
2.0 * self.pressure_angle
+ 4.0 * self.pitch_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
+ 32.0
* abs(
cos(self.pitch_angle + (2.0 * sin(self.pitch_angle)) / self.z)
)
* cos(self.pressure_angle)
* sqrt(
4.0 * cos(2.0 * self.pressure_angle)
- 2.0
* (
-2.0
+ cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
- 2.0 * cos(2.0 * self.pitch_angle)
+ cos(2.0 * (self.pressure_angle + self.pitch_angle))
+ 4.0
* cos(
2.0 * self.pitch_angle
+ (4.0 * sin(self.pitch_angle)) / self.z
)
)
)
* sin(2.0 * self.pitch_angle)
)
/ (
-6.0
- 2.0 * cos(2.0 * self.pressure_angle)
+ cos(2.0 * self.pressure_angle - 2.0 * self.pitch_angle)
- 2.0 * cos(2.0 * self.pitch_angle)
+ cos(2.0 * (self.pressure_angle + self.pitch_angle))
)
** 2
)
)
self.involute_start = -pi/2. + \ self.involute_start = -pi / 2.0 + arctan(
arctan(1/tan(self.pitch_angle)*1/cos(self.pressure_angle)) 1 / tan(self.pitch_angle) * 1 / cos(self.pressure_angle)
)
self.involute_start_radius = self.get_radius(self.involute_start) self.involute_start_radius = self.get_radius(self.involute_start)
self.r_f = sin(self.pitch_angle - sin(pitch_angle) * 2 / self.r_f = sin(
self.z) - self.clearance * sin(self.pitch_angle) 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.z_f = cos(self.pitch_angle - sin(pitch_angle) * 2 / self.z)
self.add_foot = True self.add_foot = True
def involute_function_x(self): def involute_function_x(self):
def func(s): def func(s):
return(( return -(
-(cos(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))*sin(self.pressure_angle)*sin(s)) + 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*1/sin(self.pressure_angle)*1/sin(self.pitch_angle)))) * sin(s)
return(func) ) + (
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 involute_function_y(self):
def func(s): def func(s):
return(( return cos(s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle)) * (
cos(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle))*(cos(s)*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) * + cos(self.pressure_angle) * cos(self.pitch_angle) * sin(s)
sin(s*1/sin(self.pressure_angle)*1/sin(self.pitch_angle)))) ) + sin(self.pressure_angle) * sin(s) * sin(
return(func) s * 1 / sin(self.pressure_angle) * 1 / sin(self.pitch_angle)
)
return func
def involute_function_z(self): def involute_function_z(self):
def func(s): def func(s):
return(( return cos(self.pitch_angle) * cos(s) - cos(self.pressure_angle) * sin(
cos(self.pitch_angle)*cos(s) - cos(self.pressure_angle)*sin(self.pitch_angle)*sin(s))) self.pitch_angle
return(func) ) * sin(s)
return func
def get_radius(self, s): def get_radius(self, s):
x = self.involute_function_x() x = self.involute_function_x()
y = self.involute_function_y() y = self.involute_function_y()
rx = x(s) rx = x(s)
ry = y(s) ry = y(s)
return(sqrt(rx**2 + ry**2)) return sqrt(rx**2 + ry**2)
def involute_points(self, num=10): def involute_points(self, num=10):
pts = linspace(self.involute_start, self.involute_end, num=num) pts = linspace(self.involute_start, self.involute_end, num=num)
@@ -111,7 +218,7 @@ class BevelTooth(object):
xyz = [[p[0], p[1], 1] for p in xy] xyz = [[p[0], p[1], 1] for p in xy]
backlash_rot = rotation3D(self.angular_backlash / 2) backlash_rot = rotation3D(self.angular_backlash / 2)
xyz = backlash_rot(xyz) xyz = backlash_rot(xyz)
return(xyz) return xyz
def points(self, num=10): def points(self, num=10):
pts = self.involute_points(num=num) pts = self.involute_points(num=num)
@@ -120,18 +227,22 @@ class BevelTooth(object):
ref = reflection3D(pi / 2) ref = reflection3D(pi / 2)
pts1 = ref(pts)[::-1] pts1 = ref(pts)[::-1]
if self.add_foot: if self.add_foot:
return([ return [
array([pts[0], pts[1]]), array([pts[0], pts[1]]),
array(pts[1:]), array(pts[1:]),
array([pts[-1], pts1[0]]), array([pts[-1], pts1[0]]),
array(pts1[:-1]), array(pts1[:-1]),
array([pts1[-2], pts1[-1]]) array([pts1[-2], pts1[-1]]),
]) ]
else: else:
return([pts, array([pts[-1], pts1[0]]), pts1]) return [pts, array([pts[-1], pts1[0]]), pts1]
def _update(self): def _update(self):
self.__init__(z=self.z, clearance=self.clearance, self.__init__(
z=self.z,
clearance=self.clearance,
pressure_angle=self.pressure_angle, pressure_angle=self.pressure_angle,
pitch_angle=self.pitch_angle, pitch_angle=self.pitch_angle,
backlash=self.backlash, module=self.module) backlash=self.backlash,
module=self.module,
)

View File

@@ -33,6 +33,7 @@ def compute_shifted_gears(m, alpha, t1, t2, x1, x2):
Returns: Returns:
(float, float): distance between gears [length], pressure angle of the assembly [rad] (float, float): distance between gears [length], pressure angle of the assembly [rad]
""" """
def inv(x): def inv(x):
return np.tan(x) - x return np.tan(x) - x
@@ -42,7 +43,7 @@ def compute_shifted_gears(m, alpha, t1, t2, x1, x2):
return inv(x) - inv_alpha_w return inv(x) - inv_alpha_w
def d_root_inv(x): def d_root_inv(x):
return 1. / np.cos(x) - 1 return 1.0 / np.cos(x) - 1
alpha_w = find_root(alpha, root_inv, d_root_inv) alpha_w = find_root(alpha, root_inv, d_root_inv)
dist = m * (t1 + t2) / 2 * np.cos(alpha) / np.cos(alpha_w) dist = m * (t1 + t2) / 2 * np.cos(alpha) / np.cos(alpha_w)

View File

@@ -21,7 +21,7 @@ from numpy import cos, sin, arccos, pi, array, linspace, transpose, vstack
from ._functions import rotation, reflection from ._functions import rotation, reflection
class CycloidTooth(): class CycloidTooth:
def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.25, backlash=0.00, head=0.0): def __init__(self, z1=5, z2=5, z=14, m=5, clearance=0.25, backlash=0.00, head=0.0):
self.m = m self.m = m
self.z = z self.z = z
@@ -44,40 +44,58 @@ class CycloidTooth():
def epicycloid_x(self): def epicycloid_x(self):
def func(t): def func(t):
return(((self.d2 + self.d) * cos(t))/2. - (self.d2 * cos((1 + self.d / self.d2) * t))/2.) return ((self.d2 + self.d) * cos(t)) / 2.0 - (
return(func) self.d2 * cos((1 + self.d / self.d2) * t)
) / 2.0
return func
def epicycloid_y(self): def epicycloid_y(self):
def func(t): def func(t):
return(((self.d2 + self.d) * sin(t))/2. - (self.d2 * sin((1 + self.d / self.d2) * t))/2.) return ((self.d2 + self.d) * sin(t)) / 2.0 - (
return(func) self.d2 * sin((1 + self.d / self.d2) * t)
) / 2.0
return func
def hypocycloid_x(self): def hypocycloid_x(self):
def func(t): def func(t):
return((self.d - self.d1)*cos(t)/2 + self.d1/2 * cos((self.d / self.d1 - 1) * t)) return (self.d - self.d1) * cos(t) / 2 + self.d1 / 2 * cos(
return(func) (self.d / self.d1 - 1) * t
)
return func
def hypocycloid_y(self): def hypocycloid_y(self):
def func(t): def func(t):
return((self.d - self.d1)*sin(t)/2 - self.d1/2 * sin((self.d/self.d1 - 1)*t)) return (self.d - self.d1) * sin(t) / 2 - self.d1 / 2 * sin(
return(func) (self.d / self.d1 - 1) * t
)
return func
def inner_end(self): def inner_end(self):
return( 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.d1 - self.d))))/self.d) * arccos(
(2 * self.d1**2 - self.di**2 - 2 * self.d1 * self.d + self.d**2)
/ (2.0 * self.d1 * (self.d1 - self.d))
)
)
/ self.d
) )
def outer_end(self): def outer_end(self):
return ( return (
(self.d2*arccos((2*self.d2**2 - self.da**2 + self.d2
2*self.d2*self.d + self.d**2) / * arccos(
(2.*self.d2*(self.d2 + self.d))))/self.d (2 * self.d2**2 - self.da**2 + 2 * self.d2 * self.d + self.d**2)
/ (2.0 * self.d2 * (self.d2 + self.d))
) )
) / self.d
def points(self, num=10): def points(self, num=10):
inner_x = self.hypocycloid_x() inner_x = self.hypocycloid_x()
inner_y = self.hypocycloid_y() inner_y = self.hypocycloid_y()
outer_x = self.epicycloid_x() outer_x = self.epicycloid_x()
@@ -95,12 +113,18 @@ class CycloidTooth():
pts1 = vstack([pts_inner[:-2], pts_outer]) pts1 = vstack([pts_inner[:-2], pts_outer])
rot = rotation(self.phipart / 4 - self.angular_backlash / 2) rot = rotation(self.phipart / 4 - self.angular_backlash / 2)
pts1 = rot(pts1) pts1 = rot(pts1)
ref = reflection(0.) ref = reflection(0.0)
pts2 = ref(pts1)[::-1] pts2 = ref(pts1)[::-1]
one_tooth = [pts1, array([pts1[-1], pts2[0]]), pts2] one_tooth = [pts1, array([pts1[-1], pts2[0]]), pts2]
return(one_tooth) return one_tooth
def _update(self): def _update(self):
self.__init__(m=self.m, z=self.z, z1=self.z1, z2=self.z2, self.__init__(
clearance=self.clearance, backlash=self.backlash, head=self.head) m=self.m,
z=self.z,
z1=self.z1,
z2=self.z2,
clearance=self.clearance,
backlash=self.backlash,
head=self.head,
)

View File

@@ -17,13 +17,43 @@
# *************************************************************************** # ***************************************************************************
from __future__ import division from __future__ import division
from numpy import tan, cos, sin, sqrt, arctan, pi, array, linspace, transpose, vstack, ndarray from numpy import (
from ._functions import nearestpts, rotation, reflection, trimfunc, diff_norm, translation tan,
cos,
sin,
sqrt,
arctan,
pi,
array,
linspace,
transpose,
vstack,
ndarray,
)
from ._functions import (
nearestpts,
rotation,
reflection,
trimfunc,
diff_norm,
translation,
)
class InvoluteTooth(): class InvoluteTooth:
def __init__(self, m=5, z=15, pressure_angle=20 * pi / 180., clearance=0.12, shift=0.5, beta=0., def __init__(
undercut=False, backlash=0.00, head=0.00, properties_from_tool=False): self,
m=5,
z=15,
pressure_angle=20 * pi / 180.0,
clearance=0.12,
shift=0.5,
beta=0.0,
undercut=False,
backlash=0.00,
head=0.00,
properties_from_tool=False,
):
self.pressure_angle = pressure_angle self.pressure_angle = pressure_angle
self.beta = beta self.beta = beta
self.m_n = m self.m_n = m
@@ -38,8 +68,7 @@ class InvoluteTooth():
def _calc_gear_factors(self): def _calc_gear_factors(self):
if self.properties_from_tool: if self.properties_from_tool:
self.pressure_angle_t = arctan( self.pressure_angle_t = arctan(tan(self.pressure_angle) / cos(self.beta))
tan(self.pressure_angle) / cos(self.beta))
self.m = self.m_n / cos(self.beta) self.m = self.m_n / cos(self.beta)
else: else:
self.pressure_angle_t = self.pressure_angle self.pressure_angle_t = self.pressure_angle
@@ -47,30 +76,45 @@ class InvoluteTooth():
self.pitch = self.m * pi self.pitch = self.m * pi
self.c = self.clearance * self.m_n self.c = self.clearance * self.m_n
self.midpoint = [0., 0.] self.midpoint = [0.0, 0.0]
self.d = self.z * self.m self.d = self.z * self.m
self.dw = self.m * self.z self.dw = self.m * self.z
self.da = self.dw + 2. * self.m_n + 2. * \ self.da = self.dw + 2.0 * self.m_n + 2.0 * (self.shift + self.head) * self.m_n
(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.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.dg = self.d * cos(self.pressure_angle_t)
self.phipart = 2 * pi / self.z self.phipart = 2 * pi / self.z
self.undercut_end = sqrt(-self.df ** 2 + self.da ** 2) / self.da 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.undercut_rot = (
(self.c + self.m_n) * tan(self.pressure_angle_t))) / self.df))) -self.df
/ self.dw
* tan(
arctan(
(
2
* (
(self.m * pi) / 4.0
- (self.c + self.m_n) * tan(self.pressure_angle_t)
)
)
/ self.df
)
)
)
self.involute_end = sqrt(self.da**2 - self.dg**2) / self.dg self.involute_end = sqrt(self.da**2 - self.dg**2) / self.dg
self.involute_rot1 = sqrt(-self.dg ** 2 + (self.dw) ** 2) / self.dg - arctan( self.involute_rot1 = sqrt(-(self.dg**2) + (self.dw) ** 2) / self.dg - arctan(
sqrt(-self.dg ** 2 + (self.dw) ** 2) / self.dg) 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 = (
self.involute_rot2 = 1 / self.z * \ self.m / (self.d) * (pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
(pi / 2 + 2 * self.shift * tan(self.pressure_angle_t)) )
self.involute_rot2 = (
1 / self.z * (pi / 2 + 2 * self.shift * tan(self.pressure_angle_t))
)
self.involute_rot = self.involute_rot1 + self.involute_rot2 self.involute_rot = self.involute_rot1 + self.involute_rot2
self.angular_backlash = self.backlash / (self.d / 2) self.angular_backlash = self.backlash / (self.d / 2)
self.involute_start = 0. self.involute_start = 0.0
if self.dg <= self.df: 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
@@ -82,9 +126,10 @@ class InvoluteTooth():
y = array(list(map(fy, pts))) y = array(list(map(fy, pts)))
xy = transpose([x, y]) xy = transpose([x, y])
rotate = rotation( rotate = rotation(
self.undercut_rot + self.phipart / 2 - self.angular_backlash / 2) self.undercut_rot + self.phipart / 2 - self.angular_backlash / 2
)
xy = rotate(xy) xy = rotate(xy)
return(array(xy)) return array(xy)
def involute_points(self, num=10): def involute_points(self, num=10):
pts = linspace(self.involute_start, self.involute_end, num=num) pts = linspace(self.involute_start, self.involute_end, num=num)
@@ -94,7 +139,7 @@ class InvoluteTooth():
y = array(list(map(fy, pts))) y = array(list(map(fy, pts)))
rot = rotation(self.involute_rot - self.angular_backlash / 2) rot = rotation(self.involute_rot - self.angular_backlash / 2)
xy = rot(transpose(array([x, y]))) xy = rot(transpose(array([x, y])))
return(xy) return xy
def points(self, num=10): def points(self, num=10):
l1 = self.undercut_points(num=num) l1 = self.undercut_points(num=num)
@@ -109,7 +154,8 @@ class InvoluteTooth():
u1 = False u1 = False
if self.dg > self.df: if self.dg > self.df:
u1 = vstack( 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 e1 = l2
else: else:
e1 = l2 e1 = l2
@@ -121,35 +167,39 @@ class InvoluteTooth():
else: else:
u2 = reflect(u1)[::-1] u2 = reflect(u1)[::-1]
one_tooth = [u1, e1, [e1[-1], e2[0]], e2, u2] one_tooth = [u1, e1, [e1[-1], e2[0]], e2, u2]
return(one_tooth) return one_tooth
def gearfunc(self, x): def gearfunc(self, x):
rot = rotation(2 * x / self.dw, self.midpoint) rot = rotation(2 * x / self.dw, self.midpoint)
return(rot) return rot
def undercut_function_x(self): def undercut_function_x(self):
def func(psi): def func(psi):
return( return cos(psi - (self.df * tan(psi)) / self.dw) * sqrt(
cos(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 + self.df**2 / 4 + (self.df**2 * tan(psi) ** 2) / 4.0
(self.df ** 2 * tan(psi) ** 2) / 4.)) )
return(func)
return func
def undercut_function_y(self): def undercut_function_y(self):
def func(psi): def func(psi):
return( return sin(psi - (self.df * tan(psi)) / self.dw) * sqrt(
sin(psi - (self.df * tan(psi)) / self.dw) * sqrt(self.df ** 2 / 4 + self.df**2 / 4 + (self.df**2 * tan(psi) ** 2) / 4.0
(self.df ** 2 * tan(psi) ** 2) / 4.)) )
return(func)
return func
def involute_function_x(self): def involute_function_x(self):
def func(phi): def func(phi):
return(self.dg / 2 * cos(phi) + phi * self.dg / 2 * sin(phi)) return self.dg / 2 * cos(phi) + phi * self.dg / 2 * sin(phi)
return(func)
return func
def involute_function_y(self): def involute_function_y(self):
def func(phi): def func(phi):
return(self.dg / 2 * sin(phi) - phi * self.dg / 2 * cos(phi)) return self.dg / 2 * sin(phi) - phi * self.dg / 2 * cos(phi)
return(func)
return func
def _update(self): def _update(self):
if not hasattr(self, "properties_from_tool"): if not hasattr(self, "properties_from_tool"):
@@ -158,8 +208,19 @@ class InvoluteTooth():
class InvoluteRack(object): class InvoluteRack(object):
def __init__(self, m=5, z=15, pressure_angle=20 * pi / 180., thickness=5, beta=0, head=0, clearance=0.25, def __init__(
properties_from_tool=False, add_endings=False, simplified=False): self,
m=5,
z=15,
pressure_angle=20 * pi / 180.0,
thickness=5,
beta=0,
head=0,
clearance=0.25,
properties_from_tool=False,
add_endings=False,
simplified=False,
):
self.pressure_angle = pressure_angle self.pressure_angle = pressure_angle
self.thickness = thickness self.thickness = thickness
self.m = m self.m = m
@@ -171,7 +232,6 @@ class InvoluteRack(object):
self.add_endings = add_endings self.add_endings = add_endings
self.simplified = simplified 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): def _update(self):
if not hasattr(self, "add_endings"): if not hasattr(self, "add_endings"):
@@ -180,7 +240,6 @@ class InvoluteRack(object):
self.simplified = False self.simplified = False
def points(self, num=10): def points(self, num=10):
import copy
m, m_n, pitch, pressure_angle_t = self.compute_properties() m, m_n, pitch, pressure_angle_t = self.compute_properties()
a = (2 + self.head + self.clearance) * m_n * tan(pressure_angle_t) a = (2 + self.head + self.clearance) * m_n * tan(pressure_angle_t)
@@ -189,16 +248,16 @@ class InvoluteRack(object):
[-m_n * (1 + self.clearance), -a - b], [-m_n * (1 + self.clearance), -a - b],
[m_n * (1 + self.head), -b], [m_n * (1 + self.head), -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] teeth = [tooth]
trans = translation([0., pitch, 0.]) trans = translation([0.0, pitch, 0.0])
for i in range(self.z - 1): for i in range(self.z - 1):
if self.simplified and i > 3 and i < (self.z - 6): if self.simplified and i > 3 and i < (self.z - 6):
tooth = trans(tooth).tolist() tooth = trans(tooth).tolist()
else: else:
tooth = trans(tooth).tolist() tooth = trans(tooth).tolist()
teeth.append(copy.deepcopy(tooth)) teeth.append(tooth.copy())
if self.simplified and (i == 3): if self.simplified and (i == 3):
teeth[-1].pop() teeth[-1].pop()
teeth[-1].pop() teeth[-1].pop()
@@ -212,9 +271,13 @@ class InvoluteRack(object):
teeth = array([v for t in teeth for v in t]) # flattening teeth = array([v for t in teeth for v in t]) # flattening
if self.add_endings: if self.add_endings:
ext1 = teeth[0] + array([0., a + b - pitch / 2]) ext1 = teeth[0] + array([0.0, a + b - pitch / 2])
ext2 = teeth[-1] - array([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()] teeth = (
[ext1.tolist(), ext1.tolist()]
+ teeth.tolist()
+ [ext2.tolist(), ext2.tolist()]
)
else: else:
teeth = [teeth[0].tolist()] + teeth.tolist() + [teeth[-1].tolist()] teeth = [teeth[0].tolist()] + teeth.tolist() + [teeth[-1].tolist()]
# teeth.append(list(teeth[-1])) # teeth.append(list(teeth[-1]))

View File

@@ -7,6 +7,7 @@ from ._functions import rotation, rotation3D
class _GearProfile(object): class _GearProfile(object):
rot3D = False rot3D = False
def profile(self, num=10): def profile(self, num=10):
tooth = self.points(num=num) tooth = self.points(num=num)
tooth = [list(point) for wire in tooth for point in wire] tooth = [list(point) for wire in tooth for point in wire]
@@ -21,19 +22,19 @@ class _GearProfile(object):
profile.append(profile[0]) profile.append(profile[0])
return np.array(profile) return np.array(profile)
class InvoluteProfile(InvoluteTooth, _GearProfile): class InvoluteProfile(InvoluteTooth, _GearProfile):
pass pass
class CycloidProfile(CycloidTooth, _GearProfile): class CycloidProfile(CycloidTooth, _GearProfile):
pass pass
class BevelProfile(BevelTooth, _GearProfile): class BevelProfile(BevelTooth, _GearProfile):
rot3D = True rot3D = True
class InvoluteRackProfile(InvoluteRack): class InvoluteRackProfile(InvoluteRack):
def profile(self): def profile(self):
return self.points() return self.points()

View File

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