Conversation
Lay the groundwork for saving draw and the diagrams as svg and pdf files. The OpenGL2 backend rasterizes 3D geometry in GL2PS exports, so resolve visibility in software instead: split and order the opaque fills with a BSP painter, clip the strokes against every nearer triangle, and write the result through Matplotlib, embedding the vendored font in svg text as the results plots already do. Keep the module unwired for now, so the diagrams and draw can adopt it in their own commits. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Let the vector export embed the vendored font without importing the plotting module, which imports the rendering module. The rendering module will import the vector export once the diagrams adopt it, so leaving the function in place would close an import cycle. Keep it beside the font it embeds, in a module that imports none of the three. Name the font family through the fonts module in the moved SVG fixture, so the fixture follows the font if it ever changes. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Give papers and slides diagrams that stay sharp at any size and keep their labels as selectable text. Record each diagram as it is drawn and export it through the core vector module, rather than rebuilding the scene from VTK's actors, since recording keeps what each helper means explicit. Read the camera, the window size, and the labels after every render, and export from the last reading, so the dragged and edited labels survive even when closing the window destroys it. Take each label's box from VTK and center its text in it, which places it as VTK does without modeling VTK's padding. Paint coincident strokes so the first one recorded shows, since VTK's depth test keeps the first of two equally deep fragments. Embed every font face an svg's text uses, so math labels carry their STIX faces along with Liberation Mono. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fix diagrams that painted every panel edge over every vortex line, which buried vorticity arrows that sit in front of the panels. Painting strokes in the reverse of the order they were recorded was based on a wrong reading of VTK: a line VTK shows on top wins on depth, and of two equally deep lines VTK keeps the later one. Let every stroke and every occluder outline hide the strokes behind it across its own width, so the nearer of two crossing strokes shows wherever they cross, and a stroke behind an arrow tip can no longer paint over the tip's outline. Widen each hiding ribbon by half the hidden stroke's width, so a clipped stroke's round end stops short of the stroke in front. Exempt strokes that share an end, so a polyline's segments don't clip each other where they meet, and record each collocation point's cross as four half arms from the point for the same reason. Look the triangles up through a grid on screen, since the ribbons add two triangles per stroke and a diagram can hold tens of thousands of strokes. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Let a diagram saved as an svg or pdf file be sized for a paper, the way plot_results_versus_time's figures can be. The page takes the requested size in inches, and the view framed in the window scales to fill it, while the labels and lines keep absolute sizes in points, so a figure printed at a column's width gets the font size and line weight the publisher asks for. Open the window at the page's aspect ratio, so the framing chosen on screen is the framing saved. Leave the window and WebP output untouched, since they are screenshots of what VTK draws. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Format dispatch, overlapping-occluder visibility, and unconditional scene-recording issues remain unresolved.
Review effort: Balanced
Findings: 2
Open (4)
What changed in this PR
Adds SVG/PDF diagram export with selectable embedded-font text, configurable figure styling, and software-resolved visibility, advancing issue #266.
Changes:
- Records rendered diagram geometry into vector scenes with BSP and hidden-line processing.
- Extends all diagram APIs with vector formats and figure styling parameters.
- Moves SVG font embedding into
_fonts.pyand adds focused tests.
| File | Description |
|---|---|
CLAUDE.md |
Documents vector export architecture. |
pterasoftware/_core.py |
Forwards new diagram options. |
pterasoftware/_fonts.py |
Embeds all used SVG font faces. |
pterasoftware/_output_plotting.py |
Uses shared font embedding. |
pterasoftware/_output_rendering.py |
Records scenes and dispatches exports. |
pterasoftware/_vector_export.py |
Implements vector projection and visibility. |
pterasoftware/geometry/airfoil.py |
Adds vector airfoil diagrams. |
pterasoftware/geometry/airplane.py |
Adds vector airplane diagrams. |
pterasoftware/geometry/wing.py |
Adds vector wing diagrams. |
pterasoftware/geometry/wing_cross_section.py |
Adds vector cross-section diagrams. |
pterasoftware/problems.py |
Adds vector problem diagrams. |
pterasoftware/steady_horseshoe_vortex_lattice_method.py |
Exports horseshoe-solver diagrams. |
pterasoftware/steady_ring_vortex_lattice_method.py |
Exports ring-solver diagrams. |
pterasoftware/unsteady_ring_vortex_lattice_method.py |
Exports unsteady-solver diagrams. |
tests/unit/fixtures/fonts_fixtures.py |
Adds SVG font fixtures. |
tests/unit/fixtures/output_plotting_fixtures.py |
Removes relocated SVG fixture. |
tests/unit/fixtures/vector_export_fixtures.py |
Adds vector geometry fixtures. |
tests/unit/test_fonts.py |
Tests multi-face font embedding. |
tests/unit/test_output_plotting.py |
Removes relocated embedding tests. |
tests/unit/test_output_rendering.py |
Tests recording and export state. |
tests/unit/test_vector_export.py |
Tests vector algorithms and output. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if entries: | ||
| # Paint the farthest occluder first. Each occluder's fill is its first path, | ||
| # and the outlines of its faces follow it unfilled. | ||
| entries.sort(key=lambda entry: -entry[0]) |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #328 +/- ##
==========================================
- Coverage 96.41% 96.24% -0.17%
==========================================
Files 48 49 +1
Lines 10642 11281 +639
==========================================
+ Hits 10260 10857 +597
- Misses 382 424 +42 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
This PR lets every
diagrammethod save its diagram as an svg or pdf file, chosen by the path's suffix, alongside the existing WebP output. The scene is recorded in a new private module,_vector_export.py, as the rendering helpers draw it, then exported through a parallel camera that matches the window's final view. Visibility is resolved in software: opaque fills are split and ordered by a BSP painter, and strokes are clipped against every nearer triangle and against each other, so the file shows what the window showed. Labels are read from VTK after every render, so dragged and edited labels survive, and are written as selectable text with their fonts embedded. The diagram methods also gainfigure_size_in,font_size,text_color, andline_width, which size a vector diagram for a paper the wayplot_results_versus_timesizes its figures.All new parameters default to the current behavior, so existing calls are unaffected, and the WebP output is unchanged.
output.drawis deliberately out of scope here and will adopt the same module in a follow-up PR, so the module's translucent polygon and screen polygon support, which onlydrawneeds, is currently unused.Motivation
Diagrams are mainly useful as figures in papers, slides, and documentation, where a WebP screenshot blurs when scaled and its labels can't be selected or searched. VTK's own vector export (GL2PS) rasterizes 3D geometry with the OpenGL2 backend, so it can't produce true vector output of these scenes. Recording the scene and resolving visibility in software gives sharp output at any size whose labels stay text. The figure parameters exist because a vector diagram is otherwise sized by the window, so its fonts and lines have no predictable physical size on a printed page.
Relevant Issues
Part of #266.
Changes
_vector_export.pywithVectorCamera(a parallel camera that projects diagram axes to display coordinates, plusfit_to_page),VectorText,VectorLayer, andVectorScene. A layer records opaque quadrilaterals, polylines, convex occluders (the arrow tips), dots, and translucent polygons, andVectorScene.savewrites the scene through Matplotlib with TrueType fonts in pdf files and embedded fonts in svg files.get_paint_order), adapted from csg.js, which uses explicit stacks and samples candidate splitters, so it orders crossing fills exactly and can't hit the recursion limit.get_visible_intervals). Each stroke and each occluder outline hides the strokes behind it across its own width, so the nearer of two crossing strokes shows, matching VTK's depth test. Strokes that share an end are exempt, and triangles are looked up through a grid on screen so diagrams with tens of thousands of strokes stay fast._output_plotting.pyto_fonts.pyasembed_fonts_in_svg, which avoids an import cycle. It now embeds every face the text uses, including the STIX faces Matplotlib writes math in, each subset to its glyphs.plot_results_versus_timeuses the moved function.layerparameter throughadd_axes_and_points,_add_arrow_tips,add_airfoil,add_airfoil_lines,add_airfoils,add_panels,add_vortices, andadd_steady_problemin_output_rendering.py. Each helper records what it draws as it adds the PyVista actors, and each collocation point's cross is recorded as four half arms so its strokes don't clip each other.show_diagramto read the camera, window size, and labels after every render (_get_diagram_export_state) and to export from the last reading when the path ends with".svg"or".pdf". Each label is drawn in its VTK background box, with math in STIX and other text in Liberation Mono.pathonAirfoil.diagram,WingCrossSection.diagram,Wing.diagram,Airplane.diagram,SteadyProblem.diagram, the unsteady problems'diagram, and the three solvers'diagrammethods to accept".svg"and".pdf".figure_size_in,font_size,text_color, andline_widthto the same methods, validated by_output_rendering.validate_diagram_figure_parameters:figure_size_inopens the window at the page's aspect ratio and scales the view to fill the page, while text and line widths stay in points.font_sizesets every label's size, including math labels.text_colorcolors the labels.line_widthsets the width of the panel edges, airfoil lines, and vortices, and scales the other lines with it.tests/unit/test_vector_export.pyand its fixtures, covering projection, page fitting, paint order (including a test that samples inside every fragment's footprint), stroke clipping and depth ties, line width scaling, and svg and pdf writing. Addedtests/unit/test_fonts.pyfor the moved font embedding, and extendedtests/unit/test_output_rendering.pyfor layer recording, export state reading, page fitting, label sizing and coloring, and parameter validation._vector_export.pyto the architecture list inCLAUDE.md, and updated the_fonts.pyentry.Dependency Updates
None.
Change Magnitude
Moderate: Medium-sized change that adds or modifies a feature without large-scale impact.
Checklist (check each item when completed or not applicable)
mainand is up to date with the upstreammainbranch.--in-place --black). See the style guide for type hints and docstrings for more details.pterasoftwarepackage use type hints. See the style guide for type hints and docstrings for more details.testspackage.testspackage.ascii-only,pre-commit-hooks, andzizmorGitHub actions.lintjob of theCIGitHub action.testjobs of theCIGitHub action.Assisted-by: Claude Opus 5.5