Skip to content

Proposal: Rust-based rewrite of CQ-editor with a Python CadQuery worker - #604

Draft
igors93 wants to merge 114 commits into
CadQuery:masterfrom
igors93:master
Draft

igors93 wants to merge 114 commits into
CadQuery:masterfrom
igors93:master

Conversation

@igors93

@igors93 igors93 commented Oct 24, 2026

Copy link
Copy Markdown

Summary

This pull request proposes a major modernization of CQ-editor, rebuilding its graphical interface in Rust while retaining Python and CadQuery for CAD modeling and script execution.

The work consists of 51 commits focused on improving usability, performance, reliability, and the overall development experience.

Main improvements

  • New architecture: Rust-based GUI with a separate Python worker for CadQuery execution.
  • Modern 3D viewer: GPU rendering, improved camera navigation, selection, and large-model optimizations.
  • Advanced inspection: Section views, exploded assemblies, X-ray visualization, hidden-line rendering, and named camera views.
  • Improved code editing: Parameter controls, source navigation, code generation from selected geometry, and interactive measurements.
  • Export capabilities: High-resolution screenshots, transparent backgrounds, and turntable animations.
  • Testing and documentation: Automated tests, developer documentation, and updated workflows.

Motivation

The goal is to make CQ-editor more responsive, approachable, and powerful while preserving CadQuery as its modeling foundation.

This implementation also provides a foundation for future improvements to the editing experience, rendering performance, and CAD inspection tools.

Scope and compatibility

This is a substantial architectural rewrite rather than a small incremental change. It replaces significant portions of the existing PyQt application, so compatibility, packaging, migration, and long-term maintenance require careful review.

I understand that integrating a change of this magnitude may require discussion, revisions, or a different integration strategy.

I welcome feedback from the maintainers on whether this approach aligns with the future direction of CQ-editor.

Testing

The repository includes automated tests covering the Rust interface, Python worker, application behavior, rendering, and communication between components.

Additional information

The full implementation and commit history are available at:

https://github.com/igors93/CQ-editor

Thank you for taking the time to review this proposal.

Replace the PyQt interface with an egui and wgpu application, and run CadQuery scripts in an isolated Python worker.

Rebuild the editor, 3D viewer, debugger and panels; add process recovery, export support, updated documentation and CI. Remove the legacy GUI and packaging.
Move Rust and Python tests into purpose-based directories under tests/. Add coverage for editor state, scene geometry, worker protocol, script isolation, exports, and process recovery.

Update CI and documentation for the new layout. The suite now contains 79 Rust and 127 Python tests.
Preserve OCCT face, edge, and vertex identifiers for every displayed part and carry their mesh associations through protocol v2.

Pick vertices and edges in screen space with face occlusion, retain the exact hit position, and highlight the selected subshape in the viewer and Objects panel.

Add regression coverage for transformed geometry, merged assemblies, protocol decoding, picking priority, occlusion, and topology selection lifecycle.
Cache tessellated meshes by BREP content and tolerance with a bounded LRU, and let the GUI advertise retained geometry so unchanged meshes travel as lightweight references.

Keep assembly geometry in local coordinates with per-instance transforms, allowing repeated profiles to share CPU and GPU buffers while preserving topology picking and transformed bounds.

Add a lazily built triangle BVH for face picking and regression coverage for cache reuse, protocol references, transformed instances, large dimensions, and BVH pruning.
Require explicit conflict resolution when the watched script changes while the editor buffer has local modifications. Provide compare, keep-local, reload, and save-copy actions in the conflict dialog.

Create a durable recovery backup before confirmed replacement, keep reload separate from model execution, and report exact watcher paths so imported modules can rerun without reloading the main document.

Add regression coverage for protected reloads, external-update classification, recovery copies, save-copy behavior, snapshot validation, and changed-path reporting.
Write document, copy, recovery, and history files through synchronized temporary files before atomically replacing their destinations. Preserve Unix permissions and Windows ACLs and file metadata during replacement, while retaining line endings and UTF-8 BOM state.

Reject invalid UTF-8 without opening or rewriting the source file. Add five-second crash checkpoints, startup session recovery, and a bounded local history with previews and explicit version restoration from the File menu.

Cover interrupted writes, non-UTF-8 input, BOM and metadata preservation, simulated restart recovery for named and untitled documents, checkpoint deduplication, saved baselines, cleanup, and history retention.
Move all documentation into docs/: installation and a guided first-model
tour, a user guide covering the interface, scripting API, 3D view, editor,
debugging, console, exporting, preferences, shortcuts and troubleshooting,
and a development guide covering architecture, the worker protocol,
building, testing and contributing.

