cadquery-freecad-module/ThirdParty/cqparts_bearings/ball.py

263 lines
9.4 KiB
Python

from math import pi, radians, sin, cos, asin
import cadquery
import cqparts
from cqparts.params import *
from cqparts import constraint
from cqparts.constraint import Mate
from cqparts.utils import CoordSystem
from . import register
class _Ring(cqparts.Part):
# Basic shape
outer_radius = PositiveFloat(10, doc="outside radius")
inner_radius = PositiveFloat(8, doc="inside radius")
width = PositiveFloat(5, doc="ring's width")
# Ball rails
ball_radius = PositiveFloat(3, doc="ball bearing radius")
rolling_radius = PositiveFloat(6, doc="distance of ball's center from bearing's axis")
@classmethod
def get_ball_torus(cls, rolling_radius, ball_radius):
return cadquery.Workplane("XY").union(
cadquery.CQ(cadquery.Solid.makeTorus(
rolling_radius, ball_radius, # radii
pnt=cadquery.Vector(0,0,0).wrapped,
dir=cadquery.Vector(0,0,1).wrapped,
angleDegrees1=0.,
angleDegrees2=360.,
))
)
def get_ring(self):
return cadquery.Workplane('XY', origin=(0, 0, -self.width / 2)) \
.circle(self.outer_radius).circle(self.inner_radius) \
.extrude(self.width)
def make(self):
ring = self.get_ring()
torus = self.get_ball_torus(self.rolling_radius, self.ball_radius)
return ring.cut(torus)
def make_simple(self):
return self.get_ring()
def get_mate_center(self, angle=0):
"""
Mate at ring's center rotated ``angle`` degrees.
:param angle: rotation around z-axis (unit: deg)
:type angle: :class:`float`
:return: mate in ring's center rotated about z-axis
:rtype: :class:`Mate <cqparts.constraint.Mate>`
"""
return Mate(self, CoordSystem.from_plane(
cadquery.Plane(
origin=(0, 0, self.width / 2),
xDir=(1, 0, 0),
normal=(0, 0, 1),
).rotated((0, 0, angle)) # rotate about z-axis
))
class _Ball(cqparts.Part):
radius = PositiveFloat(10, doc="radius of sphere")
def make(self):
return cadquery.Workplane('XY').sphere(self.radius)
class _BallRing(cqparts.Assembly):
rolling_radius = PositiveFloat(8, doc="radius at which the balls roll (default: half way between outer & inner radii)")
ball_diam = PositiveFloat(3, doc="diameter of ball bearings (default: distance between outer and inner radii / 2)")
ball_count = IntRange(3, None, 8, doc="number of ball bearings in ring")
angle = Float(0, doc="bearing's inner ring's rotation (unit: deg)")
@classmethod
def ball_name(cls, index):
return 'ball_%03i' % index
@classmethod
def get_max_ballcount(cls, ball_diam, rolling_radius, min_gap=0.):
"""
The maximum number of balls given ``rolling_radius`` and ``ball_diam``
:param min_gap: minimum gap between balls (measured along vector between
spherical centers)
:type min_gap: :class:`float`
:return: maximum ball count
:rtype: :class:`int`
"""
min_arc = asin(((ball_diam + min_gap) / 2) / rolling_radius) * 2
return int((2 * pi) / min_arc)
def make_components(self):
components = {}
for i in range(self.ball_count):
components[self.ball_name(i)] = _Ball(radius=self.ball_diam / 2)
return components
def make_constraints(self):
constraints = []
ball_angle = -radians(self.angle * 2)
rail_angle_delta = radians(self.angle / 2)
for i in range(self.ball_count):
# crude, innacruate calculation. justification: ball position is just illustrative
ball = self.components[self.ball_name(i)]
arc_angle = i * ((pi * 2) / self.ball_count)
rail_angle = arc_angle + rail_angle_delta
constraints.append(constraint.Fixed(
ball.mate_origin,
CoordSystem(
origin=(
self.rolling_radius * cos(rail_angle),
self.rolling_radius * sin(rail_angle),
0,
),
xDir=(cos(ball_angle), sin(ball_angle), 0),
normal=(0, 0, 1),
)
))
return constraints
@register(name='ballbearing')
class BallBearing(cqparts.Assembly):
"""
Ball bearing
.. image:: /_static/img/bearings/ball-bearing.png
"""
# Inner & Outer Rings
outer_diam = PositiveFloat(30, doc="outer diameter")
outer_width = PositiveFloat(None, doc="outer ring's thickness (default maximum / 3)")
inner_diam = PositiveFloat(10, doc="inner diameter")
inner_width = PositiveFloat(None, doc="inner ring's thickness (default maximum / 3)")
width = PositiveFloat(5, doc="bearing width")
# Rolling Elements
ball_diam = PositiveFloat(None, doc="diameter of ball bearings (default: distance between outer and inner radii / 2)")
rolling_radius = PositiveFloat(None, doc="radius at which the balls roll (default: half way between outer & inner radii)")
tolerance = PositiveFloat(0.001, doc="gap between rolling elements and their tracks")
ball_count = IntRange(3, None, None, doc="number of ball bearings")
ball_min_gap = PositiveFloat(None, doc="minimum gap between balls (measured along vector between spherical centers) (default: ``ball_diam`` / 10)")
# Dynamic
angle = Float(0, doc="bearing's inner ring's rotation (unit: deg)")
def initialize_parameters(self):
if self.inner_diam >= self.outer_diam:
raise ValueError("inner diameter exceeds outer: %r >= %r" % (
self.inner_diam, self.outer_diam
))
# --- Inner & Outer Rings
if self.outer_width is None:
self.outer_width = ((self.outer_diam - self.inner_diam) / 2) / 3
if self.inner_width is None:
self.inner_width = ((self.outer_diam - self.inner_diam) / 2) / 3
# --- Rolling elements
if self.rolling_radius is None:
self.rolling_radius = (((self.outer_diam - self.inner_diam) / 2) + self.inner_diam) / 2
if self.ball_diam is None:
self.ball_diam = (self.outer_diam - self.inner_diam) / 4
if (self.rolling_radius + (self.ball_diam / 2)) > (self.outer_diam / 2):
raise ValueError("rolling elements will protrude through outer ring")
elif (self.rolling_radius - (self.ball_diam / 2)) < (self.inner_diam / 2):
raise ValueError("rolling elements will protrude through inner ring")
if self.ball_min_gap is None:
self.ball_min_gap = self.ball_diam / 10
if self.ball_count is None:
self.ball_count = _BallRing.get_max_ballcount(
ball_diam=self.ball_diam,
rolling_radius=self.rolling_radius,
min_gap=self.ball_min_gap,
)
else:
max_ballcount = _BallRing.get_max_ballcount(
ball_diam=self.ball_diam,
rolling_radius=self.rolling_radius,
)
if self.ball_count > max_ballcount:
raise ValueError("%r balls cannot fit in bearing" % self.ball_count)
super(BallBearing, self).initialize_parameters()
def make_components(self):
return {
'outer_ring': _Ring(
outer_radius=self.outer_diam / 2,
inner_radius=(self.outer_diam / 2) - self.outer_width,
width=self.width,
ball_radius=(self.ball_diam / 2) + self.tolerance,
rolling_radius=self.rolling_radius,
),
'inner_ring': _Ring(
outer_radius=(self.inner_diam / 2) + self.inner_width,
inner_radius=self.inner_diam / 2,
width=self.width,
ball_radius=(self.ball_diam / 2) + self.tolerance,
rolling_radius=self.rolling_radius,
),
'rolling_elements': _BallRing(
rolling_radius=self.rolling_radius,
ball_diam=self.ball_diam,
ball_count=self.ball_count,
angle=self.angle,
),
}
def make_constraints(self):
outer = self.components['outer_ring']
inner = self.components['inner_ring']
ring = self.components['rolling_elements']
constraints = [
constraint.Fixed(outer.mate_origin),
constraint.Coincident(
inner.get_mate_center(angle=0),
outer.get_mate_center(angle=self.angle)
),
constraint.Coincident(ring.mate_origin, outer.mate_origin),
]
# rolling elements
# FIXME: use a more sensible constraint when available (see issue #30)
return constraints
def get_cutter(self):
cutter = cadquery.Workplane('XY', origin=(0, 0, -self.width / 2)) \
.circle(self.outer_diam / 2).extrude(self.width)
if self.ball_diam > self.width:
cutter = cutter.union(_Ring.get_ball_torus(self.rolling_radius, self.ball_diam / 2))
return cutter
@property
def mate_axis_start(self):
return Mate(self, CoordSystem(origin=(0, 0, -self.width / 2)))
@property
def mate_axis_center(self):
return self.mate_origin
@property
def mate_axis_end(self):
return Mate(self, CoordSystem(origin=(0, 0, self.width / 2)))