spacr.qt.widgets.fractal_travel

The spaceout fractal: a GPU shader when there is one, Numba otherwise.

Ported from fractal_travel.py v2.1.0. Two renderers, not one engine with a switch:

  • GPU – VisPy/gloo with a GLSL fragment shader, four spatial samples per physical pixel, and a detail loop that adapts from sampled GPU time.

  • CPU – a cheaper orbit-fold fractal in Numba, evaluated at animation rate with a four-position temporal 2x2 jitter and a rolling four-frame window. No keyframes and no crossfades: every displayed frame is new.

THREE THINGS THIS FILE DOES THAT THE SCRIPT DID NOT.

vispy is not installed in the shipped environment, so backend='auto' resolves to CPU today and to GPU the day it is. Nothing else changes.

It is PySide6. The script was PyQt6, and importing that binding inside this application would put two Qt bindings in one process, which does not raise – it segfaults.

And it WINDS DOWN. A backdrop eating cores while a segmentation runs is the opposite of what it is for, so pause() stops the render loop and leaves the last frame on screen. See CpuFractalWidget.pause().

Exceptions

GpuBackendError

The GPU renderer could not be built. Always caught by auto.

Classes

CameraState

Where the GPU field is looking, at one instant.

DepthPhase

How far along the trajectory the camera is, as a number that only grows.

HardwareProfile

CPU capacity used to choose a conservative automatic render quality.

OrbitEngine

The four-frame temporal window, and nothing else.

Pointer

Where the pointer is, and whether it is pushing.

RegionTour

Floats the camera between the coordinates worth looking at.

RuntimeControls

What the user can move while it is running.

Settings

What the picture is made of. Every field is a Preferences row.

Functions

apply_saved_controls(→ int)

Push the saved settings into every running backdrop.

clamp(→ float)

Return value limited to the inclusive low/high range.

create_fractal_widget([settings, controls, hardware])

Build the fractal backdrop, GPU when there is one.

default_region_tour(→ RegionTour)

A tour over the committed regions, or an empty one without them.

gpu_is_available(→ bool)

Whether the GPU renderer can be built at all.

nudge_zoom_rate(→ float)

Speed the descent up or slow it down.

pattern_for_this_machine(→ str)

The pattern that can actually be drawn here.

platform_can_do_opengl(→ bool)

Whether this Qt platform can host a GL canvas at all.

resolve_backend(→ str)

Which renderer will actually run, given what is installed.

resolved_cpu_threads(→ int)

How many Numba workers to take, leaving the application some.

resolved_quality(→ str)

Resolve auto to a quality the selected backend can sustain.

restart_the_dive(→ None)

Send every running backdrop back to the surface.

state_at_seconds(→ CameraState)

The camera at t. Pure, so a test can assert it moves.

target_render_size(→ tuple)

How many pixels to shade for a widget of this size.

Module Contents

exception spacr.qt.widgets.fractal_travel.GpuBackendError[source]

Bases: RuntimeError

The GPU renderer could not be built. Always caught by auto.

Initialize self. See help(type(self)) for accurate signature.

class spacr.qt.widgets.fractal_travel.CameraState[source]

Where the GPU field is looking, at one instant.

Parameters:
  • t – the time the state was computed for, in seconds.

  • depth – distance travelled along the trajectory, divided by 12.

  • tx – horizontal drift offset in shader coordinates, scaled by the dream amount.

  • ty – vertical drift offset in shader coordinates, scaled by the dream amount.

  • rotation – rotation of the view in radians.

  • shear_x – upper off-diagonal term of the 2 × 2 stretch-and-shear matrix applied after the rotation.

  • shear_y – lower off-diagonal term of that matrix.

  • stretch_x – horizontal diagonal term of that matrix; 1.0 leaves the axis unstretched.

  • stretch_y – vertical diagonal term of that matrix; 1.0 leaves the axis unstretched.

  • palette_phase – offset added to the colour palette’s phase.

class spacr.qt.widgets.fractal_travel.DepthPhase[source]

How far along the trajectory the camera is, as a number that only grows.