Add a roadmap with the bugs and problems found in the current code, gaps in
the everyday experience and planned features for beginners and advanced
users, organized into milestones.

Replace the PyQt-era screenshots with captures of the current interface,
move the changelog and the test guide into docs/, and reduce the worker
README to pointers into the documentation.
Reformat the three files reported by the CI format check with black
26.10.1, the version CI installs. No behavior changes.
After Stop, Restart Python or a worker crash the GUI keeps the previous
objects on screen and releases their keys after the next run. A fresh
worker numbered its objects from n1 again, so that release freed the new
objects and the CQ Inspector, export and upload failed.

The GUI now passes its worker generation in CQ_EDITOR_SESSION and the
worker prefixes its registry keys with it (s1-n1, s2-n1, ...), so stale
releases are harmless. The inspector state is reset when the worker
restarts, and the unknown-object error suggests rendering again.

Add regression tests for prefixed registries, stale releases after a
restart and the session environment of a real worker process. Mark the
fix and the CI format fix as resolved in the roadmap, add status columns
to the roadmap tables, and update the protocol, architecture, testing and
user guide pages.
The property sheet of the Objects panel was a bottom panel without a
scroll area. Panels grow to fit their contents, so in a short pane the
sheet took the whole height, and the tree's scroll area, which keeps a
64-point minimum, was drawn over it.

The sheet's contents now scroll, its height is capped so the tree keeps
at least 80 points (or 60% of a very short pane), and the tree's scroll
area may shrink below its default minimum.

Add headless layout tests that render a sheet much taller than the pane
and check that the tree keeps its room above it. Mark BUG-02 as resolved
in the roadmap.
Breakpoints, the error line and the debug line were plain line numbers.
Adding or removing lines above a breakpoint moved the code but not the
marker, so the debugger stopped on a different statement than the one
marked.

The editor now remembers the text its markers refer to and, whenever the
text changes, moves them with a line map: lines before the edited block
keep their number, lines after it shift, lines inside it map by position,
and a breakpoint on a deleted line is removed. Comparing texts covers
typing, editor commands, reloads and restored versions alike. Runs and
debugger steps sync the markers first, so Python receives current line
numbers.

Add tests for inserted, split, merged, deleted and indented lines and for
the editor's marker syncing. Mark BUG-04 as resolved in the roadmap.
The editor already requests completions when a "." is typed; the editor
guide and the roadmap said the popup only opened on Ctrl+Space.
Apply pointer movement incrementally so orbit, pan, and drag zoom no longer compound the full drag distance every frame.

Add an upper-left zoom overlay with magnifier buttons, a logarithmic percentage slider, and a Fit-relative reference. Keep overlay interactions out of scene picking and wheel handling.

Cover incremental motion, percentage conversion, responsive layout, and real overlay button interaction with regression tests.
Switch to a closed-hand cursor as soon as orbit or pan is pressed and keep it active throughout the drag, including outside the viewport. Use a vertical resize cursor for drag zoom.

Keep navigation gestures and cursors away from the navigation cube and zoom overlay, preserve click selection, and verify that orbit changes only camera direction.
Audit the last PyQt revision into a versioned compatibility matrix with explicit Verified, Enhanced, Changed, Gap, and Unverified states. Require test evidence or a documented decision for every inventoried capability.

Protect the complete legacy shortcut set and mouse gesture mapping, and execute all eight unchanged pre-rewrite examples as worker compatibility fixtures. Record stereo rendering, rich Jupyter output, bundled distribution, preference-default differences, and GUI-only checks without claiming false equivalence.

Fix Ctrl+left-drag panning when an input event only carries the Ctrl modifier. Derive search navigation tooltips from the real command bindings so Previous correctly reports Alt+F3 instead of conflicting with the Top view shortcut.
Align the legacy example regression test with Black 26.10.1, the formatter used by continuous integration. This is a formatting-only follow-up with no behavior change.
Connect picked face, edge, and vertex identifiers to the live OCCT subshapes retained by the Python worker. Add protocol v4 measurement requests for exact entity properties, minimum distance, closest points, supported angles, curve/surface type, area, length, radius, and diameter.

