pypsbuilder 2.6.6__tar.gz → 2.6.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.github/workflows/testing.yml +4 -1
  2. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.readthedocs.yaml +1 -0
  3. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/CHANGELOG.md +17 -0
  4. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/PKG-INFO +1 -1
  5. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/psbuilders-tutorial.rst +43 -9
  6. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite/tc-avgpelite.txt +16 -17
  7. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/__init__.py +1 -1
  8. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/psbuilders.py +213 -28
  9. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tcapi.py +12 -5
  10. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tcinit.py +50 -18
  11. pypsbuilder-2.6.8/pypsbuilder/tests/test_psbuilders.py +432 -0
  12. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/test_tcapi.py +11 -9
  13. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/test_tcinit.py +87 -7
  14. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/ptbuilder.ui +12 -0
  15. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/pxbuilder.ui +12 -0
  16. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/txbuilder.ui +12 -0
  17. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_addinv.py +1 -1
  18. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_adduni.py +1 -1
  19. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_ptbuilder.py +8 -0
  20. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_pxbuilder.py +8 -0
  21. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_txbuilder.py +8 -0
  22. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui_uniguess.py +1 -1
  23. pypsbuilder-2.6.6/pypsbuilder/tests/test_psbuilders.py +0 -200
  24. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.github/dependabot.yml +0 -0
  25. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.github/workflows/release.yml +0 -0
  26. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.github/workflows/testrelease.yml +0 -0
  27. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.gitignore +0 -0
  28. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.pre-commit-config.yaml +0 -0
  29. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/.zenodo.json +0 -0
  30. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/AUTHORS.md +0 -0
  31. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/CONTRIBUTING.md +0 -0
  32. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/LICENSE +0 -0
  33. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/README.md +0 -0
  34. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/codecov.yml +0 -0
  35. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/_static/theme_overrides.css +0 -0
  36. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.Dogmin.rst +0 -0
  37. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.InvPoint.rst +0 -0
  38. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.PTsection.rst +0 -0
  39. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.PXsection.rst +0 -0
  40. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.TCResult.rst +0 -0
  41. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.TCResultSet.rst +0 -0
  42. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.TXsection.rst +0 -0
  43. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psclasses.UniLine.rst +0 -0
  44. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psexplorer.PTPS.rst +0 -0
  45. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psexplorer.PXPS.rst +0 -0
  46. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.psexplorer.TXPS.rst +0 -0
  47. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.tcapi.TC34API.rst +0 -0
  48. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api/pypsbuilder.tcapi.TC35API.rst +0 -0
  49. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/api.rst +0 -0
  50. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/authors.rst +0 -0
  51. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/command-line.rst +0 -0
  52. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/conf.py +0 -0
  53. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/create_environment.png +0 -0
  54. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/crossing.png +0 -0
  55. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/delta.png +0 -0
  56. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/install_locally.png +0 -0
  57. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/invsearch.png +0 -0
  58. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/isopleths_1.png +0 -0
  59. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/isopleths_2.png +0 -0
  60. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/modes.png +0 -0
  61. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_areas.png +0 -0
  62. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_dogmin.png +0 -0
  63. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_finished.png +0 -0
  64. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_highlight.png +0 -0
  65. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_init.png +0 -0
  66. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_inv.png +0 -0
  67. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_modes.png +0 -0
  68. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psbuilder_uni.png +0 -0
  69. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psiso_alm.png +0 -0
  70. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psiso_labelkey.png +0 -0
  71. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psiso_mode.png +0 -0
  72. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psiso_other.png +0 -0
  73. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/psshow_out.png +0 -0
  74. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/ptpath.png +0 -0
  75. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/show_data.png +0 -0
  76. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/show_grid.png +0 -0
  77. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/show_label.png +0 -0
  78. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/show_out.png +0 -0
  79. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/show_plain.png +0 -0
  80. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/images/status.png +0 -0
  81. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/index.rst +0 -0
  82. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/psexplorer-tutorial.rst +0 -0
  83. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/docs/readme.rst +0 -0
  84. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/environment.yml +0 -0
  85. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite/tc-ds62.txt +0 -0
  86. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite/tc-mp50MnNCKFMASHTO.txt +0 -0
  87. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite/tc-prefs.txt +0 -0
  88. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite_34/tc-6axmn.txt +0 -0
  89. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite_34/tc-avgpelite.txt +0 -0
  90. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite_34/tc-ds62.txt +0 -0
  91. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/avgpelite_34/tc-prefs.txt +0 -0
  92. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv1-ic.txt +0 -0
  93. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv1-log.txt +0 -0
  94. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv2-ic.txt +0 -0
  95. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv2-log.txt +0 -0
  96. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv3-ic.txt +0 -0
  97. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/inv3-log.txt +0 -0
  98. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni1-ic.txt +0 -0
  99. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni1-log.txt +0 -0
  100. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni2-ic.txt +0 -0
  101. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni2-log.txt +0 -0
  102. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni3-ic.txt +0 -0
  103. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/examples/outputs/uni3-log.txt +0 -0
  104. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/notebooks/pypsbuilder.ipynb +0 -0
  105. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pyproject.toml +0 -0
  106. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/images/ptbuilder.png +0 -0
  107. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/images/pxbuilder.png +0 -0
  108. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/images/pypsbuilder.png +0 -0
  109. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/images/txbuilder.png +0 -0
  110. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/psclasses.py +0 -0
  111. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/psexplorer.py +0 -0
  112. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/__init__.py +0 -0
  113. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/test_pclasses.py +0 -0
  114. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/test_psclasses.py +0 -0
  115. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/tests/test_psexplorer.py +0 -0
  116. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/addinv.ui +0 -0
  117. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/adduni.ui +0 -0
  118. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/compile_ui.sh +0 -0
  119. {pypsbuilder-2.6.6 → pypsbuilder-2.6.8}/pypsbuilder/ui/uniguess.ui +0 -0