SPEED MUST CHANGE THE RATE, NOT THE POSITION. Depth used to be t * speed, so a scroll that doubled the speed doubled the depth in the same instant: measured at t=60s, speed 1 -> 2 moved the camera 5.0 units, which is 3,600 frames of ordinary travel arriving in one. That is the jump reported as “it ruins the immersion when it jumps”.

Integrating instead – phase += dt * speed – makes a speed change continuous by construction. The camera is exactly where it was; only how fast it leaves matters.

Kept as a small object rather than two floats on the widget because the invariant is worth naming: value never decreases.

Create the depth phase at rest, with no previous timestamp.

advance(t: float, speed: float) → float[source]

Move the phase to wall-clock t at speed, and return it.

A t that goes BACKWARDS – a restart, a clock reset – re-bases rather than rewinding: the phase is the distance travelled, and travel does not un-happen.

Parameters:
  • t – wall-clock time in seconds; the first call, or a time earlier than the last one, only re-bases.

  • speed – travel rate in phase units per second; negative values count as 0.

class spacr.qt.widgets.fractal_travel.HardwareProfile[source]

CPU capacity used to choose a conservative automatic render quality.

Parameters:

logical_cpus – logical processors available to the application.

static detect() → HardwareProfile[source]

Read what this machine can offer the renderer.

FLOORED AT ONE CPU. os.cpu_count returns None on some platforms, and a worker pool sized from None is a crash rather than a slow backdrop.

Returns:

the profile.

class spacr.qt.widgets.fractal_travel.OrbitEngine(thread_count: int)[source]

The four-frame temporal window, and nothing else.

Holds no keyframes: the only state is the ring of the last four jitter phases, which is what the antialiasing needs and all it needs.

Four is the published 2x2. samples, set by the widget from the Supersampling setting, makes it N x N phases walked over N * N frames; one a side keeps no history at all.

Parameters:

thread_count – worker threads to render with. Clamped to at least one, so a caller that computed zero from an unavailable CPU count still renders.

Create the orbit engine without allocating its buffers yet.

Parameters:

thread_count – worker threads to render with; clamped to at least one.

render(width: int, height: int, t: float, speed: float, dream: float, iterations: int, pointer_x: float = 0.0, pointer_y: float = 0.0, pull: float = 0.0, push: float = 0.0) → numpy.ndarray[source]

Render one frame of the orbit.

Parameters:
  • width – the frame’s width in pixels.

  • height – its height in pixels.

  • t – the time to render at.

  • speed – the travel-speed multiplier.

  • dream – how far the orbit wanders.

  • iterations – the iteration budget per pixel.

  • pointer_x – horizontal pointer influence, 0 for none.

Returns:

the rendered frame.

class spacr.qt.widgets.fractal_travel.Pointer[source]

Where the pointer is, and whether it is pushing.

SAMPLED, NEVER RECEIVED. The backdrop sits behind every control; a widget that accepted mouse events would eat the click meant for the button on top of it. So nothing here is a mouse handler – the position is read from QCursor.pos() on the render tick, and the buttons from QApplication.mouseButtons(), both of which are global state that costs nothing and steals nothing.

Coordinates come back in the -1..1 space the fractals already work in, with (0, 0) at the centre, so a kernel can use them without knowing anything about widgets.

Create the pointer state the kernels read.

Drag movement accumulates rather than being sampled: a dropped frame does not lose the movement, it arrives with the next one instead.

sample(widget, size: float = 1.0, strength: float = 1.0) → Pointer[source]

Read the pointer relative to widget. Never raises.

Parameters:
  • widget – visible Qt widget whose global rectangle defines the returned centred coordinates and inside/outside state. Coordinates use the short edge as their scale, so that axis maps to -1..1 and the long axis may extend beyond it.

  • size – how far the effect reaches in short-edge-normalised coordinate units; 1.0 reaches the widget’s short edge.

  • strength – how hard it pulls, 0 to 2.

class spacr.qt.widgets.fractal_travel.RegionTour(regions, dwell: float = 18.0, travel: float = 9.0)[source]

Floats the camera between the coordinates worth looking at.

Around twenty regions are chosen on the image and the camera floats automatically towards them.

SMOOTHLY IS THE WHOLE REQUIREMENT, so the interpolation is a smoothstep rather than a straight line: it leaves one region and arrives at the next with zero velocity, which is what stops the arrival reading as a stop. A linear blend is continuous in position and not in velocity, and the eye sees the corner.

