Skip to content

Commit b00811d

Browse files
andyclaude
andcommitted
Merge origin/main into wip/windows-msi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PFPK14wBcLD1s4dYUowpP4
2 parents 4080c38 + 32414b6 commit b00811d

52 files changed

Lines changed: 2361 additions & 283 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CMakeLists.txt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,6 +1887,16 @@ if(PYTHON3_EXECUTABLE)
18871887
COMMAND ${PYTHON3_EXECUTABLE}
18881888
${CMAKE_CURRENT_SOURCE_DIR}/tests/look/contrast.py)
18891889

1890+
# Text in a window that has lost focus keeps its contrast (GTK :backdrop),
1891+
# and disabled text still looks disabled (#210). Compiles
1892+
# tests/look/backdrop.C against this build; SKIPs without Xvfb or PIL.
1893+
add_test(NAME look_backdrop
1894+
COMMAND ${PYTHON3_EXECUTABLE}
1895+
${CMAKE_CURRENT_SOURCE_DIR}/tests/look/login_backdrop.py
1896+
backdrop ${CMAKE_BINARY_DIR}/look-backdrop
1897+
--libdir ${CMAKE_BINARY_DIR} --tag after)
1898+
set_tests_properties(look_backdrop PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 300)
1899+
18901900
# The offscreen renderer's .rgb output against ImageMagick and PIL (#231).
18911901
# Needs `ninja render-rgb`, Xvfb, convert and PIL; SKIPs without them.
18921902
add_test(NAME look_imageconv
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
type: map
3+
title: "An atom's shell with a non-bonding orbital is drawn at that orbital (#140)"
4+
area: mo-diagram
5+
section: "The MO correlation diagram (#132)"
6+
paths: ["src/tdat/chemistry/MoDiagram.C", "include/tdat/MoDiagram.H"]
7+
issues: [140, 132]
8+
---
9+
`placeFragments()` draws every component of an atom's shell on one row.
10+
The row is the mean of the orbitals the components became, except when
11+
the shell has a non-bonding orbital: then the row is at that orbital's
12+
energy, so it sits level with its parent (water's 1b1 on O 2p).
13+
14+
Non-bonding means `nonbondingShellFraction() >= NONBONDING_SHELL_SHARE`
15+
(0.99): the share on that one shell over the share on everything the
16+
diagram draws. Polarisation functions of column atoms (O d, H p) are
17+
left out of the denominator, because without them symmetry-forbidden
18+
mixing gives exactly 1 in any basis; atoms in neither column count
19+
against. Because of that, every b1 orbital of water is "pure O p",
20+
including diffuse virtuals, so only the lowest qualifying orbital per
21+
irrep pins the row.
22+
23+
A lone pair from s/p mixing (water 3a1, NH3 3a1) is not single-shell
24+
(f = 0.76 and 0.86 in ORCA def2-SVP; 0.93 and 0.94 in extended Huckel)
25+
and is drawn in the usual way, connected to s, p and the ligand set, as
26+
in Albright-Burdett-Whangbo (2013) Figs. 7.2 and 9.4. classify() tags it
27+
MIXED, not nb, when its irrep is on both sides and it holds at least
28+
`LINK_SHARE` (0.05) of each column's drawn shells; with a composition,
29+
connect() draws every side at or above `LINK_SHARE`, nb or not. Before
30+
this, water 3a1 was tagged nb by the count and drawn to O only.
31+
32+
The panel prints the fraction per level and shell as `[MOSHELL]` under
33+
`ECCE_DEBUG_MOSYM`. The canvas opens the molecular column out too (it
34+
calls `columnGeometry()` with spreading on for it), so a pinned row and
35+
its orbital can still be drawn a few pixels apart when other molecular
36+
levels crowd it: 4 px in extended Huckel water at 1400x950.
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
---
22
type: map
3-
title: "Fragment columns may be opened out for legibility; the molecular column never is"
3+
title: "Fragment columns may be opened out for legibility, and so is the molecular column"
44
area: mo-diagram
55
section: "The MO correlation diagram (#132)"
66
paths: ["src/apps/builder/MoDiagramCanvas.H"]
77
issues: [132]
88
---
9-
Fragment columns may be opened out for legibility; the molecular
10-
column never is, because its energies are the measurement. Whatever
9+
Fragment columns may be opened out for legibility, and so is the
10+
molecular column (the canvas spreads it too), so a crowded molecular
11+
level can be drawn a few pixels off its energy. Whatever
1112
computes a correlation line's endpoint must use the same `columnGeometry()`
1213
answer the levels are drawn with, or the lines point at nothing.

‎docs/claude/mo-diagram/index.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ MOPAC → spec) is the bridge it would reuse.
3030
- [Look at `tools/modiagram/render`'s output before believing any layout claim](look-at-tools-modiagram-renders-output-before.md)
3131
- [Without `symops` every fragment column is empty and no line is drawn](empty-and-no-line-is-drawn.md)
3232
- [A fragment column is not sorted by energy](not-sorted-by-energy.md)
33-
- [Fragment columns may be opened out for legibility; the molecular column never is](fragment-columns-may-be-opened-out-for.md)
33+
- [Fragment columns may be opened out for legibility, and so is the molecular column](fragment-columns-may-be-opened-out-for.md)
3434
- [MO composition: MoAoBasis, Mulliken by default (#161)](mo-composition-is-moaobasis-mulliken-by-default.md)
35+
- [An atom's shell with a non-bonding orbital is drawn at that orbital (#140)](a-shell-with-a-non-bonding-orbital-is-drawn-at-it.md)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
type: pitfall
3+
title: "GTK :backdrop fades every label to ~2.8:1; ewxApp::applyBackdropStyle() undoes it, and headless runs need ECCE_TEST_BACKDROP"
4+
area: wx-viewer
5+
paths: ["src/wxgui/ewxClasses/ewxApp.C", "tests/look/login_backdrop.py", "tests/look/backdrop.C", "tests/look/organizer_backdrop.py"]
6+
issues: [210]
7+
---
8+
Adwaita draws text in an unfocused toplevel (`:backdrop`) in a 50 % mix of
9+
text and background: a normal label drops from 11.6:1 to 2.8:1, so with
10+
several ECCE windows open the unfocused ones look disabled.
11+
`ewxApp::applyBackdropStyle()` (called from `OnInit`) adds an
12+
application-priority CSS sheet giving `label`, `entry` and `.view` the
13+
theme's normal colour in `:backdrop` and `@insensitive_fg_color` for
14+
`:disabled:backdrop`. GTK3 CSS has no `:not()`, so the disabled case is its
15+
own rule. `ECCE_BACKDROP=theme` switches the fix off.
16+
- Without a window manager (Xvfb) GTK never enters `:backdrop`: every
17+
window stays active whatever `xdotool windowfocus` does. Tests set the
18+
flag themselves (`backdrop.C`) or run the app with `ECCE_TEST_BACKDROP=1`,
19+
which re-asserts it on every top-level window each 200 ms.
20+
- `ctest -R look_backdrop` measures the ink contrast of a window in
21+
`:backdrop` against an active one; it fails below 85 %, or if a
22+
disabled label is no dimmer than a normal one.

‎docs/claude/wx-viewer/index.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Read this before changing any dialog, panel, sizer, grid, ewx control or the Ope
1111

1212
### wxWidgets 3.2/GTK3, the 3D viewer, and C++ pitfalls
1313

14+
- [Every plot is an `ewxPlotCtrl` styled by `PlotStyle.H`, the palette the vibrational spectrum canvas uses; `tests/plots/capture.py` pictures them before and after a change](plots-share-the-spectrum-style.md)
1415
- [`ComputeMoCmd` caches a grid before computing it; a failed Compute must remove it (#239)](mo-grid-cache-keeps-a-failed-compute.md)
1516
- [The redraw sensor must not be drained inside `schedule()`: `SoWxEventHandler::setUpCallbacks()` only wakes idle](open-inventors-redraw-sensor-is-a-one.md)
1617
- [`ECCE_USE_COIN` (default ON) builds the viewer against Coin3D; `=OFF` keeps the vendored core for one release](ecce-use-coin-builds-the-viewer-against-coin3d.md)
@@ -31,6 +32,7 @@ Read this before changing any dialog, panel, sizer, grid, ewx control or the Ope
3132
- [`wxFIXED_MINSIZE`](wxfixed-minsize.md)
3233
- [Every drop-down is at least as wide as its widest entry](drop-down-min-width.md)
3334
- [Run-state colours: submitted yellow, created sky blue, pale fills outlined in light themes; checked by tests/look/contrast.py](run-state-colours.md)
35+
- [GTK :backdrop fades every label to ~2.8:1; `ewxApp::applyBackdropStyle()` undoes it; headless runs need `ECCE_TEST_BACKDROP` (#210)](gtk-backdrop-fades-labels-and-has-no-headless-trigger.md)
3436
- [`std::map`/`unordered_set` iterator invalidation](std-map-unordered-set-iterator-invalidation.md)
3537
- [Uncontrolled format strings](uncontrolled-format-strings.md)
3638
- [`EcceException::what()` returns storage that lives as long as the exception](ecceexception-what.md)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
type: map
3+
title: "Every plot is an `ewxPlotCtrl` styled by `PlotStyle.H`, the palette the vibrational spectrum canvas uses; `tests/plots/capture.py` pictures them before and after a change"
4+
area: wx-viewer
5+
section: "wxWidgets 3.2/GTK3, the 3D viewer, and C++ pitfalls"
6+
paths: ["include/wxgui/PlotStyle.H", "include/wxgui/ewxPlotCtrl.H", "src/wxgui/ewxClasses/ewxPlotCtrl.C", "src/wxgui/wxplotctrl", "src/apps/builder/BuilderPlotShot.C", "tests/plots"]
7+
issues: [210, 214]
8+
---
9+
`SpectrumCanvas` (#214) is specific to spectra; what is shared is its
10+
colours. `PlotPalette` (was `SpectrumPalette`, still a typedef) lives in
11+
`include/wxgui/PlotStyle.H`, header-only so `tools/spectrum/render` needs
12+
no wx widgets. Every other plot is an `ewxPlotCtrl` (the old wxPlotCtrl
13+
under `src/wxgui/wxplotctrl`); `ewxPlotCtrl::ApplyStyle()` runs from
14+
`Create()` and gives it the system window colours, Okabe-Ito curves
15+
(`SeriesPen(i)`), small tick labels, short outside ticks, a pale grid, and
16+
a readout box for the point under the pointer. Drag zooms a rectangle,
17+
double click on empty plot fits everything, click still selects the point.
18+
19+
- **A new plot panel needs nothing**; if it sets curve pens itself, use
20+
`p_plotCtrl->SeriesPen(n)` and `GetPalette()`, not `wxBLUE`/`wxCYAN`.
21+
A curve whose pens still equal the library default is restyled in
22+
`StyleNewCurve()`; one with its own pens keeps them.
23+
- **`SetCorrectTicks(false)`.** The library's tick "correction" moved the
24+
view to put ticks on round numbers and cut data off after a resize (the
25+
Geometry Trace curve left the plot). Ticks now go where they fall.
26+
- **The y-axis window is as wide as its labels** (`CalcYAxisTickPositions`
27+
re-lays out when the width changes); it used to be a fixed guess at
28+
`-5.5e+555`. The window now ends at the plot frame so ticks reach it.
29+
- **`DrawAreaOverlay()` and `StyleNewCurve()`** are the two new virtual
30+
hooks in `wxPlotCtrl`; the readout is drawn in the first.
31+
- **Rate-constant, equilibrium, MO-level and step plots all go through
32+
this**; `PlotMeta3D`'s plot is commented out and draws nothing.
33+
- **Pictures.** `tests/plots/capture.py --png DIR --tag before|after`
34+
floats each plot panel in the real Builder at two sizes (scene command
35+
`plotshot`) and saves the pixels; `tests/plots/sheet.py DIR` makes the
36+
contact sheets. One Builder per panel and size: resizing a floated
37+
panel left stale pixels in the plot's native windows.
38+
- **`gtpick <step|mid|last>`** (scene script) clicks the plot where a step
39+
is drawn and checks the molecule is the trace's; it is in
40+
`tests/apps/geomtrace_stress.py`.

‎docs/claude/wx-viewer/vibrational-spectrum-canvas-214.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ from the raw fixtures by `oracle.py` (no ECCE parser involved);
3030
- **The envelope is area-normalised**, so its y values are
3131
intensity per cm-1 and sit on a second, right-hand axis next to the
3232
stick axis; they are not comparable with the stick heights.
33+
- **The palette is shared.** `SpectrumPalette` is a typedef of
34+
`PlotPalette` in `include/wxgui/PlotStyle.H`, which every other plot
35+
uses too (see the plots entry); the spectrum paints byte-identically
36+
with it.
3337
- **Hooks:** `ECCE_SPECTRUM_DUMP=<path>` (with `ECCE_SPECTRUM_CLICK=<mode>`
3438
and `ECCE_EXIT_AFTER_DUMP=1`) writes what the panel's canvas holds and
3539
paints it to `<path>.png`.

‎include/tdat/MoDiagram.H‎

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,13 @@ struct MoLevel
136136
*/
137137
double tabulated;
138138

139-
/** What the level is: see MoDiagram::classify(). */
140-
enum Character { UNKNOWN, BONDING, NONBONDING, ANTIBONDING };
139+
/**
140+
* What the level is: see MoDiagram::classify(). MIXED is the middle
141+
* orbital of a three-orbital interaction (water and ammonia 3a1):
142+
* neither bonding nor antibonding by count, but with a real share on
143+
* both sides, so it is drawn to both and not called non-bonding.
144+
*/
145+
enum Character { UNKNOWN, BONDING, NONBONDING, ANTIBONDING, MIXED };
141146
Character character;
142147

143148
/**
@@ -390,12 +395,47 @@ class MoDiagram
390395
*
391396
* The tabulated energy is kept as the annotation, where it belongs.
392397
*
398+
* Exception (#140): an atom's shell that has a non-bonding orbital
399+
* in the molecular column (nonbondingShellFraction() at least
400+
* NONBONDING_SHELL_SHARE) is drawn at that orbital's energy, so the
401+
* orbital lies level with its parent.
402+
*
393403
* @param connections from connect(), which must therefore be
394404
* called first
405+
* @return the number of shells drawn at a non-bonding orbital
395406
*/
396-
static void placeFragments(const MoColumn& centre,
397-
MoColumn& left, MoColumn& right,
398-
const vector<MoConnection>& connections);
407+
static int placeFragments(const MoColumn& centre,
408+
MoColumn& left, MoColumn& right,
409+
const vector<MoConnection>& connections);
410+
411+
/**
412+
* A molecular orbital whose nonbondingShellFraction() for one atom
413+
* shell is at least this is that shell's non-bonding orbital (#140).
414+
*
415+
* Where symmetry forbids mixing (water's out-of-plane 2p) the
416+
* fraction is 1 to the six decimals printed, in every basis tried.
417+
* Allowed mixing, even when slight, left 0.055 or more elsewhere
418+
* (extended Huckel NH3 and water 2a1: 0.055, 0.07; SCF water and
419+
* NH3 3a1: 0.24, 0.14; nothing in CO or Cr(CO)6 above 0.88). 0.99
420+
* keeps a factor of five from the nearest mixed orbital.
421+
*/
422+
static constexpr double NONBONDING_SHELL_SHARE = 0.99;
423+
424+
/**
425+
* How much of a molecular orbital is one atom shell, as a fraction
426+
* of the part of it the diagram draws.
427+
*
428+
* The denominator is every shell of both columns plus any atom in
429+
* neither column; shells of a column atom the diagram does not draw
430+
* (polarisation functions: oxygen d, hydrogen p) are left out,
431+
* since the diagram cannot show mixing with them. -1 when the
432+
* level carries no per-shell composition.
433+
*
434+
* @param onLeft the shell is in the left column, else the right
435+
* @param slot index into that column's shellKeys
436+
*/
437+
static double nonbondingShellFraction(const MoLevel& mo, bool onLeft,
438+
int slot);
399439

400440
/**
401441
* Fold levels below a cutoff into the column's hidden band.
@@ -706,9 +746,23 @@ class MoDiagram
706746
const vector<MoLevel>& centre,
707747
const vector<MoLevel>& right,
708748
vector<MoConnection>& connections,
709-
double cutoff = 0.05,
749+
double cutoff = LINK_SHARE,
710750
bool bothSidesQualify = false);
711751

752+
/**
753+
* The least share of a molecular orbital, on the shells a column
754+
* draws, for which connect() draws a line to that column; classify()
755+
* uses the same number to decide that a level touches both sides.
756+
*/
757+
static constexpr double LINK_SHARE = 0.05;
758+
759+
/**
760+
* A level's share on the shells one column draws: the sum of its
761+
* per-shell shares, or the column's whole share where there is no
762+
* per-shell split, -1 where neither was computed.
763+
*/
764+
static double drawnShare(const MoLevel& level, bool onLeft);
765+
712766
/**
713767
* Fold levels ABOVE a cutoff, except ones a caller has protected.
714768
*

‎include/tdat/MoSpec.H‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,19 @@ static bool buildModel(const char* tables, const char* molecule,
341341
MoDiagram::classify(left.levels, centre.levels, right.levels,
342342
left.fromHalves);
343343
MoDiagram::connect(left.levels, centre.levels, right.levels, links);
344-
MoDiagram::placeFragments(centre, left, right, links);
344+
const int pinnedShells =
345+
MoDiagram::placeFragments(centre, left, right, links);
345346
MoDiagram::classifyByEnergy(left.levels, centre.levels,
346347
right.levels, links);
348+
// Same sentence as MoDiagramPanel::build(), kept in step.
349+
if (pinnedShells > 0) {
350+
char text[200];
351+
snprintf(text, sizeof(text), "A shell with a non-bonding orbital "
352+
"(one that is at least %d%% that shell, of the orbitals "
353+
"drawn) is drawn at that orbital's energy instead.",
354+
(int)lround(100.0*MoDiagram::NONBONDING_SHELL_SHARE));
355+
note = note.empty() ? string(text) : (note + " " + text);
356+
}
347357
}
348358

349359

0 commit comments

Comments
 (0)