@@ -9,9 +9,11 @@ on:
9
9
  jobs:
10
10
  build:
11
11
  name: Package testing
12
- runs-on: ubuntu-latest
12
+ runs-on: ${{ matrix.os }}
13
13
  strategy:
14
+ fail-fast: false
14
15
  matrix:
16
+ os: [ubuntu-latest, windows-latest, macos-latest]
15
17
  python-version: ["3.12", "3.13", "3.14"]
16
18
 
17
19
  env:
@@ -28,6 +30,7 @@ jobs:
28
30
  enable-cache: true
29
31
 
30
32
  - name: Install Qt runtime dependencies
33
+ if: runner.os == 'Linux'
31
34
  run: |
32
35
  sudo apt-get update
33
36
  sudo apt-get install -y libegl1 libxkbcommon0
@@ -22,6 +22,7 @@ build:
22
22
  python:
23
23
  install:
24
24
  - method: uv
25
+ command: sync
25
26
  path: .
26
27
  extras:
27
28
  - docs
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
  All notable pypsbuilder changes.
3
3
 
4
+ ## [2.6.8] - 2026-09-05
5
+ ### Added
6
+ - `Export JSON` menu item added to ptbuilder/txbuilder/pxbuilder File menu, exporting invariant points and univariant lines in a schema readable back in by `Import from JSON`
7
+ ### Fixed
8
+ - TC35API rejects `omit` scriptfile script as unrecognised (discontinued in THERMOCALC 3.51); use `with`/`inexcess` instead
9
+ - TC35API bombed-run detection matches THERMOCALC 3.51's actual `-- run bombed` output
10
+ - About, Modes and Full TC output dialogs no longer get hidden behind the main builder window; About dialog now also shows the builder's icon
11
+ - `Import from JSON` no longer requires an empty project; existing invariant points/univariant lines are left untouched and only genuinely new entries are calculated. It seeds THERMOCALC ptguesses from the nearest already-calculated invariant point before each calculation
12
+ - `tcinit`'s bundled TC 3.51 starting-guess template used inconsistent solution-model names (`L`, `pl`, `ksp` instead of `liq`, `pl4tr`, `k4tr`) for the same phases it names correctly elsewhere in the template
13
+ ### Changed
14
+ - CI test matrix now runs on Linux, Windows and macOS (in addition to the existing Python version matrix)
15
+
16
+ ## [2.6.7] - 2026-08-29
17
+ ### Added
18
+ - `tcinit` now prompts for the P-T range (`[pmin, pmax, tmin, tmax]`), used to set the generated scriptfile's `diagramPT`/`calcP`/`calcT` lines
19
+ - `tcinit` creates the destination directory automatically when it doesn't exist yet but its parent does
20
+
4
21
  ## [2.6.6] - 2026-08-29
5
22
  ### Fixed
6
23
  - project extra jupyter renamed to lab
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pypsbuilder
3
- Version: 2.6.6
3
+ Version: 2.6.8
4
4
  Summary: THERMOCALC front-end for constructing and analyzing PT pseudosections
5
5
  Project-URL: Homepage, https://github.com/ondrolexa/pypsbuilder
6
6
  Project-URL: Documentation, https://pypsbuilder.readthedocs.io
@@ -65,18 +65,23 @@ Using tcinit to setup working directory
65
65
  ---------------------------------------
66
66
 
67
67
  For TC3.5 you can use the ``tcinit`` command to initialize a working
68
- directory. Run it in an empty directory and answer the prompts::
68
+ directory. Run it and answer the prompts::
69
69
 
70
70
  $ tcinit
71
71
 