DRIFT IS OFF THE MOMENT THE USER TAKES THE CAMERA. Dragging is a statement about where they want to be, and a tour that resumes over it is the application arguing. take_over() stops it for good; restart() is what Ctrl+R calls.

Parameters:
  • regions – (name, x, y, half_width, score) rows, usually spacr.qt.widgets.fractal_regions.REGIONS.

  • dwell – seconds spent at a region before leaving.

  • travel – seconds spent moving between two regions.

Set up a tour that dwells on each region and travels between them.

Parameters:
  • regions – the regions to visit, in order.

  • dwell – seconds spent on a region; floored just above zero, so a tour cannot be configured to skip its own stops.

  • travel – seconds spent moving between them, floored the same way.

period() → float[source]

Seconds for one full circuit of every region.

restart() → None[source]

Ctrl+R: hand the camera back to the tour.

take_over() → None[source]

The user moved the camera. The tour does not argue.

target_at(seconds: float) → tuple | None[source]

Where the camera should be heading at seconds.

None when the tour is not steering, so a caller can leave the camera exactly where the user put it rather than being handed a coordinate it has to ignore.

Parameters:

seconds – time along the tour in seconds; it wraps after one full circuit of the regions.

property active: bool[source]

Whether the tour is still steering.

class spacr.qt.widgets.fractal_travel.RuntimeControls[source]

What the user can move while it is running.

speed_at(t: float) → float[source]

The speed to use at t seconds.

Constant speed unless variable_speed is on, in which case it sweeps between speed_min and speed_max – named bounds rather than a hidden percentage, so what the travel will actually do is readable from the settings instead of inferred from watching it. speed_period is how long one full sweep takes, which is the “how gradually” control: a larger number is a slower CHANGE, not a slower fractal.

The bounds are used in whichever order they are given: a min above a max is a swapped pair, not an empty range, and refusing to animate would be a worse answer than animating between the two numbers.

Parameters:

t – elapsed time in seconds; it sets the point on the sine sweep when variable speed is on and is ignored otherwise.

class spacr.qt.widgets.fractal_travel.Settings[source]

What the picture is made of. Every field is a Preferences row.

supersampling is samples per pixel along each axis, the Fractal tab’s Supersampling row. Every renderer used a fixed 2x2 whatever it said; now the shaders and the CPU kernels take an N x N grid from it. Two is the published grid, so a Settings() nobody filled in draws what it always drew.

validated() → Settings[source]

A copy with every field inside the range the renderers accept.

Clamped rather than refused: this is a backdrop, and a preferences file with a silly number in it must not stop the application from drawing one.

spacr.qt.widgets.fractal_travel.apply_saved_controls() → int[source]

Push the saved settings into every running backdrop.

Returns:

how many were updated.

THE BACKDROP KEEPS THE CONTROLS IT WAS BUILT WITH. Saving Preferences writes new values to the store, but an existing RuntimeControls object otherwise continues holding its old values. This function synchronises every live object so changes take effect immediately.

Everything that can change while a backdrop is on screen is pushed here. What cannot – the pattern, the backend, the quality, the scale and the Mandelbrot reference orbit – needs the backdrop rebuilding, which spacr.qt.widgets.ambient.rebuild_the_spaceout_backdrops() does when Preferences is saved.

spacr.qt.widgets.fractal_travel.clamp(value: float, low: float, high: float) → float[source]

Return value limited to the inclusive low/high range.

Parameters:
  • value – the number to limit.

  • low – inclusive lower bound.

  • high – inclusive upper bound.

spacr.qt.widgets.fractal_travel.create_fractal_widget(settings: Settings | None = None, controls: RuntimeControls | None = None, hardware: HardwareProfile | None = None)[source]

Build the fractal backdrop, GPU when there is one.

Returns:

a QWidget carrying backend_name, stats_text(), pause(), resume() and shutdown(). Never raises for a missing GPU: an explicit backend='gpu' that cannot be built still falls back, because a backdrop is not worth refusing to start the application over.

spacr.qt.widgets.fractal_travel.default_region_tour(**kwargs) → RegionTour[source]

A tour over the committed regions, or an empty one without them.

spacr.qt.widgets.fractal_travel.gpu_is_available() → bool[source]

Whether the GPU renderer can be built at all.

Asked rather than assumed, and asked WITHOUT importing vispy into the application when the answer is no: importlib.util.find_spec looks the module up without executing it, so a missing vispy costs nothing and a present one is not initialised twice.

A platform that cannot host a GL canvas counts as no GPU, because the alternative is a core dump rather than an exception.

spacr.qt.widgets.fractal_travel.nudge_zoom_rate(steps: int) → float[source]

Speed the descent up or slow it down.

Parameters:

steps – how many notches; positive is faster.

Returns:

the resulting rate, or 0.0 when no backdrop is running.

Clamped, unlike the settings fields: this is a key held down rather than a number somebody typed, so there is nothing to tell them about and a rate of 10^12 from leaning on an arrow key is not a request.

spacr.qt.widgets.fractal_travel.pattern_for_this_machine(pattern: str, backend: str = 'auto') → str[source]

The pattern that can actually be drawn here.

Parameters:
  • pattern – what the user asked for.

  • backend – the resolved backend, or "auto".

Returns:

pattern, or the fallback when it cannot be drawn.

TWO PATTERNS ARE GPU-ONLY. Mandelbrot needs a texture of the reference orbit and a shader to perturb around it, and orbit_gpu is a fragment shader with no numba twin – the CPU orbit fold is a DIFFERENT picture, four samples across four frames rather than four of one instant, which is why the two are separate entries at all.

So a machine with no usable GL context gets the orbit fold instead – it has a CPU renderer and is the cheapest of the ones that do – rather than a backdrop that draws nothing.

Silent, and deliberately: the backdrop is decoration, and a dialog explaining that a machine cannot run one of five ornaments is worth less than the interruption costs.

spacr.qt.widgets.fractal_travel.platform_can_do_opengl() → bool[source]

Whether this Qt platform can host a GL canvas at all.

CHECKED BEFORE THE GPU WIDGET IS BUILT, because getting it wrong does not raise. On the offscreen platform Qt prints “QOpenGLWidget is not supported on this platform” and the process DUMPS CORE – which no except around the constructor can catch, so the fallback in create_fractal_widget would never run. Every test and every headless launch is that platform.

spacr.qt.widgets.fractal_travel.resolve_backend(requested: str) → str[source]

Which renderer will actually run, given what is installed.

Parameters:

requested – 'auto', 'gpu' or 'cpu'; any other value is treated as DEFAULT_BACKEND, and 'auto' picks the GPU when one is available.

Returns:

'gpu' or 'cpu' – never 'auto', because a caller showing the user which one they are on cannot show them “auto”.

spacr.qt.widgets.fractal_travel.resolved_cpu_threads(settings: Settings, hardware: HardwareProfile) → int[source]

How many Numba workers to take, leaving the application some.

Capped at 24 because beyond that the scheduling overhead grows for this image size, and capped below the machine’s count because a backdrop that takes every core starves the run the user actually cares about.

Parameters:
  • settings – backdrop settings; a non-None cpu_threads is used, clamped to the threads available, instead of the automatic choice.

  • hardware – CPU profile; its logical_cpus, further capped by Numba’s thread limit and by 24, is the number of threads available.

spacr.qt.widgets.fractal_travel.resolved_quality(requested: str, backend: str, hardware: HardwareProfile) → str[source]

Resolve auto to a quality the selected backend can sustain.

Explicit quality names pass through unchanged. GPU auto mode uses the balanced profile because GPU capacity is otherwise unknown; CPU auto mode uses high only when at least sixteen logical processors are available.

Parameters:
  • requested – auto, balanced, high, or a future explicit quality name.

  • backend – resolved renderer backend, normally gpu or cpu.

  • hardware – detected logical-CPU capacity.

Returns:

the explicit quality name to apply.

spacr.qt.widgets.fractal_travel.restart_the_dive() → None[source]

Send every running backdrop back to the surface.

Called when the fractal settings change. A dive that resumed at the depth it had reached would apply the new numbers to a viewport thirty decades down, where a changed starting scale or iteration count has nothing recognisable to act on – so the change looks as though it did nothing.

spacr.qt.widgets.fractal_travel.state_at_seconds(t: float, speed: float, dream: float, depth_phase: float | None = None) → CameraState[source]

The camera at t. Pure, so a test can assert it moves.

Parameters:
  • t – time in seconds; it drives every oscillation of the camera.

  • speed – travel rate, used only when depth_phase is None.

  • dream – amount of drift, shear and stretch; 0 keeps the camera centred and unskewed, though it still rotates.

  • depth_phase – the integrated distance travelled. When given it is what positions the camera along the trajectory, and speed no longer does – which is what stops a scroll teleporting it. None reproduces the old t * speed, for callers that have no phase to keep.

spacr.qt.widgets.fractal_travel.target_render_size(logical_width: int, logical_height: int, device_scale: float, render_scale: float, base_pixels: float = 0.0, adaptive_scale: float = 1.0) → tuple[source]

How many pixels to shade for a widget of this size.

LIFTED OUT OF THE CANVAS so it can be measured. The backdrop asks which of four candidates makes fullscreen choppy while the backdrop is smooth, and says to answer with numbers before writing a fix – which is not possible while the arithmetic only exists inside a nested method on a class that needs a GL context.

The rule itself is unchanged: shade render_scale squared of the widget’s own physical pixels, never more than the widget has and never fewer than 180,000, keeping the aspect ratio and an even width and height.

Parameters:
  • logical_width – widget width in logical pixels, floored at 320.

  • logical_height – widget height in logical pixels, floored at 180.

  • device_scale – device pixel ratio, floored at 1.0.

  • render_scale – linear fraction of the physical pixels to shade; 0 or less uses base_pixels instead.

Returns:

(width, height) in physical pixels.

Nested helpers

_join_on_destroy._join(*_args)

Stop and join the render thread. Closes over the thread ONLY.

spacr/qt/widgets/fractal_travel.py:935

_make_cpu_widget.CpuFractalWidget.__init__(self, parent=None) → None

Build the CPU canvas, painting its own background.

spacr/qt/widgets/fractal_travel.py:1096

_make_cpu_widget.CpuFractalWidget._accept_frame(self, frame, render_seconds: float) → None

Take a rendered frame, note how long it took, and repaint.

spacr/qt/widgets/fractal_travel.py:1268

_make_cpu_widget.CpuFractalWidget._adapt_resolution(self) → None

Trade resolution for frame rate, from the measured render time.

WAITS FOR TWELVE FRAMES and then only reconsiders every twenty-fourth, so the scale settles instead of oscillating on a single slow frame. The budget is 78% of the period rather than all of it, because Qt’s own conversion and the rest of the application have to fit in the remainder.

Both directions are damped and clamped – down no further than 0.58, up no further than 1.35 – so a stall cannot drive the picture to nothing and a fast machine cannot drive it past what the window can show.

spacr/qt/widgets/fractal_travel.py:1242

_make_cpu_widget.CpuFractalWidget._on_failure(self, message: str) → None

Record the worker’s error and stop treating a frame as pending.

spacr/qt/widgets/fractal_travel.py:1299

_make_cpu_widget.CpuFractalWidget._request_frame(self) → None

Ask the worker for the next frame, unless stopped or paused.

spacr/qt/widgets/fractal_travel.py:1217

_make_cpu_widget.CpuFractalWidget._target_size(self) → tuple[int, int]

The pixel size to shade at, from the render scale and the window.

RENDER SCALE is the fraction of the window’s own pixels to shade: 1.0 is native and anything less trades sharpness for speed. It was a setting nobody read, and it is the direct answer to “how do I get the image sharper”.

ON THIS PATH ONLY, AND THAT IS WORTH SAYING WHERE THE CLAIM IS MADE. _render_scale() is read here and nowhere else: the GPU canvas shades its physical size at every one of its three uses and never calls target_render_size. So on a machine with a working GPU – which is the default, backend='auto' – this setting has no effect whatever, and a reader who came here from the sentence above would otherwise go looking for the code that applies it.

IT IS NOT A VISIBLE CONTROL, which is why this is a comment rather than a tooltip: spaceout/fractal_render_scale is a bare QSettings key with no row in Preferences, so nobody can move a slider and watch nothing happen. Making the GPU honour it would mean render-to-texture, which was measured and rejected: every shader runs 7x to 25x inside its frame budget at 4K, so there is no headroom to buy back.