Add Auto, Object, Face, Edge, and Vertex picking modes, retain up to two highlighted topology selections, and present results in a dockable Measurements panel. Explicitly scope topology identifiers to the current scene entry so they are never treated as persistent names after remodeling or a worker restart.

Cover numeric OCCT results, stale identifiers, protocol serialization, selection filtering, dual highlights, and display precision. Update the user and development documentation for the new workflow and identity guarantees.
Picking tested every visible part and, inside each part, every edge segment
and vertex on each pointer move, so hover latency grew linearly with the
assembly: about 9 us per part in release builds, 38 ms at 4,096 parts and
152 ms at 16,384 parts.

Add a generic bounds BVH to spatial.rs and pick in two phases. The viewer
retains a SceneIndex over the world bounds of the visible parts and rebuilds
it only when the scene's pick revision changes (parts added, removed, shown
or hidden); camera moves and selection changes keep it. Each mesh lazily
builds edge-segment and vertex BVHs, shared by all of its instances, which
are pruned in screen space against the pick tolerance. The camera matrices
are now computed once per pick instead of once per projected point.

BVH queries return indices in ascending order, so coincident parts and
entities resolve exactly as the linear scan did; without this, a duplicate
shown after an object could win the pick. Pick revisions come from a
process-wide counter, so an index is never reused for a different scene,
and an index entry whose node key no longer matches is ignored instead of
reporting another node's part.

Tests compare the indexed pick with a linear scan across orthographic,
perspective and close-up views in every selection mode, check that the edge
and vertex BVHs never drop a primitive within tolerance, and cover index
rebuilds, result ordering and pruning. The ignored
pick_latency_on_growing_assemblies benchmark sweeps the pointer over 64 to
16,384 instanced parts: the median pick takes 4 us and 0.1 ms respectively
(0.6 ms and 152 ms before), and rebuilding the index for 16,384 parts takes
about 4 ms.