72
- It downloads THERMOCALC 3.51 for your platform (Linux, Windows or macOS) and
73
- lets you choose one of four bundled datasets/a-x files: ``metapelite``,
74
- ``metabasite``, ``igg25`` (igneous, White et al. 2025 update) or ``igw24``
75
- (igneous, Weller et al. 2024). The chosen dataset, a-x file, starting
76
- guesses and preferences file are written into the project directory,
77
- already tagged with the ``%{PSBGUESS-...}``, ``%{PSBCALC-...}`` and
78
- ``%{PSBBULK-...}`` blocks described above, so the resulting scriptfile works
79
- with ``ptbuilder``/``txbuilder``/``pxbuilder`` out of the box.
72
+ You will be asked for a project directory (defaults to the current
73
+ directory; if it doesn't exist yet but its parent does, it is created for
74
+ you automatically), a project name (max 8 characters), the P-T range to use
75
+ (``[pmin, pmax, tmin, tmax]``, defaulting to ``[1.9, 10.1, 345, 805]`` and
76
+ written into the scriptfile's ``diagramPT``/``calcP``/``calcT`` lines), and
77
+ one of four bundled datasets/a-x files: ``metapelite``, ``metabasite``,
78
+ ``igg25`` (igneous, White et al. 2025 update) or ``igw24`` (igneous, Weller
79
+ et al. 2024). It then downloads THERMOCALC 3.51 for your platform (Linux,
80
+ Windows or macOS). The chosen dataset, a-x file, starting guesses and
81
+ preferences file are written into the project directory, already tagged
82
+ with the ``%{PSBGUESS-...}``, ``%{PSBCALC-...}`` and ``%{PSBBULK-...}``
83
+ blocks described above, so the resulting scriptfile works with
84
+ ``ptbuilder``/``txbuilder``/``pxbuilder`` out of the box.
80
85
 
81
86
 
82
87
  New P-T pseudosection project
@@ -245,6 +250,35 @@ When you have already calculated invariant points and lines and you want to use
245
250
  them in separate project, create new project, set axes limits properly and
246
251
  use `File>Import > Import into range` to read it.
247
252
 
253
+ Import and export JSON
254
+ -----------------------
255
+
256
+ Besides the ``.ptb``/``.txb``/``.pxb`` project files, invariant points and
257
+ univariant lines can be exchanged as plain JSON, e.g. to share a
258
+ pseudosection's topology without the project file, or to seed a new project
259
+ from results calculated elsewhere.
260
+
261
+ ``File>Export>Export JSON...`` writes the current project's invariant points
262
+ and univariant lines to a JSON file with top-level keys ``kind`` (``"PT"``,
263
+ ``"TX"`` or ``"PX"``), ``P``/``T`` (the diagram's fixed P or T value for
264
+ T-X/P-X sections, otherwise its ``[min, max]`` range), and ``inv``/``uni``
265
+ lists. Each entry in ``inv``/``uni`` carries ``P``/``T``/``X`` (a
266
+ ``[min, max]`` range, or ``null`` for the axis unused by this builder's
267
+ ``kind``), ``phases`` and ``out`` (the zero-mode phase(s): two for an
268
+ invariant point, one for a univariant line).
269
+
270
+ ``File>Import>Import from JSON...`` reads such a file back and calculates any
271
+ invariant point or univariant line not already present in the project
272
+ (existing ones are left untouched and reported as duplicates). Its P/T range
273
+ must fit inside the project's own diagram window, and phases referenced in
274
+ the file that aren't defined in this project prompt for a replacement name
275
+ (or are resolved from a ``phase_map`` when calling ``import_from_json()``
276
+ from a script). Each calculation is seeded from the nearest already
277
+ calculated invariant point, invariant points that fail on the first pass are
278
+ retried once more points are available as seeds, and univariant lines are
279
+ stepped along whichever axis they run along more, at a resolution capped
280
+ relative to the full diagram range.
281
+
248
282
  T-X and P-X pseudosections
249
283
  --------------------------
250
284
 
@@ -1,6 +1,6 @@
1
1
  % ==================================================================
2
2
  % Scriptfile for pypsbuilder calculation
3
- %
3
+ %
4
4
  % Use "%" for commenting (nothing is read between % and the line ending).
5
5
  % No lines are read after the "*" character.
6
6
  % DO NOT REMOVE pypsbuilder TAGS !!!
@@ -16,7 +16,6 @@ autoexit
16
16
 
17
17
  inexcess q H2O
18
18
  %tozero H2O
19
- omit sp0 sp1 Ni NiO opx sp ta plc abh ilmm hemm ilm0 ksp0 mt0 mt1 heme
20
19
 
21
20
  % =======================
22
21
  % which conditions
@@ -61,7 +60,7 @@ samecoding plc pl % rename Cbar1 plagioclase as 'pl'
61
60
 
62
61
  %{PSBGUESS-BEGIN}
63
62
  % ----------------------------------------------------------
64
- % at P = 5.5, T = 500, for: g pl mu bi chl ab ru q H2O
63
+ % at P = 5.5, T = 500, for: g pl mu bi chl ab ru q H2O
65
64
  % ----------------------------------------------------------
66
65
  ptguess 5.539 500.35
67
66
  % ----------------------------------------------------------
@@ -98,7 +97,7 @@ xyzguess Q4(chl) 0.0941449 range -1.000 1.000
98
97
 
99
98
  % --------------------------------------------------------
100
99
  % Starting guesses in MnNCKFMASHTO (over-ridden by any above)
101
- % for g liq pl ksp ep ma mu pa bi opx sa cd st chl ctd sp mt ilmm hemm ilm hem mt1
100
+ % for g liq pl ksp ep ma mu pa bi opx sa cd st chl ctd sp mt ilmm hemm ilm hem mt1
102
101
  % --------------------------------------------------------
103
102
 
104
103
  xyzguess x(g) 0.824169
@@ -121,9 +120,9 @@ xyzguess na(ksp) 0.227897
121
120
  xyzguess ca(ksp) 0.0114380
122
121
 
123
122
  xyzguess f(ep) 0.379418
124
- xyzguess Q(ep) 0.346850 range 0.000 0.500
123
+ xyzguess Q(ep) 0.346850 range 0.000 0.500
125
124
 
126
- xyzguess x(ma) 0.6
125
+ xyzguess x(ma) 0.6
127
126
  xyzguess y(ma) 0.96
128
127
  xyzguess f(ma) 0.001
129
128
  xyzguess n(ma) 0.05
@@ -133,7 +132,7 @@ xyzguess x(mu) 0.501982
133
132
  xyzguess y(mu) 0.839614
134
133
  xyzguess f(mu) 0.0172093
135
134
  xyzguess n(mu) 0.144000
136
- xyzguess c(mu) 0.00179401
135
+ xyzguess c(mu) 0.00179401
137
136
 
138
137
  xyzguess x(pa) 0.501982
139
138
  xyzguess y(pa) 0.998030
@@ -153,7 +152,7 @@ xyzguess m(opx) 0.001
153
152
  xyzguess y(opx) 0.128377 range 0.000 2.000
154
153
  xyzguess f(opx) 0.01
155
154
  xyzguess c(opx) 0.01
156
- xyzguess Q(opx) 0.216433
155
+ xyzguess Q(opx) 0.216433
157
156
 
158
157
  xyzguess x(sa) 0.1
159
158
  xyzguess y(sa) 0.3
@@ -181,13 +180,13 @@ xyzguess x(ctd) 0.990857
181
180
  xyzguess m(ctd) 0.001
182
181
  xyzguess f(ctd) 0.01
183
182
 
184
- xyzguess x(sp) 0.6
185
- xyzguess y(sp) 0.95
186
- xyzguess z(sp) 0.01
183
+ xyzguess x(sp) 0.6
184
+ xyzguess y(sp) 0.95
185
+ xyzguess z(sp) 0.01
187
186
 
188
- xyzguess x(mt) 0.85
189
- xyzguess y(mt) 0.05
190
- xyzguess z(mt) 0.01
187
+ xyzguess x(mt) 0.85
188
+ xyzguess y(mt) 0.05
189
+ xyzguess z(mt) 0.01
191
190
 
192
191
  xyzguess i(ilmm) 0.858568
193
192
  xyzguess g(ilmm) 0.0294376
@@ -199,11 +198,11 @@ xyzguess g(hemm) 0.00296953
199
198
  xyzguess m(hemm) 0.0305831
200
199
  xyzguess Q(hemm) -0.0102634 range -0.300 0.300
201
200
 
202
- xyzguess x(ilm) 0.80
201
+ xyzguess x(ilm) 0.80
203
202
  xyzguess Q(ilm) 0.01 range -0.990 0.990
204
203
 
205
- xyzguess x(hem) 0.20
206
- xyzguess Q(hem) 0.8 range -0.99 0.99
204
+ xyzguess x(hem) 0.20
205
+ xyzguess Q(hem) 0.8 range -0.99 0.99
207
206
 
208
207
  xyzguess x(mt1) 0.632160
209
208
  xyzguess Q(mt1) 0.564334
@@ -22,7 +22,7 @@ __all__ = (
22
22
  "get_tcapi",
23
23
  )
24
24
 
25
- __version__ = "2.6.6"
25
+ __version__ = "2.6.8"
26
26
  __author__ = "Ondrej Lexa"
27
27
  __copyright__ = "© Ondrej Lexa 2016-2025"
28
28
  __email__ = "lexa.ondrej@gmail.com"
@@ -81,6 +81,10 @@ app_icons = dict(
81
81
  PXBuilder="images/pxbuilder.png",
82
82
  )
83
83
 
84
+ # maps a section's x_var/y_var ("T"/"p"/"C") to the JSON entry key that
85
+ # carries that axis's [min, max] span ("T"/"P"/"X")
86
+ json_key_for_var = {"T": "T", "p": "P", "C": "X"}
87
+
84
88
 
85
89
  class BuildersBase(QtWidgets.QMainWindow):
86
90
  """Main base class for pseudosection builders."""
@@ -96,7 +100,9 @@ class BuildersBase(QtWidgets.QMainWindow):
96
100
  )
97
101
  self.setWindowIcon(QtGui.QIcon(window_icon))
98
102
  self.__changed = False
99
- self.about_dialog = AboutDialog(self.builder_name, __version__, __copyright__)
103
+ self.about_dialog = AboutDialog(
104
+ self.builder_name, __version__, __copyright__, window_icon, parent=self
105
+ )
100
106
  self.unihigh = None
101
107
  self.invhigh = None
102
108
  self.outhigh = None
@@ -274,6 +280,7 @@ class BuildersBase(QtWidgets.QMainWindow):
274
280
  self.actionAbout.triggered.connect(self.about_dialog.exec)
275
281
  self.actionImport_project.triggered.connect(self.import_from_prj)
276
282
  self.actionImport_json.triggered.connect(lambda: self.import_from_json())
283
+ self.actionExport_json.triggered.connect(lambda: self.export_to_json())
277
284
  self.actionCleanup.triggered.connect(self.cleanup_storage)
278
285
  self.actionFixphase.triggered.connect(self.fix_phasenames)
279
286
  self.actionShow_areas.triggered.connect(self.check_prj_areas)
@@ -701,16 +708,70 @@ class BuildersBase(QtWidgets.QMainWindow):
701
708
  entry["out"] = [mapping.get(p, p) for p in entry.get("out", [])]
702
709
  return True
703
710
 
711
+ def _entry_xy(self, entry):
712
+ """Midpoint (x, y) of a JSON inv/uni entry in diagram (ps.xrange/yrange) units."""
713
+ xv = entry.get(json_key_for_var[self.ps.x_var])
714
+ yv = entry.get(json_key_for_var[self.ps.y_var])
715
+ if xv is None or yv is None:
716
+ return None, None
717
+ return sum(xv) / 2, sum(yv) / 2
718
+
719
+ def _nearest_invpoint(self, x, y):
720
+ """Closest existing InvPoint to (x, y), distance normalized by
721
+ ps.xrange/yrange, or None if there's nothing to compare against.
722
+ """
723
+ if x is None or y is None or not self.ps.invpoints:
724
+ return None
725
+ xspan = self.ps.xrange[1] - self.ps.xrange[0]
726
+ yspan = self.ps.yrange[1] - self.ps.yrange[0]
727
+ if xspan <= 0 or yspan <= 0:
728
+ return None
729
+ return min(
730
+ self.ps.invpoints.values(),
731
+ key=lambda inv: ((inv._x - x) / xspan) ** 2 + ((inv._y - y) / yspan) ** 2,
732
+ )
733
+
734
+ def _seed_guess_from_nearest(self, entry):
735
+ """Seed the scriptfile's ptguesses from the nearest already-calculated
736
+ invariant point to this JSON entry, mirroring invsel_guesses's manual
737
+ `update_scriptfile(guesses=inv.ptguess())` pattern.
738
+ """
739
+ nearest = self._nearest_invpoint(*self._entry_xy(entry))
740
+ if nearest is not None:
741
+ self.tc.update_scriptfile(guesses=nearest.ptguess())
742
+
743
+ def _autoconnect_inv(self, inv, id_inv):
744
+ """Connect any univariant line that now has both its endpoints
745
+ calculated, mirroring the manual autoconnect behavior.
746
+ """
747
+ for uni in self.ps.unilines.values():
748
+ if uni.contains_inv(inv):
749
+ candidates = [inv]
750
+ for other_inv in self.ps.invpoints.values():
751
+ if other_inv.id != id_inv and uni.contains_inv(other_inv):
752
+ candidates.append(other_inv)
753
+ if len(candidates) == 2:
754
+ self.uni_connect(uni.id, candidates)
755
+
756
+ def _import_step_count(self, span, full_span):
757
+ """Step count so the physical step used for a JSON-imported univariant
758
+ line is never finer than 1/200 of the full pseudosection range for
759
+ the axis being stepped (a floor on step size), capped at
760
+ spinSteps.value().
761
+ """
762
+ if full_span <= 0 or span <= 0:
763
+ return self.spinSteps.value()
764
+ min_step = full_span / 200
765
+ max_steps = int(span / min_step)
766
+ return max(min(self.spinSteps.value(), max_steps), 1)
767
+
704
768
  def import_from_json(self, jsonfile=None, phase_map=None):
705
- if not (
706
- self.ready and len(self.ps.invpoints) == 0 and len(self.ps.unilines) == 0
707
- ):
769
+ if not self.ready:
708
770
  qb = QtWidgets.QMessageBox
709
771
  qb.critical(
710
772
  self,
711
773
  "Cannot import",
712
- "Import from JSON requires an initialized, empty project "
713
- "(no invariant points or univariant lines yet).",
774
+ "Import from JSON requires an initialized project.",
714
775
  qb.StandardButton.Abort,
715
776
  )
716
777
  return
@@ -747,10 +808,14 @@ class BuildersBase(QtWidgets.QMainWindow):
747
808
  n_uni_ok = n_uni_fail = n_uni_dup = 0
748
809
  QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.CursorShape.WaitCursor)
749
810
  try:
811
+ # Round 1: attempt every new invariant point once. Entries that
812
+ # fail are retried in round 2 below, once other points from this
813
+ # same import are available as nearby ptguess seeds.
750
814
  n = len(data["inv"])
815
+ retry = []
751
816
  for i, entry in enumerate(data["inv"]):
752
817
  self.statusBar().showMessage(
753
- "Importing invariant point {}/{}...".format(i + 1, n)
818
+ "Importing invariant point {}/{} (round 1)...".format(i + 1, n)
754
819
  )
755
820
  QtWidgets.QApplication.processEvents()
756
821
  try:
@@ -766,6 +831,27 @@ class BuildersBase(QtWidgets.QMainWindow):
766
831
  if not isnew:
767
832
  n_inv_dup += 1
768
833
  continue
834
+ self._seed_guess_from_nearest(entry)
835
+ inv = self._import_calc_inv(inv_tmp.phases, inv_tmp.out)
836
+ if inv is None:
837
+ retry.append((id_inv, inv_tmp, entry))
838
+ continue
839
+ self.invmodel.appendRow(id_inv, inv)
840
+ n_inv_ok += 1
841
+ if self.checkAutoconnectInv.isChecked():
842
+ self._autoconnect_inv(inv, id_inv)
843
+ self.plot()
844
+
845
+ # Round 2: retry points that failed in round 1, now that any
846
+ # points calculated in round 1 are available as nearby ptguess
847
+ # seeds too.
848
+ n = len(retry)
849
+ for i, (id_inv, inv_tmp, entry) in enumerate(retry):
850
+ self.statusBar().showMessage(
851
+ "Importing invariant point {}/{} (round 2)...".format(i + 1, n)
852
+ )
853
+ QtWidgets.QApplication.processEvents()
854
+ self._seed_guess_from_nearest(entry)
769
855
  inv = self._import_calc_inv(inv_tmp.phases, inv_tmp.out)
770
856
  if inv is None:
771
857
  n_inv_fail += 1
@@ -773,16 +859,8 @@ class BuildersBase(QtWidgets.QMainWindow):
773
859
  self.invmodel.appendRow(id_inv, inv)
774
860
  n_inv_ok += 1
775
861
  if self.checkAutoconnectInv.isChecked():
776
- for uni in self.ps.unilines.values():
777
- if uni.contains_inv(inv):
778
- candidates = [inv]
779
- for other_inv in self.ps.invpoints.values():
780
- if other_inv.id != id_inv and uni.contains_inv(
781
- other_inv
782
- ):
783
- candidates.append(other_inv)
784
- if len(candidates) == 2:
785
- self.uni_connect(uni.id, candidates)
862
+ self._autoconnect_inv(inv, id_inv)
863
+ self.plot()
786
864
 
787
865
  n = len(data["uni"])
788
866
  for j, entry in enumerate(data["uni"]):
@@ -803,6 +881,7 @@ class BuildersBase(QtWidgets.QMainWindow):
803
881
  if not isnew:
804
882
  n_uni_dup += 1
805
883
  continue
884
+ self._seed_guess_from_nearest(entry)
806
885
  uni = self._import_calc_uni(uni_tmp.phases, uni_tmp.out, entry)
807
886
  if uni is None:
808
887
  n_uni_fail += 1
@@ -814,13 +893,13 @@ class BuildersBase(QtWidgets.QMainWindow):
814
893
  n_uni_ok += 1
815
894
  if self.checkAutoconnectUni.isChecked() and len(candidates) == 2:
816
895
  self.uni_connect(id_uni, candidates)
896
+ self.plot()
817
897
  finally:
818
898
  QtWidgets.QApplication.restoreOverrideCursor()
819
899
 
820
900
  self.changed = True
821
901
  self.invview.resizeColumnsToContents()
822
902
  self.uniview.resizeColumnsToContents()
823
- self.plot()
824
903
  summary = (
825
904
  "Imported {}/{} invariant points and {}/{} univariant lines from JSON."
826
905
  ).format(n_inv_ok, len(data["inv"]), n_uni_ok, len(data["uni"]))
@@ -834,6 +913,73 @@ class BuildersBase(QtWidgets.QMainWindow):
834
913
  ).format(n_inv_dup, n_uni_dup)
835
914
  self.statusBar().showMessage(summary)
836
915
 
916
+ def _build_export_json(self):
917
+ """Build the JSON-serializable dict of current invariant points and
918
+ univariant lines. Kept free of Qt/file I/O so it can be tested
919
+ directly against a duck-typed stand-in for self.
920
+ """
921
+ kind_map = {"PTBuilder": "PT", "TXBuilder": "TX", "PXBuilder": "PX"}
922
+ kind = kind_map[self.builder_name]
923
+
924
+ def axes(x0, x1, y0, y1):
925
+ if kind == "PT":
926
+ return {"P": [y0, y1], "T": [x0, x1], "X": None}
927
+ elif kind == "TX":
928
+ return {"P": None, "T": [x0, x1], "X": [y0, y1]}
929
+ else: # PX
930
+ return {"P": [y0, y1], "T": None, "X": [x0, x1]}
931
+
932
+ if kind == "TX":
933
+ P = float(sum(self.tc.prange) / 2)
934
+ T = [float(v) for v in self.ps.xrange]
935
+ elif kind == "PX":
936
+ P = [float(v) for v in self.ps.yrange]
937
+ T = float(sum(self.tc.trange) / 2)
938
+ else:
939
+ P = [float(v) for v in self.ps.yrange]
940
+ T = [float(v) for v in self.ps.xrange]
941
+
942
+ inv_list = []
943
+ for inv in self.ps.invpoints.values():
944
+ entry = axes(inv._x, inv._x, inv._y, inv._y)
945
+ entry["phases"] = sorted(inv.phases)
946
+ entry["out"] = sorted(inv.out)
947
+ inv_list.append(entry)
948
+
949
+ uni_list = []
950
+ for uni in self.ps.unilines.values():
951
+ entry = axes(
952
+ float(uni.x.min()),
953
+ float(uni.x.max()),
954
+ float(uni.y.min()),
955
+ float(uni.y.max()),
956
+ )
957
+ entry["phases"] = sorted(uni.phases)
958
+ entry["out"] = sorted(uni.out)
959
+ uni_list.append(entry)
960
+
961
+ return {"kind": kind, "P": P, "T": T, "inv": inv_list, "uni": uni_list}
962
+
963
+ def export_to_json(self):
964
+ if not self.ready:
965
+ self.statusBar().showMessage("Project is not yet initialized.")
966
+ return
967
+ filename = QtWidgets.QFileDialog.getSaveFileName(
968
+ self, "Export to JSON", str(self.tc.workdir), "JSON file (*.json)"
969
+ )[0]
970
+ if not filename:
971
+ return
972
+ if not filename.lower().endswith(".json"):
973
+ filename = filename + ".json"
974
+ data = self._build_export_json()
975
+ with open(filename, "w", encoding="utf-8") as f:
976
+ json.dump(data, f, indent=2)
977
+ self.statusBar().showMessage(
978
+ "Exported {} invariant points and {} univariant lines to {}".format(
979
+ len(data["inv"]), len(data["uni"]), filename
980
+ )
981
+ )
982
+
837
983
  def cleanup_storage(self):
838
984
  if self.ready:
839
985
  qb = QtWidgets.QMessageBox
@@ -1057,10 +1203,12 @@ class BuildersBase(QtWidgets.QMainWindow):
1057
1203
  def show_output(self, int):
1058
1204
  if self.ready:
1059
1205
  if int == 0:
1060
- dia = OutputDialog("Modes", self.textOutput.toPlainText())
1206
+ dia = OutputDialog("Modes", self.textOutput.toPlainText(), parent=self)
1061
1207
  dia.exec()
1062
1208
  if int == 1:
1063
- dia = OutputDialog("TC output", self.textFullOutput.toPlainText())
1209
+ dia = OutputDialog(
1210
+ "TC output", self.textFullOutput.toPlainText(), parent=self
1211
+ )
1064
1212
  dia.exec()
1065
1213
 
1066
1214
  def toolbar_mode_name(self):
@@ -3126,12 +3274,22 @@ class PTBuilder(BuildersBase, Ui_PTBuilder):
3126
3274
  results=res,
3127
3275
  )
3128
3276
 
3277
+ def _uni_calc_strategy(self, p0, p1, t0, t1):
3278
+ """ "calc_p" (steps T, calctatp no) or "calc_t" (steps P, calctatp
3279
+ yes) for a JSON-imported univariant line spanning [p0, p1] x
3280
+ [t0, t1]: step through whichever axis spans proportionally more of
3281
+ the full pseudosection range - i.e. the axis the line runs along
3282
+ more, as plotted - and solve for the other.
3283
+ """
3284
+ ratio_P = (p1 - p0) / (self.tc.prange[1] - self.tc.prange[0])
3285
+ ratio_T = (t1 - t0) / (self.tc.trange[1] - self.tc.trange[0])
3286
+ return "calc_p" if ratio_P < ratio_T else "calc_t"
3287
+
3129
3288
  def _import_calc_uni(self, phases, out, entry):
3130
3289
  try:
3131
- p0, p1 = entry["P"]
3132
- t0, t1 = entry["T"]
3133
- ratio_P = (p1 - p0) / (self.tc.prange[1] - self.tc.prange[0])
3134
- ratio_T = (t1 - t0) / (self.tc.trange[1] - self.tc.trange[0])
3290
+ p0, p1 = sorted(entry["P"])
3291
+ t0, t1 = sorted(entry["T"])
3292
+ strategy = self._uni_calc_strategy(p0, p1, t0, t1)
3135
3293
  except (KeyError, TypeError, ValueError, ZeroDivisionError):
3136
3294
  return None
3137
3295
  # extend by 10% of the JSON line's own P/T span to minimize
@@ -3140,13 +3298,19 @@ class PTBuilder(BuildersBase, Ui_PTBuilder):
3140
3298
  text = 0.1 * (t1 - t0)
3141
3299
  prange = (max(p0 - pext, self.tc.prange[0]), min(p1 + pext, self.tc.prange[1]))
3142
3300
  trange = (max(t0 - text, self.tc.trange[0]), min(t1 + text, self.tc.trange[1]))
3143
- calc = self.tc.calc_p if ratio_P < ratio_T else self.tc.calc_t
3301
+ calc = getattr(self.tc, strategy)
3302
+ if strategy == "calc_p":
3303
+ span = trange[1] - trange[0]
3304
+ full_span = self.tc.trange[1] - self.tc.trange[0]
3305
+ else:
3306
+ span = prange[1] - prange[0]
3307
+ full_span = self.tc.prange[1] - self.tc.prange[0]
3144
3308
  tcout, ans = calc(
3145
3309
  phases,
3146
3310
  out,
3147
3311
  prange=prange,
3148
3312
  trange=trange,
3149
- steps=self.spinSteps.value(),
3313
+ steps=self._import_step_count(span, full_span),
3150
3314
  )
3151
3315
  status, res, output = self.tc.parse_logfile()
3152
3316
  if status in ("bombed", "nir") or len(res) < 2:
@@ -5647,12 +5811,28 @@ class UniGuess(QtWidgets.QDialog, Ui_UniGuess):
5647
5811
  class AboutDialog(QtWidgets.QDialog):
5648
5812
  """About dialog"""
5649
5813
 
5650
- def __init__(self, builder, version, copyright, parent=None):
5814
+ def __init__(self, builder, version, copyright, icon=None, parent=None):
5651
5815
  """Display a dialog that shows application information."""
5652
5816
  super(AboutDialog, self).__init__(parent)
5653
5817
 
5654
5818
  self.setWindowTitle("About")
5655
- self.resize(300, 100)
5819
+ self.resize(320, 220)
5820
+ # keep the dialog above the main window (and everything else)
5821
+ # instead of a plain modal QDialog, which some window managers can
5822
+ # still push behind the parent window
5823
+ self.setWindowFlags(
5824
+ self.windowFlags() | QtCore.Qt.WindowType.WindowStaysOnTopHint
5825
+ )
5826
+
5827
+ if icon is not None:
5828
+ self.setWindowIcon(QtGui.QIcon(icon))
5829
+ logo = QtWidgets.QLabel()
5830
+ logo.setPixmap(
5831
+ QtGui.QIcon(icon).pixmap(
5832
+ 64, 64, QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On
5833
+ )
5834
+ )
5835
+ logo.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
5656
5836
 
5657
5837
  title = QtWidgets.QLabel("{} {}".format(builder, version))
5658
5838
  title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
@@ -5682,6 +5862,8 @@ class AboutDialog(QtWidgets.QDialog):
5682
5862
  self.layout = QtWidgets.QVBoxLayout()
5683
5863
  self.layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignVCenter)
5684
5864
 
5865
+ if icon is not None:
5866
+ self.layout.addWidget(logo)
5685
5867
  self.layout.addWidget(title)
5686
5868
  self.layout.addWidget(suptitle)
5687
5869
  self.layout.addWidget(author)
@@ -5700,6 +5882,9 @@ class OutputDialog(QtWidgets.QDialog):
5700
5882
 
5701
5883
  self.setWindowTitle(title)
5702
5884
  self.resize(800, 600)
5885
+ self.setWindowFlags(
5886
+ self.windowFlags() | QtCore.Qt.WindowType.WindowStaysOnTopHint
5887
+ )
5703
5888
 
5704
5889
  self.plainText = QtWidgets.QPlainTextEdit(self)
5705
5890
  self.plainText.setLineWrapMode(QtWidgets.QPlainTextEdit.LineWrapMode.NoWrap)
@@ -507,9 +507,10 @@ class TC35API(TCAPI):
507
507
  # omit
508
508
  errinfo = "Wrong omit in scriptfile."
509
509
  if "omit" in scripts:
510
- self.omit = set(scripts["omit"][0].split())
510
+ raise ScriptfileError("omit, is unrecognised (or discontinued).")
511
511
  else:
512
512
  self.omit = set()
513
+ errinfo = "Error in scriptfile."
513
514
  # autoexit
514
515
  if "autoexit" not in scripts:
515
516
  raise ScriptfileError(
@@ -541,15 +542,21 @@ class TC35API(TCAPI):
541
542
  "Dogmin script should be removed from scriptfile."
542
543
  )
543
544
  # union ax phases and samecoding and diff omit
544
- if "BOMBED" in self.tcout:
545
- raise TCError(self.tcout.split("BOMBED")[1].split(os.linesep)[0])
546
- else:
545
+ if "reading ax:" in self.tcout:
547
546
  ax_phases = set(
548
547
  self.tcout.split("reading ax:")[1]
549
548
  .split(2 * os.linesep)[0]
550
549
  .split()
551
550
  )
552
551
  self.phases = ax_phases.union(*self.samecoding) - self.omit
552
+ else:
553
+ if "-- run bombed" in self.tcout:
554
+ raise TCError(
555
+ "-- run bombed"
556
+ + self.tcout.split("-- run bombed")[1].split(os.linesep)[0]
557
+ )
558
+ else:
559
+ raise TCError("Unspecified error")
553
560
  # OK
554
561
  self.status = "Initial check done."
555
562
  self.OK = True
@@ -573,7 +580,7 @@ class TC35API(TCAPI):
573
580
  self.OK = False
574
581
 
575
582
  def parse_logfile(self, **kwargs):
576
- """Parser for THERMOCALC 3.50beta output.
583
+ """Parser for THERMOCALC 3.51 output.
577
584
 
578
585
  It parses the outputs of THERMOCALC after calculation.
579
586