spacr/qt/widgets/fractal_travel.py:1185

_make_cpu_widget.CpuFractalWidget.closeEvent(self, event) → None

Shut the render thread down before the widget goes.

spacr/qt/widgets/fractal_travel.py:1358

_make_cpu_widget.CpuFractalWidget.is_paused(self) → bool

Whether the animation is currently held.

spacr/qt/widgets/fractal_travel.py:1169

_make_cpu_widget.CpuFractalWidget.paintEvent(self, _event) → None

Draw the last frame, or the ground colour before there is one.

spacr/qt/widgets/fractal_travel.py:1307

_make_cpu_widget.CpuFractalWidget.pause(self) → bool

Stop rendering and leave the last frame on screen.

Called when a RUN STARTS. A backdrop taking nineteen cores while a segmentation is queued is the opposite of what it is for, and stopping is better than thinning: a slower fractal still holds the threads.

Returns:

True when this call did the stopping, False when it was already paused – so a caller can tell whether to resume.

spacr/qt/widgets/fractal_travel.py:1144

_make_cpu_widget.CpuFractalWidget.resizeEvent(self, event) → None

Ask for a frame at the new size, unless one is already in flight.

Guarded so a drag-resize does not queue a render per pixel of travel – the worker would then be shading sizes the window has already left.

spacr/qt/widgets/fractal_travel.py:1317

_make_cpu_widget.CpuFractalWidget.resume(self) → bool

Start rendering again from where the clock left off.

spacr/qt/widgets/fractal_travel.py:1161

_make_cpu_widget.CpuFractalWidget.set_animating(self, on: bool) → bool

AmbientWidget’s verb for the same thing.

The ambient backdrop this replaces is stopped and started with set_animating, and its callers – the Home screen’s teardown among them – reach for that name. Answering to it makes this a drop-in rather than something every call site has to learn.

spacr/qt/widgets/fractal_travel.py:1173

_make_cpu_widget.CpuFractalWidget.shutdown(self) → None

Stop for good and join the thread. Safe to call twice.

spacr/qt/widgets/fractal_travel.py:1344

_make_cpu_widget.CpuFractalWidget.stats_text(self) → str

The render size, rate and state, for the overlay.

spacr/qt/widgets/fractal_travel.py:1327

_make_cpu_widget._Worker.__init__(self) → None

Build the engine this thread will shade with.

spacr/qt/widgets/fractal_travel.py:1033

_make_cpu_widget._Worker._say_something(signal, *args) → None

Emit, unless the object that owns the signal has been freed.

The last thing this thread does with a widget that is being destroyed, so it must never raise: nothing is listening, and an exception here ends the process rather than the frame.

spacr/qt/widgets/fractal_travel.py:1069

_make_cpu_widget._Worker.render(self, request: object) → None

Shade one frame and hand it back, if anyone is still there.

A FRAME CAN FINISH AFTER ITS WIDGET IS GONE. The shading runs on this thread while the GUI thread may be closing the window or swapping the pattern, and Qt deletes the worker’s C++ side with it – so emit raises “Signal source has been deleted”. The except below then emitted the FAILURE signal, which raised the same way, and an exception escaping a slot on a QThread takes the process down: “Aborted (core dumped)”.

spacr/qt/widgets/fractal_travel.py:1040

_make_gpu_widget.GpuFractalWidget.__init__(self, parent=None) → None

Build the GL widget under the heavy-import lock.

spacr/qt/widgets/fractal_travel.py:2598

_make_gpu_widget.GpuFractalWidget.closeEvent(self, event) → None

Shut the canvas down before the widget goes.

spacr/qt/widgets/fractal_travel.py:2660

_make_gpu_widget.GpuFractalWidget.is_paused(self) → bool

Whether the canvas is currently held.

spacr/qt/widgets/fractal_travel.py:2632

_make_gpu_widget.GpuFractalWidget.pause(self) → bool

Stop drawing while a run is on. The last frame stays up.

spacr/qt/widgets/fractal_travel.py:2618

_make_gpu_widget.GpuFractalWidget.resume(self) → bool

Restart the animation. Returns whether it was paused.