Document the two-phase picking, how to run the benchmark and why GPU id
picking was not adopted, and mark PERF-03 as resolved.
The worker sends a mesh as a bare reference when the request advertised it
in known_geometry or when it already sent it earlier for the same request,
including previous pauses of a debugging session. The GUI, however, only
kept meshes alive while the current scene showed them. A debugging session
that paused inside a function (where the script's objects are not locals)
dropped those meshes, and when they reappeared the next pause and the final
result failed with "worker referenced unavailable mesh" and "Cannot update
the 3D scene". Debug runs also advertised no geometry at all, so every
debugging session retransmitted and re-uploaded every mesh.

Replace the scene's weak mesh map with a MeshLibrary keyed by content id:

- Meshes advertised in a request and meshes received for it are pinned
  until the request ends (run_finished, console_result or a worker
  restart), which is exactly as long as the worker may reference them.
  scene_add now carries its request id for this.
- Recently used meshes are retained within a 128 MB budget (least recently
  used first out), and known_geometry advertises them too. A render after
  Clear, a debugging session and repeated runs therefore receive references
  for unchanged geometry instead of the arrays.
- A full mesh for geometry that is still held resolves to the existing
  Arc<Mesh>. The renderer keys GPU buffers by mesh and keeps them while the
  mesh lives, so retained and resent geometry is never uploaded again.

The unused Mesh::geometry_id field is removed; the library owns that
mapping. Debug runs now advertise known geometry like normal runs.

Tests replay the failing debugging sequence without retention (it fails
when pinning is disabled), check that advertised geometry survives clearing
the view until its request ends, cover the retention budget and eviction
order, mesh reuse for resent geometry, mesh byte sizes and the scene_add
request id. Document the reference contract and the GUI mesh library.
Every mesh carried two copies of its triangles: the display mesh read from
OCCT's glTF writer, and separate pick triangles tagged with their OCCT face.
The pick copy was built by walking every triangulation node and triangle
from Python, which cost more than the meshing itself on detailed parts
(725 ms of 2.2 s for a plate with 324 filleted holes, 123 ms against 38 ms
of meshing for a knurled cylinder) and enlarged every mesh message by about
a third.

The glTF writer emits each face's triangles in TopExp_Explorer order, so
per-face triangle counts (one call per face) tag every display triangle with
its 1-based face id. The display triangles are then also the pick
triangles, and face_positions / face_indices stay empty. If the counts ever
fail to add up to the display triangle count, the worker falls back to the
previous separate extraction instead of guessing.

The GUI already picked on the display triangles when no separate set was
present, but only reported a face id and built face highlights from the
separate set. Both now use triangle_faces with whichever triangles are
picked.

Measured on fresh shapes (tessellate, before -> after):
- plate with 324 filleted holes: 2189 ms -> 1476 ms, 7.87 MB -> 5.22 MB
- knurled cylinder: 182 ms -> 53 ms, 0.18 MB -> 0.13 MB
- fine sphere: 34 ms -> 21 ms, 0.18 MB -> 0.11 MB

Tests compare the new face ids face by face with the former extraction on
ten shape kinds (fillets, reversed faces of a cut, torus seams, compounds,
a solid listed twice, sketch faces, moved shapes, open shells), cover the
fallback and the count check, and check picking and face highlights on
meshes without separate pick triangles. Mark PERF-01 resolved and record
the measured cost of the temporary glTF file under PERF-02.
A run only reported the script's duration, so a slow render could not be
attributed to CadQuery, meshing, serialization, transfer or the GPU, and
whether unchanged parts were really reused was invisible.

The worker now accumulates, for the whole request (a debugging session
included), the time spent on content ids (BREP hashing), on meshing
geometry not sent as a reference and on base64 encoding, and counts the
meshes it meshed, served from its cache or sent as bare references, plus
the raw bytes sent. run_finished carries them as `timings`, together with
the worker's wall clock just before sending.

The GUI's reader thread stamps when it read the reply and how long parsing
took, so the transfer time (serialization and pipe) follows from the two
clocks; it is reported as unknown if they disagree. After decoding the
scene, one "Run timings" line goes to the Log panel. The renderer logs GPU
uploads (count, size and time) at the debug level. RunFinished is boxed
inside Message to keep the enum small.

Tests cover the reported timings and counts, and the completion criterion
end to end through the server: after editing one part of a three-part
assembly, a GUI that advertises its meshes gets one meshed part and two
references, and a worker without GUI geometry re-meshes only the edited
part and serves the others from its cache. Rust tests cover decoding with
and without timings, the transfer computation and the report.

Checked against the real worker through the Rust reader: re-running an
assembly after changing one part went from 3 meshed parts and 0.53 MB to
1 meshed part, 2 references and 0.08 MB.

Document the timings, add a troubleshooting entry, and record why base64
in JSON is kept: about 35 ms to encode and 6 ms to parse 5 MB of arrays,
against more than a second of meshing for the same model.
Every bug still open in the roadmap (BUG-05 to BUG-20, PERF-02) is fixed,
together with eight problems a review of the whole code found (BUG-21 to
BUG-28). Each item in docs/roadmap.md now records its resolution and
evidence.

Worker and runs:
- Worker events, automatic reload and recovery checkpoints run in eframe's
  App::logic, which also runs while the window is minimized, instead of
  waiting for a repaint (BUG-05).
- Opening another file clears the previous model, selection, inspector and
  variables; a run or debugging session still going for that file is
  discarded and its objects released (BUG-12).
- Local imports are asked for again after every save and successful run,
  and the watched set is replaced by the script and its current imports
  without duplicates (BUG-09).
- The console wraps debug() like show_object, so objects are named after
  their variable (BUG-13). DebugStop derives from BaseException, so a
  script's `except Exception` no longer swallows Stop (BUG-22).
- The glTF round trip reuses one scratch folder per worker, and the next
  worker removes files left by workers stopped while meshing. OCCT 7.8 can
  only write glTF to a file; the file itself costs about 1 ms of the
  writer's 95 ms (PERF-02).
- A scene that cannot be decoded no longer drops the run's variables and a
  queued render; a failed run request clears the debug line (BUG-27).

Interface:
- Headings get the Phosphor icons, so dialog titles show them (BUG-06).
- Help > Documentation opens docs/README.md instead of the PyQt wiki
  (BUG-08).
- Variables: name and type columns take a share of the width, the value
  column keeps at least 80 points, and clicking a row opens a detail view
  with the whole value (up to 2000 characters from the worker). The table
  and the detail view scroll instead of overlapping in a short pane
  (BUG-10).
- After a failed run the empty view and the Objects panel point to the
  Traceback, and empty-panel texts are centered rows instead of justified
  ones (BUG-11).
- Cell labels of the Variables and Traceback tables are not selectable, so
  a click on their text selects the row or jumps to the line (BUG-28).
- The status bar lays out its fixed indicators first, so a long message no
  longer hides them (BUG-25). The Log filter ignores case in sources too
  (BUG-26).
- The completion popup no longer panics when an edit leaves its anchor
  inside a multi-byte character (BUG-23).

Files and data:
- Quitting with "Don't save" no longer leaves a recovery session that the
  next start offers to restore (BUG-21).
- Local history lives in one folder per document (stable FNV-1a hash of the
  path) with timestamps in file names, so a checkpoint reads no other
  version; versions older than 90 days and the oldest beyond 64 MB are
  pruned once per session, and the flat history of earlier releases is
  migrated (BUG-16).
- Files that are not UTF-8 can be opened as Windows-1252 after a prompt;
  saving converts them to UTF-8 and the status bar says so meanwhile
  (BUG-17).
- Extracting a new worker removes the folders of other versions (BUG-15).
- The log is also written to logs/cq-editor.log in the data folder, rotated
  at 2 MB, with GUI panics and a thread that flushes buffered lines every
  second (BUG-20).

Platform and build:
- In Windows release builds, --help and argument errors go to the console
  of the parent shell, or to a message box without one (BUG-14).
- build.rs embeds the icon and version information with winresource; a
  missing resource compiler only warns, except in release CI where
  CQ_EDITOR_REQUIRE_RESOURCES=1 makes it an error (BUG-18).
- eframe is used without its "links" feature, which removes webbrowser and
  the hand-made libshlwapi.a of GNU builds; egui links are opened with the
  open crate, and only http(s) URLs are opened, so a link in an upload
  service's reply cannot start a local program (BUG-19, BUG-24).

Tests cover the fixes where the code allows it (theme fonts, documentation
and URL checks, link capture, Windows-1252 documents, the recovery layout,
migration and pruning, worker extraction, log rotation, empty-state layout,
the Variables table, the completion anchor, the Log filter, console and
debugger behavior, scratch files). The interface fixes were also checked in
the running application, and BUG-14 and BUG-18 with the release
executable. Docs, the changelog and the roadmap are updated.
Bound the worker to the Python, CadQuery, and OCP families it actually supports, and add blocking CI lanes for the released combinations plus an early-warning lane for CadQuery master.

Adapt generated collection types and TopoDS downcasts across OCP 7 and 8, expose the OCP version in worker readiness and diagnostics, and record reproducible environment data in test and benchmark output.

Add topology-aware compatibility coverage and document the support policy, exact matrix, update procedure, and installation guidance.
Reject count-preserving face reordering by comparing each proposed glTF face group with OCCT triangulation bounds and representative triangle centroids before assigning topology identifiers.

Keep the independent per-face pick mesh as a safe fallback, tolerate triangle reordering within one face, and cover the behavior across the OCP 7.8, 7.9, and 8.0 compatibility matrix.

Document the validation path and its measured cost on a 324-hole plate.
Flush captured stdout and stderr every 64 Ki characters, cap each stream at 1 MiB per request, and add explicit truncation markers. Limit Python log records to 256 Ki characters and repeated records to 1 MiB per request.

Replace unbounded Rust JSON and stderr line readers with a draining bounded reader. Apply separate 2 MiB control, 512 MiB mesh-response, and 256 KiB native-stderr limits, reporting oversized protocol messages as worker failures.

Cover fragmented output, request resets, repeated logs, bounded line recovery, message classification, and a real worker process that writes 2 MiB without a newline.
Mesh ids hashed only the shape's BREP and the tessellation tolerances, but
the GUI keeps meshes across Python restarts. After switching to another
environment or upgrading OCP, the new worker could answer with a reference
to a mesh made by the previous mesher. Ids now also hash the mesh
generator: MESH_FORMAT, the OCP version and the installed OCP binary. The
worker no longer reports OCP as unknown for conda installs, which carry no
pip metadata.

src/app.rs had grown to about 2,000 lines. It is now src/app/, one module
per concern around the central Action dispatch: documents, runs, worker
events, watching and services. Runs, queued renders, stale results and
pending requests are a pure state machine (Execution) whose transitions are
tested without a window or a process, including the queued render after a
failed run that previously had no automated test.

An inspector geometry reply that arrived after another workplane or step
was selected no longer draws its overlay: the pending request remembers
its workplane and entry.
Undo and redo are in the Edit menu (Ctrl+Z, Ctrl+Y, Ctrl+Shift+Z) and use
an editor history of its own. TextEdit's history grouped edits by a second
of stillness, counted caret moves as steps and lived in egui memory beyond
the document, so undo after opening another file brought back the previous
file's text. Typing is now grouped while it continues at the caret, and an
indent, a comment, an accepted completion, a Replace all or a reload is one
step each.

The search bar gains whole-word matching and a replace row (Ctrl+H), and
Ctrl+G opens a Go to line bar that accepts line:column. Searching now starts
from the caret: the first Enter or F3 after typing a query used to skip the
first match. The bars take their Enter and Escape keys, so the editor never
receives them as well, and Replace remembers the match it selected because
an unfocused TextEdit collapses its selection.

Completions are offered after the first characters of a name, three by
default and configurable, outside comments and strings and without status
messages. Resting the pointer on a name shows its signatures and docstring,
or the type of a variable, through a new describe request served by jedi.

Breakpoints are remembered per file in the settings, with the text of their
lines, and return to the line with that text when the file changed in the
meantime. The property sheet of the Objects panel shows the geometry type,
area or length, radius and center of the selected face, edge or vertex.

When Python cannot start, including the Windows Store placeholder, or
CadQuery cannot be imported, the dialog says which, lists the interpreters
found on the computer and offers Choose interpreter and the installation
guide, which is also in the Help menu.

The logging test no longer fails when other tests log at the same time.
The ninth example builds a welded workbench of 20 x 20 mm square tube as an
assembly, with its cutting plan and a summary of the tube used printed to
the Log panel.

The example smoke tests now run every bundled example; the documentation
states that only examples 01 to 08 are unchanged pre-rewrite compatibility
fixtures, and counts nine examples.
Bundle a pinned Python 3.12, CadQuery 2.8, OCP 7.9 and Jedi runtime in native Windows, Linux and macOS installers. Add application shortcuts, non-destructive Python file associations, bundled-runtime discovery and clean Windows uninstall integration.

Build Apple Silicon and Intel macOS packages separately, smoke-test every installed runtime before publication, preserve platform-specific lockfiles and publish SHA-256 checksums from version tags. Synchronize the Rust and worker versions, compatibility contract, roadmap and installation documentation.
Use a space-free per-user Library prefix required by constructor's relocatable PKG bootstrap. Align the macOS smoke test with the installed runtime and add a packaging regression check for invalid PKG paths.
Release builds use fat LTO with one codegen unit (opt-level stays 3): the
Windows executable goes from 21.5 to 17.9 MB. The icon stores its 64 to
192 px images as PNG with identical pixels (308 KB to 50 KB) and is
embedded once, through a shared static, instead of twice.

The installers exclude the Boost, Eigen and TBB headers, CMake files and
import libraries that the vtk metapackage brings for compiling against
VTK. Nothing installed links Boost's DLLs; the runtime libraries stay.
Each installer is about 19 MB smaller, an installation about 230 MB and
17,000 files smaller.
- Warn once per repeated object name in show_object() and debug()
- Add CadQuery snippet library in Edit menu and Command Palette
- Add file search filter in the Project panel
- Add object and subassembly name filter in the Objects panel
- Add line/block duplication and up/down movement in the editor
- Add configurable 3D selection mode shortcuts
- Allow restoring individual or all modified script parameters
- Allow copying measurements as structured Text, JSON, or CSV
- Add reverse history search (Ctrl+R) in the Python console
- Allow pinning favorite commands in the Command Palette
- Add resolution presets and aspect-ratio display in Screenshot dialog
- Allow copying rendered screenshots directly to the system clipboard
The grid lies on the XY plane at the lowest point of the shown model, so
the model stands on it, instead of at z = 0 following the camera. Its
spacing follows the size of the model in 1, 2 or 5 steps instead of the
zoom, with a major line every five, and its lines keep round coordinates
while the view turns and zooms. The floor grows in doublings to reach the
edge of the view and fades into the distance; lines too close together on
screen fade out instead of blurring.

The X, Y and Z axes are short positive guides from the origin, solid where
visible and dashed where the model hides them, with a dot marking the
origin, so a part built around the origin no longer hides them.
An orbit begun on the model turns around the point picked under the
pointer, which keeps its place and depth on screen, also in a panned view;
begun on empty space it turns around the middle of the view as before. Both
turntable and trackball orbits do this. The point is picked after any
camera transition has ended, so a drag started during one turns around what
is under the pointer in the view it moves.

Right drag now orbits like left drag instead of zooming; the wheel, pinch
and Shift + left drag still zoom. The compatibility matrix marks the mouse
bindings as Changed and records the decision.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant