cycloidgen v7.6.0
Numbers — none move. Every existing design analyses exactly as it did:
motor_kind defaults to none, nothing about the motor feeds back into the
geometry, and tests/test_motor.py holds that promise by comparing whole report
sections between a design with a motor stated and the same design without one.
What is new is a set of numbers that were not there before.
Added
-
The motor is a curve now, not a mounting face. The app has always known
which NEMA frame a drive bolts to and nothing whatever about what comes out of
the shaft — so the duty point was taken on trust: an output torque at a speed,
worked back through the ratio and the efficiency to an input torque nobody
then asked about. That is a complete answer to is the gearbox strong enough
and no answer at all to will this turn, which is the question a drive is
bought to settle. Set a torque curve under Motor in the panel and the
analysis puts the duty point on it: what the motor has there, what is being
asked of it, and the margin between.Two models, both closed form, both stated with their limits in
core/motor.py.
A stepper's torque follows its phase current, and the current is what the
supply can force through the winding against its resistance, its inductive
reactance and its own back-EMF —V^2 = (K*w_m + I*R)^2 + (w_e*L*I)^2, solved
forIand capped at the rated current. A DC or brushless motor gets the
straight line betweenKt*V/Rat stall andKv*Vat no load, with the
continuous rating carried as a separate, much lower line.Eight numbers off a datasheet, and three of them are shared between the two
kinds because the sharing is physical rather than a squeeze: both motors run
off the same bus, both datasheets print a winding resistance, and both have a
current they will hold all day. What is not shared is what that current
means — a stepper is current limited everywhere, so its curve is already its
continuous curve, and a brushless motor's is drawn at stall current with a
continuous line two orders of magnitude below it. Sizing a gearbox on the
first is the classic way to build one that survives the bench and not the
robot, so the margin reported here is on the continuous line and the peak is
carried alongside rather than instead.The identity that makes this cheap is worth stating: torque per amp and volts
per radian per second are the same constant, so a stepper's electrical
ceiling falls out of its mechanical rating with nothing else needed —
n_ceiling = 60*V/(2*pi*K), whereK = T_hold/(sqrt(2)*I_rated)because
holding torque is measured with both phases energised. On 24 V a 0.4 Nm,
1.7 A motor makes no torque at all past about 1380 rpm; on 12 V the same motor
stops at 690. Halving the supply halves the top speed, and the supply is
usually the cheapest thing in the design to change.It also says something people do not expect: on a fixed bus and a fixed
current, more holding torque buys less top speed. More torque per amp is
more volts per rad/s, so the bigger motor meets the supply sooner. Buying
torque without buying current or volts moves the ceiling down. -
Three checks and a reading.
MOTOR_OPERATING_POINTsays where on the
curve this duty point sits — how much of the standstill torque is left here
and how close to the ceiling it is, which is the number that catches a drive
designed on holding torque.MOTOR_TORQUE_SHORTis the test — thin
margin, a duty that is a burst rather than a rating, or a motor that will not
turn the drive at all. A warning in every case, including the last: an error
in this app means the files are wrong, and these files are right. What is
wrong is which motor goes on the end, which is fixed by buying a different one
as readily as by redrawing anything — the same argumentMOTOR_SHAFT_MISMATCH
has always been a warning on. Blocking the export would also have made the app
refuse to hand over a gearbox somebody is having machined so they can go and
find a motor for it.MOTOR_SUPPLY_VOLTAGEcatches the
one thing the winding resistance decides on its own: a bus that cannot push
the rated current through a stationary winding, which scales the whole curve
down from the datasheet's, standing still included. -
A MOTOR tab, the curve with the duty point on it and the crossing where
the motor runs out labelled, plus seven rows on the datasheet and a section in
the PDF. Two of those rows are the ones to design against: the output torque
this motor buys through this reduction at this efficiency, and the output
speed past which it cannot make the required torque. On most designs here
the first is smaller than the gearbox's own capacity — so the motor, not the
contact stress, is what the drive is worth, and that comparison is now on one
page. -
The reduction can come from the motor. In the search dialog, work it out
from the motor: state what the output has to do and the reduction stops
being part of the question.design.ratio_bandasks the curve which whole
reductions can drive that load — closed form, one curve evaluation each, no
geometry needed — and the search then works across a spread of them and ranks
the results together. A reduction is a means; the job is a torque at a speed.The band's two ends fail for different reasons and it says which. Below it the
motor is short of torque and gearing down further helps. Above it the motor is
short of speed, and gearing down further is what caused it — so the answer
there is bus voltage or a different motor, and a design that fails both ends
at once is a motor that cannot do the job at any reduction. Told apart by
asking whether the next reduction up improves the margin, rather than by
whether the speed ceiling has been reached: past the peak the motor is often
still making torque, just less than the speed has taken away, and calling that
a torque shortage sends somebody to gear down when gearing down is what did
it.What the search leaves out, it says: a feasible band is usually dozens wide
and each one costs a full search, so five are sampled and the rest are
reported in the rejection tally. A bounded search that looks exhaustive is how
somebody concludes a reduction does not work when it was never tried.On the command line it is
--ratio-from-motor --out-rpm 10, and the curve
comes off the design passed with--designrather than off eight more flags —
the app already has a place to put those eight numbers, and a second way to
state them would be a second thing to keep in step. -
motor_marginjoins the batch metrics, so a study can sweep bus voltage
or reduction against what the motor can hold.nanwhere no curve was stated
— the same rule the fatigue margin follows on a printed part, because a study
that sweeps a motor across a design that has none should come back empty
rather than come back passing.
Changed
-
GearSpec.motoris nowGearSpec.motor_face. It returns the mounting
face, and an analysis result now carries the motor's own answer undermotor;
leaving both called the same thing would have put two different objects behind
one word on the two most-read classes in the app.MotorFrameand
MOTOR_FRAMESmoved to the newcore/motor.pybeside the curve and are
re-exported fromcore.spec, so every existing import still resolves. -
The panel has a Motor group. The frame and motor turns the cam used to
be filed under Mounting with the tie bolts, because a bolt pattern is what
they did to the geometry. That put the two halves of "which motor" in
different boxes the moment there was a second half. Fields the chosen kind
does not use are greyed rather than hidden — the search box already owns
visibility in that panel, and a field that vanishes takes the reason it does
not apply with it.
Fixed
-
A headless run wrote its files and then died on the way out, on Windows.
import cadquerypulls casadi in whether anything uses it or not — the
PyInstaller build already stubs it out of the frozen bundle for exactly that
reason — and with casadi 3.8.0 the two native libraries corrupt the heap while
they unload. The process does its work, prints its results, and exits
0xC0000374. Nothing here calls casadi, so nothing here could avoid it;
casadi<3.8is pinned on Windows until the unload order is fixed upstream.It matters more than a red tick on a dashboard: every scripted run reported
failure having succeeded, which is the worst way for a tool to be wrong.Measured rather than suspected, and it took the measurement to get there. The
suite ended1048 passed, 1 skippedand the step then said "exit code 1" with
nothing else in the log, which reads as a test failure that has hidden itself.
Four experiments said what it was not: the same failure onv7.5.0, code that
had been green a fortnight earlier; the same failure with PySide6 pinned back
to the version that was green; the same failure on every one of sixteen test
modules run in its own process, and none on the other twenty-three. What the
sixteen had in common was the CAD kernel. Then two lines were enough:
python -c "import cadquery"crashes,python -c "import casadi"does not,
andimport cadqueryon casadi 3.7.2 is clean. -
The test step now reports the exit code it was given. The default shell on
Windows is pwsh, which GitHub runs with$ErrorActionPreference = 'stop', and
since PowerShell 7.4 that turns any native command failure into a terminating
error and a flat1. The real code was-1073741819. A step that cannot tell
you which of those it saw is a step that cannot be debugged, and this one was
hiding the only fact that mattered. -
The Qt test modules destroy their windows.
test_workspace.pybuilt
sixty-six top-level windows and took down none of them,test_view3dnine,
test_noticeone — each holding a 3D view, matplotlib canvases and a worker
thread, all left for the interpreter to take apart at exit in whatever order
Python's collector reached them. Two other test modules here have always used
deleteLater; these are the ones that never did. They are hidden and deleted
after each test now — hidden rather than closed, becausecloseEventwrites
the workspace to the preferences and a teardown that silently saved state
would hand the next test a window restored from one it never asked about.It was not the crash above, and it was worth doing anyway: the suite went from
32:49 to 15:35 on the Windows runner, and from 17:53 to 8:42 locally. Sixty-six
live windows were not only a bad way to end a process, they were a tax on every
test that ran after them. -
The drawing's two caption lines used to run across the gearbox. They sat
in the bottom-left corner of the axes, on the argument that the housing
circle leaves that corner empty. It does, and neither line is short enough to
stay in a corner:set_aspect("equal")makes the axes a square in the middle
of a wide panel, the circle is inscribed in it, and a full line of monospace
starting at the left edge runs straight under the circle and out the other
side — across the disc on a 1560 px window and across the whole gearbox on a
1180 px one. A corner is a place for a word, not for a sentence.Both lines are figure text now, in a strip
tight_layoutis told to keep off,
and the strip is computed from the point size and the panel height rather than
written down as a fraction — the same reason the layout is already re-solved
on resize. Reserving the room rather than nudging the text is what makes it
hold on all three canvases this one figure is drawn on: the app's letterbox
panel, the PDF's square and the animation's small frame. Tested at four
shapes, and what is asserted is that the drawing stops above the strip
rather than where the text happens to be. -
The 3D tab opened with the gearbox a third of the height of its own
viewport. VTK'sResetCamerafits a sphere around the bounding box, and
a cycloidal drive is a flat cylinder — so the diagonal it is sized by is most
of a diameter longer than anything on screen, and the camera ended up that
much too far back. The software renderer had never had this problem: it
projects the vertices themselves onto the screen axes and asks how far back it
has to be to hold those, which is a millisecond once per design. The GPU
view uses the same call now, with the same field of view, so a design looks
the same size whether or not the machine has a usable GPU — and the view opens
on a gearbox instead of on a small object in an empty room. -
The empty COMPARE tab is a panel that explains itself. The invitation to
pin a reference was one sentence in the top-left corner with four hundred
pixels of nothing under it, because one label was being asked to be both that
invitation and the header over the filled table. They are two messages with
two jobs; they are two labels now, and the empty one sits in the middle of the
panel it is about to fill — which is what the trade study next door has always
done, and it matters more here, because pinning a reference is the one feature
of this window that nothing else in it mentions. -
The remembered tab is stored by name now, not by position. A tab added in
the middle renumbers every one after it, so a stored4reopened the session
on somebody else's tab — which is exactly what putting MOTOR between
EFFICIENCY and DATASHEET would have done to everybody who had the app open on
the datasheet. The name survives being reordered and says what it means in a
preferences file somebody may one day have to read. Preferences written by an
earlier version still hold an integer and it is still read, so an upgrade does
not lose the tab it was left on. The log tab's unread badge is stripped before
the name is stored, because a session saved underLOG !!would never be
found again by a window whose log is quiet. -
The motor curve was being sampled at the wrong speed — caught by its own
test before it shipped, and worth recording because the mistake is a
reasonable one.GearSpec.crank_rateis crank angle per input revolution in
the ring-fixed frame the kinematics are parameterised in, which is how every
relative speed inside the drive is stated and is not a shaft speed at all. It
is not 1 on a ring-output drive, so multiplying by it read the curve 4.5% off
on a 21:1 and further out the lower the ratio. The motor turns the input shaft
at the input speed, whichever member is grounded.
Windows - cycloidgen_..._Setup.exe. It is unsigned, so
SmartScreen will warn on first run: More info -> Run anyway. A
signed build is planned.
Linux - cycloidgen-...-x86_64.AppImage. chmod +x and run it;
it needs glibc 2.35 or newer. If it will not start, the machine has no
FUSE 2: install libfuse2, or run the file with
--appimage-extract-and-run, which unpacks it instead of mounting it.
macOS - cycloidgen-...-arm64.dmg, Apple silicon. Drag it to
Applications, then run this before opening it:
xattr -dr com.apple.quarantine /Applications/cycloidgen.app
It is signed ad-hoc but not notarized, so Gatekeeper blocks the first
launch. On macOS 15 and later the dialog it raises defaults to
Move to Trash, which deletes the install - there is no Open
Anyway in that dialog, it appears in System Settings only after a
launch has been blocked, and right-click -> Open no longer bypasses
it. Clearing the flag first avoids the dialog entirely. On an Intel
Mac, use pip.
Anywhere with Python 3.10-3.14: pip install cycloidgen, then
cycloidgen.
The numbers this produces are preliminary sizing estimates, not a
certification. Validate against a physical prototype before anything
load-bearing depends on them.