Releases: medinstech/cycloidgen
Release list
cycloidgen v7.8.0
Numbers — none move. Nothing here touches the geometry, the analysis or what
an export contains. A design opened in 7.8.0 analyses exactly as it did in
7.7.0; what is new is that the application can now tell you when it is no longer
the version those numbers should be coming from.
Added
-
It can say when it is out of date, if you say it may. An engineering tool
is installed once and used for months, and this one has had no way to mention
that it had fallen behind. That is not a missing convenience. The models
change between releases — the changelog above is mostly a record of numbers
moving — so a design sized against a build from two versions ago can disagree
with the current one about the same drive, and the person reading the old
answer has no reason to suspect it. There was nothing in the application that
could tell them.Help ▸ Check for updates… asks GitHub which release is the newest and says
so either way. Help ▸ Check for updates automatically does the same once a
day, in the background, and stays quiet unless there is something to say.Nothing is sent before you are asked. The first run puts the question up
once, in plain words, and until it is answered no request is made by any path.
What a check sends is a request for a version number: nothing about your
designs, your machine or your files, and there is nothing in the application
that collects any of that in the first place.A "no" is remembered, and there are three different ones. Not now asks again
tomorrow. Skip this version means that particular release is not wanted and
nothing more is said about it until a newer one appears — asking from the menu
overrides it, because that is the user putting the question again. Turning the
setting off stops it entirely. A prompt that cannot be dismissed for good is a
prompt people learn to click through, and then the one that matters goes the
same way.The dialog carries the release notes rather than only the version number. "Is
7.8.1 worth installing in the middle of a job" is a question about what
changed, and sending the reader to a browser to find out is how it gets
postponed indefinitely. -
On Windows, it can do the install. Choosing Download and install fetches
the installer with a progress bar you can cancel, checks it against the length
GitHub published for it, closes the application and starts it. Windows asks for
permission first — the installer needs it, and that decision stays with the
person in front of the machine. Preferences, recent files and the restored
session survive it; the installer clears the old bundle rather than copying
over it, which is why the application closes first.The build is still unsigned, and the dialog says so rather than implying a
verification that has not happened. SmartScreen warns exactly as it does for an
installer fetched in a browser, and the download is checked for being whole,
not for being ours. A signed build is still planned. -
What it offers depends on how this copy was installed, because offering the
wrong route is worse than offering none. A pip install is given the upgrade
command with the interpreter that is actually running named in it — a machine
with several Pythons is exactly where a barepip install -Uupgrades a copy
that is not the one in front of you. An AppImage or a disk image is a file you
put somewhere yourself and is not ours to replace, so those are sent to the
release page. Only the Windows installer, and only on the architecture the
release actually carries a bundle for, is offered as an install.
Changed
-
certifiis a dependency now.ssl.create_default_context()reads the
platform certificate store, which is the right answer on Windows and on a
normal Linux install and the wrong one inside a frozen bundle on macOS: OpenSSL
looks in the paths it was built against, they belong to the CI runner, and a
perfectly good connection fails verification. It is imported optionally, so a
copy installed without it falls back to the platform store rather than
failing — and nothing anywhere disables verification. -
CYCLOIDGEN_NO_UPDATE_CHECKturns the whole thing off, by every path including
the menu, which stays visible and greyed out saying why. It exists for the same
reasonCYCLOIDGEN_SETTINGSdoes: whoever ships a copy may have a better
answer than we do about how it gets updated, and a distribution whose users
update through their package manager should not be second-guessed by the
application.
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.
cycloidgen v7.7.0
Numbers — none move. A design with no duty cycle analyses exactly as it did:
the field defaults to empty, nothing about a cycle feeds back into the geometry
or the rated point, and tests/test_duty.py holds that by comparing whole report
sections between a design with a cycle and the same design without one. What is
new is a set of numbers a single rated point could not produce.
Added
-
A duty cycle, because a machine is not one point. The app has always taken
the duty as a torque out at a speed in, which is the right way to size a
gearbox and the wrong way to describe a machine. A robot joint lifts, holds,
returns and waits; a winch pulls hard and slowly and spools back fast and
empty. A single point can be the worst of those or the average of them, and
the app has been asking which one you meant without saying that it was asking.Design ▸ Duty cycle (
Ctrl+D) takes a table of them: what it does, the
torque, the speed at the output, and how long it lasts. The durations are in
whatever unit suits — only the ratios are read — so a cycle can be typed the
way it was measured rather than converted into shares that have to add up to
one.The point of it is that the quantities do not aggregate the same way, and
no single point is conservative for all of them at once:- Stress takes the worst point. Averaging the hardest moment away is how a
drive passes on paper and cracks on a bench. - Temperature takes the mean loss. A housing integrates, and sizing the
cooling to the peak is sizing it to a transient. - Bearing life takes neither. Life goes as the cube of load, so a varying
load is carried at ISO 281's equivalent load — the cubic mean, which sits
well above the arithmetic mean and well below the peak. - The motor takes both ends: it has to make the peak and survive the RMS.
Four aggregations from one cycle. Getting any of them by picking a
representative point is a coincidence rather than a method. - Stress takes the worst point. Averaging the hardest moment away is how a
-
Holding still is a point, not an error. Zero output speed is most of what
some drives do: the torque is there so the contact loads are, nothing slides
so there is no PV and no friction loss, nothing turns so no bearing life is
consumed. It falls out of the arithmetic rather than being special-cased —
the cubic mean weighs revolutions, so a point at half the speed contributes
half the wear and a point at no speed contributes none, and the equivalent
speed is averaged over the whole cycle including the standstill. That last
one is what makes the life a number of hours of cycle rather than hours of
rotation, which is the unit a service interval is actually written in. -
DUTY_RATING_MISMATCH, and it is the check that makes the rest safe to
read. Everything on the datasheet — capacity, safety factor, wind-up,
transmission error, fatigue — is computed at the rated torque. State a cycle
that goes above it and every one of those numbers is describing an easier
machine than the one you have just described. The drive is not wrong; the page
in front of you is. Beside it,DUTY_MOTOR_SHORTnames the hardest moment —
which is not always the heaviest point, because torque falls with speed and a
light point at speed can be tighter than a heavy one standing still — and
DUTY_BEARING_LIFEreports the drive's own bearings over the cycle.That last one is the one that had to be built twice. The first version
re-selected bearings for the cycle, which answers "what would I fit for this
duty" and can therefore never report a bearing that falls short: it would
simply have picked a bigger one. The question worth asking is whether the
parts the drive has survive what it will actually do, so the rated
schedule's parts are kept and only their lives are recomputed — at one scalar
equivalent torque and one equivalent speed, which is exact here because every
load in this machine is linear in output torque and every speed is linear in
input speed. Doing it that way also means nothing has to match a role by its
name to know what a number means, which is how the bearing quantities went
wrong once before. -
Where it shows up. A section on the datasheet and in the PDF, a
duty
block in the JSON with every point and every aggregate, and a shape that
matches the motor block: the key is always there so a consumer can tell "no
cycle stated" from "an older version that could not be asked", with nulls
rather than zeros so nothing reads a missing answer as a good one.
Changed
analysis.bearings.life_hoursis public. It is a fact about a part rather
than a step in selecting one, and the duty cycle asks it of parts that have
already been chosen.
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.
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 `-1073741...
cycloidgen v7.5.0
Numbers — none.
Changed
-
The application icon is the disc it cuts, not the company logo. Every
window, task bar button, Start menu entry, installer page, Dock tile and
desktop launcher showed the Medinstech mark — which says who wrote the
program and nothing whatever about what it does. Somebody scanning a task bar
full of windows is looking for this application, and a vendor logo is the
one thing on it that is also on everything else the vendor ships.It is a cycloidal disc now, and not a drawing of one:
tools/make_icon.py
renders the outline through
disc_profile(),
the same call the STEP export cuts the part with, with the pin radius taken
as a fraction ofcritical_radius()so the lobes are a disc that could
actually be manufactured rather than a flower. The icon cannot drift from the
geometry: change the profile and it changes with it.tests/test_icon.py
measures the committed images back against the equation — every point of the
outline sits at exactly the pin radius from the pin-centre locus — and against
what the tool draws today, so a hand-edited PNG fails.Each size is drawn at that size. A 256 px disc with eight lobes and six
output holes resampled to 16 px is grey soup, so it is graded like an
optical size instead: fewer lobes, deeper, and the holes come out, until at
16 px it is six lobes and a bore. The.icocarries seven separately drawn
images and Windows picks between them; the macOS.icnsis built the same way
at package time and now fills every slot up to 1024, where before it stopped
at 256 and left Finder's largest view to blur one.The disc is cut out of a brand-blue tile, which is what lets one asset be
legible on a light task bar and a dark one — the bare mark was the brand
colour against whatever the desktop happened to be, and on a dark task bar
that is 2.8:1.The installer's two panels show it too, drawn at the exact 40 px and 96 px
those controls display rather than scaled down from the 256. The Medinstech
wordmark stays on the welcome band, where it says who publishes this; the
picture beside it is now the icon the shortcut will get, which is what
somebody halfway through a setup wizard is being told about.It is not a trademark.
cycloidgen.icoandicon-*.pngcarry no name or
mark of Medinstech, they are Apache-2.0 with the rest of the source, and a
fork may keep them — see NOTICE. The brand assets beside
them are unchanged and still are not. The two generators are kept apart on
purpose:tools/make_assets.pyneeds masters that are not in this repository
and no longer writes the icon, so a brand refresh cannot quietly put the logo
back.
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.
cycloidgen v7.4.0
Numbers — none.
Added
-
The app says what its output is not, where the output is made. It said it
before, in Help ▸ About, which is a dialog nobody opens - the least-read place
in the application for the one paragraph in it that carries a consequence. It
is in three more places now, and they are the three that matter:- A strip under the export buttons, in the window, always. Not
dismissible: a disclaimer with a close button is a disclaimer that is shown
once. Quiet on purpose - a hairline, the warning ink, one line - because it
has to still be legible on the hundredth session and a banner that shouts is
read as decoration by the second. - A box before an export is written, every time, with no "do not show
again". Before rather than after, because after is a notification and this
is a decision: the files are the thing that leaves the app, gets emailed to
a shop and outlives the session that made them. NOTICE.txt, in the folder with the parts — and in the PDF dossier, on
the first page, above the verdict. That last position is deliberate: the
verdict says READY TO EXPORT in capitals, which is a statement about the
checks and reads, on its own, like a statement about the design.
The notice is written into every bundle whichever groups were selected,
because somebody exporting drawings only is exactly the person taking a DXF
straight to a laser cutter. It is the first entry in the export manifest and
the first row in the README's table of outputs, and it is not a group anyone
can untick. Asking for no groups still writes nothing at all.All five copies read
cycloidgen.notice. The risk with a disclaimer in five
places is not that one disappears - somebody would notice - but that one
softens, and the weakest copy is the one that will be quoted back. A test
holds them to the same string.What it says is also wider than what About used to say. It named the numbers;
it names the geometry too. Profiles, fits and clearances come out of the same
idealised model as the analysis, no tolerance stack has been proven, and a
STEP file that looks finished is the easiest thing here to mistake for a
drawing that has been checked. - A strip under the export buttons, in the window, always. Not
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.
cycloidgen v7.3.2
Numbers — none.
Fixed
-
The housing bore was drawn with two of every junction point, and on VTK
9.3 that was still a hole in the housing for every ring pin. 7.3.1 replaced
the triangulation that could not be trusted across builds, and every face it
cuts is checked - but the mesh was handing it a loop with edges of no length
in it: the bore arc and the pocket arc meet at the intersection, and each
computed that point for itself, agreeing to fourteen decimal places and not to
the fifteenth. Forty-four of them on a 21:1.Geometrically they cost nothing, which is why they went unnoticed for so long.
What they cost is edges. A segment with no length has no direction, so the
wall standing on it is a quad with no area, and whether any of that survives
being merged back into a closed surface is up to the mesh library: VTK 9.6
keeps the two points apart, VTK 9.3 does not, and the one that does not leaves
the wall and the cap disagreeing about where the boundary is. Two points that
are the same point are one point now.
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.
cycloidgen v7.3.1
Numbers — none. Nothing computed moves in this release: it is the 3D view's
geometry and the interpreters the package will install on.
Fixed
-
Parts in the 3D view were not watertight on macOS or on VTK 9.3, so a
section cut through them read as a hollow shell rather than as solid
material — the exact fault 7.2.0 said it had fixed, on the two platforms it
had not been run on.The cause is worth stating plainly, because it is a lesson rather than a
slip. Faces with holes in them were filled byvtkContourTriangulator, which
gives up part way on some inputs and reports nothing about it. 7.2.0 added a
check for that and a list of angles to turn the face by and try again — a
list searched, on one machine, for the smallest set that cleared every face
this app can draw. But which inputs defeat that filter is a property of the
build, not of the geometry: the same list left the housing full of holes on
VTK 9.3 (which is what Python 3.10 gets) and the end cap full of holes on
macOS arm64 at the very version it was developed on. Neither reproduces here.So the face is cut here now, in
viz/tessellate.py, by the textbook method
for a polygon with holes: a sweep adds the diagonals that leave nothing but
monotone pieces, the pieces are traced out of the loops and those diagonals
together, and each is triangulated by the stack walk. Every step is
comparisons and cross products in a fixed order, so two machines get the same
triangles — and the suite checks all of it directly: the whole area, no
directed edge twice, and a boundary exactly as long as the loops.It is also faster, and by more than the algorithm: the triangles come back
as indices into the mesh's own vertices, so a cap shares its corners with the
walls that meet it and there is nothing to append and merge afterwards, and
the cutting is remembered per mesh rather than repeated for each of the two
surfaces every part needs. A full rebuild of the 21:1 view went from 0.18 s to
0.11 s, and rebuilding a mesh already seen from 0.18 s to 0.02 s.Two things came out of writing it. Nothing in the suite had asked that a
face's triangles all be wound the same way — and the area cannot see it, so
a face covered twice over and once backwards passes on the sum. And the
housing's bore is drawn as a bore arc and a pocket arc per pin, meeting at a
point each of them computes for itself: the same point to fourteen decimal
places, which is an edge of no length to a sweep and cannot simply be dropped,
because the wall below the cap keeps that edge and a boundary only one of them
keeps is a hole.
Changed
- Python 3.13 and 3.14.
requires-pythonsaid<3.13on a tree whose whole
suite passes on 3.14, sopip installrefused an interpreter the code was
fine on. The bound is<3.15now. An upper bound is still stated rather than
dropped — this depends on OCCT, VTK and Qt through binary wheels, and an
interpreter no wheel exists for is a failed install however permissive the
metadata is — and it moved to where there is a run behind it: 3.13 and 3.14 on
Linux, and 3.14 on Windows as well, because those wheels are built per
interpreter and per platform. 3.12 stays the version every bundle is frozen
on.
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.
cycloidgen v7.3.0
Numbers
-
The disc turns against the crank, and the app had them subtracting. Every
speed measured between the disc and the crank was computed as
input_rpm * (1 - 1/i). It is1 + 1/i: the disc rotates the opposite way
from the crank — that is what a fixed-ring cycloidal drive is — so the two
rates add rather than cancel.Three places carried it. The eccentric cam bearing, which separates those
two bodies and so turns at their difference; the output pin rubbing speed,
which is the disc walking round the carrier at that same rate; and the
output stage's sweep period, derived from how fast the eccentricity
direction runs round as seen from the carrier.What gave it away was the pin-in-hole constraint. Relative to the carrier the
disc translates on a circle of radiusE, so at every crank angle each output
pin has to sit exactlyEfrom its hole centre — and it does, only with the
carrier turning at+phi/N. Put that rate in and the eccentricity direction
seen from the carrier advances at(N+1)/N, not(N-1)/N. The two stage
periods then agree for the first time: the output period comes out as the ring
period divided by the output pin count, which is what a pattern ofnrepeats
in one turn of the ring pattern has to be. They were derived independently and
only the correct rate makes them meet.Measured over the presets:
Quantity 10:1 21:1 59:1 Cam bearing speed, output PV, output sliding speed +22.2% +10.0% +3.5% Cam bearing L10 life −11% −9.1% −3.3% Input shaft support speed +10.0% +4.8% +1.7% Running temperature +4.2% +1.7% +0.3% Input torque for the rated output +1.7% +1.0% +0.4% Efficiency −1.2 pt −0.7 pt −0.3 pt Torsional stiffness +2.6% +4.5% +5.6% It is worst where it matters most — a low ratio is where that bearing is
fastest — and everything on the ring side is unchanged, which is the expected
result rather than a reassuring one: nothing about the ring contact was ever
measured against the crank. -
The two input shaft supports do not turn at the same speed, and both were
quoted at the input speed. One sits in an end plate and one in the carrier's
boss, and those two bodies move differently. The schedule still carries one
row — the seats are the same size, so one part number does for both — but it
is sized on the faster of the two and prints both. -
The output pin rollers were sized against the input speed, which is
neither the speed of anything at that contact nor the frequency of anything.
They are now counted on the rate the hole walks round the pin, which is the
cycle they are actually loaded on.
Which member is the output
-
Either of the two slow members can now be the output. A cycloidal drive is
a three-shaft machine: the crank is the input, and the ring and the carrier are
interchangeable. Ground the ring and the carrier turns atN:1, reversed —
which is what the app has always built. Ground the carrier and the housing
turns, atN+1:1, in the same direction as the input. Same parts, one more
tooth of reduction, and it is what most printed micro drives are.output_memberselects it, and everything downstream follows rather than
being told twice:- The reduction and the direction come off the two members' rotation rates, so
ratiocannot disagree with the picture drawn from the same numbers. - Every rate in the app is now stated per unit input speed rather than per
unit crank angle, because those part company here: with the carrier
grounded the crank runs at(N+1)/Nof the input. Relative speeds inside
the drive are unchanged by the choice, which they must be — grounding a
member adds one rigid rotation to all of it at once. - The motor moves to whichever member stands still. On a ring-output drive
the carrier grows a base at the end of its boss, carrying the motor's
pattern and register; the input end plate loses them and gains an output
bolt circle for the driven machine instead, on the tie bolts' own circle
and half a pitch round from them. - The 3D view, the 2D mechanism view and the exported animation turn the part
that actually turns. One rigid frame rotation applied to the whole assembly,
not a second set of motion laws to keep in step with the first. - The design search knows the difference between a reduction and a lobe count:
a 30:1 off the ring is a twenty-nine lobe disc.
- The reduction and the direction come off the two members' rotation rates, so
-
A ring-output drive is a frame, not a plate. The grounded member cannot be
a carrier hanging on six cantilevered pins with a barrel swinging off one
bearing, and the reference builds are not: the pins land in an end cap at
their far end and become the frame's own fasteners, so the carrier, the pins
and the cap are one rigid cage that the housing turns inside. That is a new
made part,end_cap, with its own STEP, STL and line on the bill of
materials, and it changes two numbers that matter.The output pins are beams. A pin built into one plate carries
F*L; the
same pin caught at both ends carriesF*a*b/L. On the 21:1 preset with two
discs that is 45.3 MPa of fully reversed bending against 19.9 MPa. Their
stiffness moves the other way and the app says so - the span is now the
whole pin rather than half a stack, so the structure comes out slightly softer
(7.26 to 6.57 Nm/arcmin) even though the case is stiffer.The housing is carried at both ends. One bearing locates a barrel and does
not hold one against a moment, which is exactly what a wheel or a pulley on a
turning barrel applies. There is a main output bearing on each of the frame's
two bosses now, and both shaft supports move inside those bosses - the one
that used to sit in the input end plate had to, because that plate turns.It costs length: the barrel has to cover the cap, so the 21:1 preset goes from
40 mm to 63 mm and from 781 g to 1033 g. -
The output pin's bending arm starts at the carrier's face, which is a
carrier drop below the first disc, and it was being measured from the disc.
Small - one millimetre on a moment arm - but it is a moment arm, and it pushed
a 16 mm disc stack on the 15:1 preset from just inside the output pin's
fatigue limit to just outside it.FATIGUE_LIFEsays so now. Carrier-output
drives are affected, which is all of the ones that existed before this
release. -
The carrier's base was drawn one millimetre off the boss it stands on.
Introduced with the base itself, earlier in this release, and never shipped:
the part is modelled in its own frame and an assembled height was used in it
without the carrier drop, so the exported solid was in two pieces. The volume
was right, which is why nothing caught it - a printer would have made both. -
OUTPUT_BOLT_CLASH, a new check. The output face's bolts share a circle
with the tie bolts, because that is the one radius on that plate with barrel
wall behind it to thread into. Equal counts interleave exactly; seven against
six leaves 0.12 mm of metal; twelve against six lands one hole on another.
Fifty-six checks.
Ring pins the housing is printed with
-
The ring pins can be formed with the housing instead of fitted into it as
separate dowels —ring_pins_integral, and the case every printed drive is.
A pocket and the pin that fills it are one shape read from either side, so
almost the whole of the difference is which arc of the same circle the bore
follows, the outward half or the inward one, andcutagainstunionin the
exporter.The pins stop being a part when they are integral: no body in the 3D view, no
STL, no line on the bill of materials — twelve lines to eleven, six bought
parts to five — and no visibility row for something you can neither see
separately nor take out. Their mass moves into the barrel and into the
housing's material, which is the point of the option: on the 21:1 preset the
drive goes from 781 g to 623 g, because 188 g of steel dowels become 30 g of
the printed material that was going to be hollowed out to seat them. -
They also stop being able to roll, and that is not free. An integral pin
cannot turn in a pocket it is part of, so the drive that pays for this is the
one that had rolling ring pins. Same 21:1, rollers on:Quantity Rolling dowels Formed with the housing Efficiency 83.7% 70.4% Ring pin loss 1.00 W 6.63 W Running temperature 29.5 °C 40.5 °C Ring pin sliding duty rolls 2.2x the PV limit PV_LIMIT_RINGfires there rather than the trade being made quietly: PLA on
steel at 0.22 MPa and 0.30 m/s is a disc that wears round long before it
breaks. The ring pin roller leaves the bearing schedule with it, five rows to
four, which is the other half of the same fact — there is no longer a part
free to turn for a needle to sit under. -
ring_pins_rollis what every consumer asks now, and it is derived rather
than enforced. The obvious way is a validator that clears
ring_pins_are_rollerswhen the pins are integral, and it does not work:
model_copyruns none, so a spec that arrived that way kept rolling pins on
an integral ring and its efficiency came back unchanged — which is how this
was caught. Deriving it also keeps the roller preference for when the pins
stop being integral, which is why the box is greyed rather than cleared. The
field is inmesh_fingerprintfor the same class of reason: it changes the
mesh, and a key that did not carry it would have served the old one.
Added
- 3MF export — ...
cycloidgen v7.2.0
Numbers
-
Every sweep in the app was running over a window that is not a period, and
the numbers taken over it have moved. A lobe pitch,360/N, is the period
of the disc's shape. It is not the period of anything a sweep samples: a
sweep samples the pins, and there areN+1of those againstNlobes, which
is the entire mechanism. Pinkmeets the profile att_k = phi/N - 2*pi*k/(N+1), so the crank has to turn360*N/(N+1)before each contact
lands where its neighbour was — 330 degrees on the default drive, not 32.7.What gave it away is that the curve did not close. Peak pin force reads
49.859 N atphi = 0and 49.271 N one lobe pitch later, so the Ring pin
load plot was a tenth of a cycle cut at an arbitrary phase, ending somewhere
other than it began. Tiled, it steps at the seam. That is what it looked
like, and it is what it was.The ring stage now sweeps
360*N/(N+1)and the output stage sweeps its own
360*N/(n*(N-1)), which is a different number and always was — the two have a
common multiple that runs to thirty input revolutions on some tooth counts,
which is exactly why neither can be swept on the other's window. Each stage
gets its own loop. This is not an approximation: a maximum per stage is a
maximum per stage, and the mean of a sum is the sum of the means.Measured against 7.1.1, over the presets and several tooth counts:
Quantity Worst change Ring peaks — pin force, contact pressure, torque capacity under 0.01% Efficiency, running temperature 0.6% Mean sliding speed 0.7% Output pin force, output PV, disc web shear 5.5% Torsional stiffness 12.6% Load concentration 8.6% Transmission error 7.4% The ring-side peaks barely move, and that is the expected result rather than a
reassuring one: a maximum over the pins does not care which phase of the cycle
you start at, only whether you covered it. What moves is everything averaged
or ripple-measured, and the output stage, which was being swept over about
half its period. -
A check that should have been firing was silent. On the 29:1 preset the
output pin contact pressure exceeds the allowable, andHERTZ_STRESS_OUTPUT
now says so. It did not before, because the peak output pin force was sampled
over a lobe pitch — half the output stage's period — and came out 5.3% low,
which was the wrong side of the limit. Anyone who took that preset as passing
should re-run it. -
ring_period_degin the transmission-error result was the lobe pitch, on
a result type whose other field,output_period_deg, has been correct since
it was written. It now reports the ring period: 330 degrees rather than 32.7
on the default drive. -
Sweeps are 144 steps rather than 72. Sampling an exact period uniformly is
unbiased at almost any count, so this is not what fixed the window — it is
only about resolving the peak. Against a 20000-step reference, 72 steps miss
peak pin force by 0.11% at 30 lobes and 144 by 0.004%. -
The transmission error's ring sweep needed four times the steps, and this
one is about resolution. Twelve samples were enough across a lobe pitch and
are less than one per pitch across the ring period, which read 11% low on the
ring share; forty-eight is where it stops moving. What is left is bounded by
the shared sweep rather than by that number, and is stated in the source: the
ring share sits about 2% under a sweep four times finer, 0.7% on the total.
Closing it means quadrupling the sweep every other study reads, and a
transmission error 2% conservative on one of its two halves is not what limits
this model.
Fixed
-
No part in the 3D view was watertight, so the section could not cap them.
A cut came out with some parts reading as solid material and others as empty
shells — half the assembly sectioned, half of it hollow.The faces are emitted one at a time and each brings its own copy of every
corner, so no face shared an edge with its neighbour: geometrically solid,
topologically a heap of loose facets, every edge in it a boundary edge.
vtkClipClosedSurfacecaps a closed surface and could not cap any of the
fourteen.The comment on the filter that was supposed to prevent this said it merged
the duplicate points.vtkPolyDataNormalsdoes not merge points — with
SplittingOnit creates them, which is what gives a cylinder's end cap a
hard edge against its wall and is the right thing for shading. So the points
are merged first and split afterwards, and the two surfaces have different
jobs: the section and the edges take the closed one, the shading takes the
split one. Twelve of the fourteen parts are now watertight.The same duplication had been doubling the edge overlay, which found every
edge twice — once from each of the two faces that should have been sharing
it. 12,614 line segments where 6,253 do.Two parts are still not clean, for reasons of their own, and are not fixed
here:output_flangehas 28 non-manifold edges where the plate and the boss
both keep the face they meet on, anddisc_1has a four-edge hole in its top
face wherevtkContourTriangulatorgives up on one arrangement of output
holes and the code checks only that it produced some triangles.disc_2,
the same part on a different hole phase, is clean. -
The screenshot tool drove the operator's real preferences. It forced the
light theme, cleared the section plane and unhid the 3D groups, then put them
back at the end — and anything that raised in between skipped the putting
back, which is how a window starts opening in the wrong theme with no sign of
why. It runs against a throwaway settings file now, which is what
settings.ENV_VARexists for and is a better answer besides: these images are
meant to show a fresh install, and now they are taken on one. -
The output stage was swept over a lobe pitch in four more places —
analyse_contacts,analyse_efficiency, the thermal solve, and the disc-web
and output-pin fatigue checks.output_stage_periodhad been in the codebase
since the transmission-error work, with a docstring warning that sweeping a
lobe pitch "reports about half the ripple that is really there", and nothing
outside that one function called it. Both period functions live in
core.kinematicsnow, next to the sweeps that need them. -
Nothing in the suite asserted that a period was a period, which is why
this survived four releases and 788 tests. There are now tests that advance
each stage by its own period and require the load pattern to return — one pin
along for the ring stage, one the other way for the output stage, because the
pattern steps onto its neighbour rather than staying put. And a test that
holds the mistake down directly: a lobe pitch must not close either stage,
compared as multisets so that renumbering the pins cannot rescue it.
Changed
-
The drawing says what speed it is showing, and the playback control says
what "1x" means. These were one confusion. The animation runs at 3 degrees
of input per 33 ms frame, which is one input revolution every four seconds —
15.2 rpm — while the tooltip claimed "input revolutions per second of wall
clock", four times faster than the thing it described. The control is
labelled PLAYBACK now and carries a live readout of the rate it actually
turns at, so the multiplier is answerable without a tooltip; the rate is
derived from the two timing constants rather than written down beside them.
The drawing carries the design's speed, which is the other half of the
question: a picture turning visibly at fifteen rpm, describing a drive rated
at a thousand, needs to say which of the two it is. -
The drawing and the datasheet name the arrangement. Ring fixed, output
taken from the disc's pin holes through the carrier — the planetary
configuration, as against grounding the carrier and driving the ring, which
is the star configuration and givesNprather thanN. The reduction line
and the output-speed line both now say the output turns against the input,
which the geometry has always done and nothing ever mentioned:
output_rpmhas no sign to carry it. The README says which member is
grounded, why that fixes the ratio and the direction, and that the disc rolls
on the inside of the pin circle. -
The explanation panel answers two questions and sat beside one of them.
It explains the selected check and the parameter you clicked, and it lived
in the bottom-right corner — so clicking a parameter in the left-hand panel
put the reply as far from the question as the layout allowed. It is under the
parameters now, and the checks list has the full width it had been competing
for. That also retires the machinery that used to hide the panel on a narrow
window and hold a floor under the detail column: nothing competes for that
width any more. -
The 3D tab's controls are grouped by what they do. Explode was alone at
the top beside the view buttons while section was squeezed onto the end of the
visibility checkboxes at 150 px, on a row that had already run out of width
and wrapped. They are the same kind of control — drag to open the assembly up
— and they share the top row now, at the same size. The second row is
visibility and nothing else. No extra height. -
The wrapping toolbar lined its widgets up by their boxes, not their
contents. Everything was placed at the top of its row, so the bearings menu
— a few pixels taller than the checkboxes beside it — painted its own glyph
low and read as dropped punctuation. Rows are measured before they ...
cycloidgen v7.1.1
Numbers
- Nothing computed moved. Every quantity this reports is what 7.1.0
reported, to the digit — the export from the trimmed bundle was compared file
by file against one built with every dependency present, and all 29 came out
the same size. This release is packaging, documentation and one dialog.
Fixed
-
"Design for requirements" could not be made to fit a short screen, which
put the buttons that end it out of reach. Four group boxes and the Search
button came to 832 px of minimum height; a dialog inherits the minimum of what
is inside it, so the window could not be sized below 885 px with its frame on
—resize(1080, 660)asked for 660 and silently got 854. On anything shorter
than that, the Use this design and Cancel box at the bottom of the results
column sat off the bottom of the screen, and dragging the edge did nothing,
because the window was already as small as it was allowed to be. A 1366x768
laptop is short enough. So is a 1080p panel at 150% scaling, where the
logical height is 720.The requirements form now scrolls, which is what breaks the inheritance: the
dialog's minimum height goes from 854 px to 156 px and it is bounded by the
results column, which stretches, rather than by the tallest thing in it.Search stays outside the scrolled part, pinned under it. Putting it in with
the fields would have fixed the reaching problem by handing it to the button
the dialog exists to have pressed — on a short screen it would have been below
the fold, which is where the button box was to begin with.The failure was not that it looked cramped. Both buttons that close the dialog
were unreachable, so the feature could be opened and not used. -
The macOS instructions deleted the application. They offered System
Settings ▸ Privacy & Security ▸ Open Anyway first andxattr -dr com.apple.quarantineas an alternative, which reads as "open it, then deal
with the warning". On macOS 15 and later that warning's default button is
Move to Trash. A tester on macOS 26 followed the README exactly and watched
the 1.3 GB install go to the Trash without the application ever running —
Gatekeeper blocked the launch, thensyspolicydmoved the bundle. Open
Anyway is not in that dialog at all; it appears in System Settings only
after a launch has already been blocked, and right-click ▸ Open stopped
bypassing Gatekeeper in macOS 15.So the order is reversed everywhere it appears — the README, the generated
release notes and RELEASING.md — and the clearing step now comes before the
first launch, where it produces no dialog at all. A test holds the order,
because it is prose in two files, one of them a heredoc inside a workflow,
and reflowing it back the wrong way is a plausible tidy-up.Being unsigned was known and stated. What was not stated is that the easiest
click destroys the install, which is the difference between a warning and a
trap. -
--versionfrom the.appdoes not print while the bundle is
quarantined. The README explained why a windowed macOS build still answers
on the command line and offered the command as proof. True for the bundle the
release workflow builds, which never carries the flag, and false for the one
a user downloads: Gatekeeper blocks theexecas well as the double-click,
so the command hangs for about ten seconds and prints nothing. It answers
normally once the quarantine flag is cleared, and the claim now says so.Both of these came from a tester on real hardware. Neither could have been
caught here: the release workflow runs the binary out of the mounted image
and it passes, because a file the workflow built itself was never quarantined
in the first place.
Changed
-
The bundle is 790 MB, down from 1.2 GB. 444 MB of it, 36%, was three
packages that CadQuery declares as dependencies and this application never
imports:casadi, an interior-point optimiser, at about 220 MB;numbawith
LLVM behind it at 142 MB; andtrame, a browser viewer, at 20 MB. CadQuery
declares what CadQuery can do, not what one caller uses.The note this replaces said the bundle was "essentially all OCCT" and
suggested a build without the CAD kernel for anybody who cared. Both halves
were wrong. OCCT is 152 MB — 12% — and the optimiser that arrives beside it is
larger than it is, so the prescribed remedy would have given up STEP and STL
export, which is half the point of the tool, to save a third of what came off
by shipping the same features with less beside them. The size was a plausible
guess nobody had weighed; measuring it took one pass and reversed the answer.Nothing is dropped on the strength of not finding an
import. Each package is
made unimportable and the whole suite is run against that — 778 tests — and the
export is compared file by file against one from an environment with every
dependency present: 29 files, identical sizes, kernel path included.casadineeded more than an exclusion, because it is imported whether it is
used or not:cadquery/__init__.pyreachesocc_impl.solver, which imports it
at the top. Sopackaging/rthook_casadi.pystands in for it, and what makes
the substitution safe is where the module uses it — all thirty-seven
references are inside function bodies, and the only names that run at import
are two annotations. Reading a name off the stand-in works, which is all an
annotation needs; calling one raises with a sentence saying the solver was left
out. This application constrains no assemblies — every part is placed by an
explicit transform off the kinematics — so the limit is real but unreachable
from the application, andpip install cycloidgenbrings the genuine article.Not done, and measured rather than left vague: about 123 MB of VTK is never
loaded either. It is left in because it cannot be checked the way the rest
was. Excluding a VTK module is a missing DLL in a frozen build, not a failed
import in a virtual environment, so the test suite would go green on a bundle
that had lost the 3D view.
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.12: 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.