spacr/qt/widgets/fractal_travel.py:2625

_make_gpu_widget.GpuFractalWidget.set_animating(self, on: bool) → bool

AmbientWidget’s verb for the same thing.

The ambient backdrop this replaces is stopped and started with set_animating, and its callers – the Home screen’s teardown among them – reach for that name. Answering to it makes this a drop-in rather than something every call site has to learn.

spacr/qt/widgets/fractal_travel.py:2636

_make_gpu_widget.GpuFractalWidget.shutdown(self) → None

Stop for good. Safe to call twice and after Qt has freed it.

spacr/qt/widgets/fractal_travel.py:2651

_make_gpu_widget.GpuFractalWidget.stats_text(self) → str

The render size, rate and state, for the overlay.

spacr/qt/widgets/fractal_travel.py:2647

_make_gpu_widget._Canvas.__init__(self) → None

Build the GL canvas, hidden until it is placed.

spacr/qt/widgets/fractal_travel.py:2004

_make_gpu_widget._Canvas._budget_at(self, depth: float, orbit) → int

The iteration budget at depth, capped by the reference.

Parameters:
  • depth – the depth in decades.

  • orbit – the reference orbit, or None while it is built.

spacr/qt/widgets/fractal_travel.py:2192

_make_gpu_widget._Canvas._dive_uniforms(self, depth: float, centre, orbit) → dict

The zoom’s uniforms for a depth and a centre.

Parameters:
  • depth – the depth in decades.

  • centre – (re, im) offset from the reference orbit.

  • orbit – the reference orbit, or None while it is built.

spacr/qt/widgets/fractal_travel.py:2208

_make_gpu_widget._Canvas._galaxy_uniforms(self, phase: float) → dict

Where the passing galaxies are, for the space pattern.

Parameters:

phase – the flight’s clock, the shader’s u_time.

Returns:

{} for every other pattern.

spacr/qt/widgets/fractal_travel.py:2089

_make_gpu_widget._Canvas._glide_frame(self, orbit, seconds: float) → tuple

One frame of the glide toward the busiest part of the view.

Parameters:
  • orbit – the reference orbit, or None while it is built.

  • seconds – time since the previous frame.

Returns:

(centre, depth).

THE DECIDING IS IN _GlideCamera, which has no Qt in it. This is the part that cannot be: reading the settings, handing it the drag, and surveying the view on a worker thread so an escape map never stalls a frame. A survey that lands after the reference moved describes a different frame and is dropped.

spacr/qt/widgets/fractal_travel.py:2229

_make_gpu_widget._Canvas._glide_frame._look()

Score the current view for structure. Off the GUI thread.

spacr/qt/widgets/fractal_travel.py:2288

_make_gpu_widget._Canvas._mandelbrot_uniforms(self, elapsed: float) → dict

The zoom’s own uniforms for this instant.

Returns:

{} for every other pattern, so the shared update can splice it in unconditionally.

spacr/qt/widgets/fractal_travel.py:2127

_make_gpu_widget._Canvas._on_native_destroyed(self, *_args) → None

Mark the canvas dead and stop its timer.

The native window can go before Python does, and a timer that fires after it draws into an object that is not there.

spacr/qt/widgets/fractal_travel.py:2567

_make_gpu_widget._Canvas._on_timer(self, _event) → None

Advance one frame, unless paused or already torn down.

spacr/qt/widgets/fractal_travel.py:2547

_make_gpu_widget._Canvas._pointer_state(self)

(x, y, pull, push) for the shader, in -1..1 space.

Returns:

zeros when the pointer is not being followed, so a shader can multiply by them unconditionally.

THE MANDELBROT IS DRAGGED, NOT ATTRACTED. Mouse interaction moves it only through drag input; the pointer’s stationary position does not pull the view about.

The other three patterns are fields that can be warped toward a point and look right doing it. A deep zoom is a camera: pulling its coordinates toward wherever the mouse happens to rest slides the picture continuously, which reads as the image drifting away from you rather than as anything you did.

The pointer is still SAMPLED, because that is what accumulates the drag – _steer consumes it. Only pull and push, which are the position-driven terms, are withheld.

spacr/qt/widgets/fractal_travel.py:2475

_make_gpu_widget._Canvas._refine_the_reference(self, camera, orbit, budget, depth, span)

Move the reference back onto the boundary, now and then.

Surveyed and rebuilt on a worker thread: the survey is a 96x54 escape map and the rebuild iterates the orbit at full precision, neither of which belongs in a frame. When the new reference lands, the camera’s centre and target move by the opposite of the reference’s move, so the picture stays exactly where it was.

spacr/qt/widgets/fractal_travel.py:2393

_make_gpu_widget._Canvas._refine_the_reference._work()

Find a better reference point for the current view. Off the GUI thread.

spacr/qt/widgets/fractal_travel.py:2437

_make_gpu_widget._Canvas._start_the_reference_orbit(self) → None

Iterate Z off the GUI thread and upload it when it is ready.

spacr/qt/widgets/fractal_travel.py:2101

_make_gpu_widget._Canvas._start_the_reference_orbit._work()

Compute the reference orbit. Off the GUI thread.

It is the expensive part of deep zooming – high-precision iteration of one point that every pixel is then perturbed from – so it must not run where it would stall the frame it is for.

spacr/qt/widgets/fractal_travel.py:2107

_make_gpu_widget._Canvas._steer(self, depth: float, budget: int, orbit, seconds: float = 0.0)

Where the dive is heading, in the reference orbit’s frame.

Parameters:

seconds – the simulation clock; unused by the fixed and guided paths, and kept so every caller passes the same arguments. The tour path is _glide_frame().

Returns:

(offset_re, offset_im) for u_center_offset.

THE DECIDING IS IN SteeringCamera, which has no Qt in it and can be driven frame by frame in a test. This method is the part that cannot be: reading the settings, and running the search on a worker thread so a 96x54 escape map does not stall the frame.

Every claim about how smooth the motion is used to come from a simulation written beside the code rather than from the code, because this logic lived inside a canvas that needs a GL context to exist. That is why three fixes in a row were wrong.

spacr/qt/widgets/fractal_travel.py:2302

_make_gpu_widget._Canvas._steer._look()

Plan the next guided step. Off the GUI thread.

spacr/qt/widgets/fractal_travel.py:2372

_make_gpu_widget._Canvas._update_uniforms(self, elapsed: float) → None

Push this frame’s camera and time into the shader.

The size is floored at one pixel: a canvas mid-resize can report zero, and a zero dimension reaches the shader as a division by nothing.

spacr/qt/widgets/fractal_travel.py:2050

_make_gpu_widget._Canvas._upload_the_orbit_if_it_arrived(self) → None

Put the finished orbit into the shader’s texture.

ON THE GUI THREAD, from inside the draw, because that is where the GL context lives. Uploaded once: _orbit_uploaded is the orbit object itself, so a rebuilt one would be noticed.

spacr/qt/widgets/fractal_travel.py:2456

_make_gpu_widget._Canvas.on_draw(self, _event) → None

Draw one frame, unless the canvas is already torn down.

spacr/qt/widgets/fractal_travel.py:2516

_make_gpu_widget._Canvas.on_resize(self, _event) → None

Resize the GL viewport, never to zero.

A canvas mid-resize reports zero, and a zero viewport is a GL error rather than a small picture.

spacr/qt/widgets/fractal_travel.py:2507

_make_gpu_widget._Canvas.stats_text(self) → str

The render size, rate and state, for the overlay.

spacr/qt/widgets/fractal_travel.py:2576

_make_gpu_widget._Canvas.stop_timer(self) → None

Stop the vispy timer. Safe to call twice, and after deletion.

spacr/qt/widgets/fractal_travel.py:2560

_make_gpu_widget._mandel_setting(name, fallback=None)

One Mandelbrot setting, falling back to the published default.

spacr/qt/widgets/fractal_travel.py:1979

spacr.qt.widgets.fractal_travel.set_num_threads(_n: int) → None[source]

Do nothing: with no Numba there is no thread pool to size.

A NO-OP RATHER THAN AN ABSENT NAME. The caller sets the thread count unconditionally, so leaving this undefined would turn a missing optional dependency into an AttributeError at the call site – far from the import that actually failed.

Parameters:

_n – the thread count, ignored.

spacr/qt/widgets/fractal_travel.py:45