Skip to content

Commit 9d86348

Browse files
kip-hartclaude
andcommitted
Carry the welcome figures in the example cache
plot_demos.py writes welcome_examples.png and joined.png beside the example inputs rather than in an output directory, and welcome.rst embeds both. The packer skipped that directory to keep the tracked input aluminum_micro.png out of the archive, so it dropped those two figures as well. A build that restored the archive never ran plot_demos.py and never produced them, Sphinx could not resolve the figure targets, and the home page shipped the raw source paths. Both have been 404 on docs.microstructpy.org since 1.6.0. Take the two by name, and refuse to pack or restore an archive without them so the failure lands on the build that publishes the cache rather than on the published page. The archive name now carries a format number, so the incomplete archive already published cannot be selected. Also add periodic microstructures to the feature list, and put the periodic tiling example in the welcome figure in place of the colormap example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent d09952f commit 9d86348

4 files changed

Lines changed: 59 additions & 11 deletions

File tree

‎README.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ MicroStructPy is a microstructure mesh generator written in Python.
1818
Features of MicroStructPy include:
1919

2020
* 2D and 3D microstructures
21+
* Periodic microstructures
2122
* Grain size, shape, orientation, and position control
2223
* Polycrystals, amorphous phases, and voids
2324
* Unstructured and raster meshes

‎docs/example_cache.py‎

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
skip the examples instead of running them.
1717
* The PNG files under ``src/microstructpy/examples``, which the pages in
1818
``docs/source/examples`` embed with ``figure::``. The meshes beside
19-
them are not in the archive, since no page refers to them.
19+
them are not in the archive, since no page refers to them. Most of
20+
these are in an output directory named for the example, and two,
21+
``joined.png`` and ``welcome_examples.png``, are written beside the
22+
inputs. ``welcome.rst`` embeds those two, so leaving them out breaks
23+
the home page of a build that restores the archive, and ``pack`` and
24+
``restore`` both refuse an archive without them.
2025
2126
An archive carries the digest of the inputs it was built from, from
2227
:mod:`docs.example_digest`. ``restore`` refuses an archive whose digest
@@ -49,6 +54,15 @@
4954
EXAMPLES = os.path.join('src', 'microstructpy', 'examples')
5055
MANIFEST = 'microstructpy-example-cache.json'
5156

57+
# Bumped when the contents of the archive change, so that an archive
58+
# packed by an older version of this script is never restored.
59+
FORMAT = 2
60+
61+
# The figures plot_demos.py writes beside the example inputs rather than
62+
# in an output directory, by create_welcome() and seed_poly_tri(). Every
63+
# other file in that directory is an input. welcome.rst embeds both.
64+
TOP_LEVEL_FIGURES = ('joined.png', 'welcome_examples.png')
65+
5266

5367
def _gallery_members():
5468
"""Every file of the Sphinx-Gallery output, relative to the root."""
@@ -65,19 +79,23 @@ def _gallery_members():
6579
def _figure_members():
6680
"""Every PNG an example wrote, relative to the root.
6781
68-
Only the files in the output directories of the examples, so that an
69-
input such as ``aluminum_micro.png`` is not carried in the archive.
82+
The output directories hold nothing but figures, so they are taken
83+
whole. The directory above them holds the inputs, and an input such
84+
as ``aluminum_micro.png`` must not be carried in the archive, so the
85+
two figures written there are taken by name.
7086
"""
7187
members = []
7288
root = os.path.join(ROOT, EXAMPLES)
7389
for dirpath, dirnames, filenames in os.walk(root):
7490
dirnames.sort()
75-
if os.path.abspath(dirpath) == os.path.abspath(root):
76-
continue # the inputs live here, the figures are in subdirs
91+
top = os.path.abspath(dirpath) == os.path.abspath(root)
7792
for name in sorted(filenames):
78-
if name.endswith('.png'):
79-
path = os.path.join(dirpath, name)
80-
members.append(os.path.relpath(path, ROOT))
93+
if not name.endswith('.png'):
94+
continue
95+
if top and name not in TOP_LEVEL_FIGURES:
96+
continue
97+
path = os.path.join(dirpath, name)
98+
members.append(os.path.relpath(path, ROOT))
8199
return members
82100

83101

@@ -94,8 +112,19 @@ def pack(filename):
94112
print('No figures under %s to pack.' % EXAMPLES, file=sys.stderr)
95113
return 1
96114

115+
# A build that restores the archive does not run plot_demos.py, so a
116+
# figure left out here is a figure the published documentation will
117+
# never have. Refuse to publish an archive that is missing one.
118+
missing = [n for n in TOP_LEVEL_FIGURES
119+
if os.path.join(EXAMPLES, n) not in figures]
120+
if missing:
121+
print('Not packing, plot_demos.py did not leave %s in %s.'
122+
% (', '.join(missing), EXAMPLES), file=sys.stderr)
123+
return 1
124+
97125
value = digest()
98-
manifest = json.dumps({'digest': value,
126+
manifest = json.dumps({'format': FORMAT,
127+
'digest': value,
99128
'gallery': len(gallery),
100129
'figures': len(figures)}, indent=2).encode()
101130

@@ -127,6 +156,13 @@ def restore(filename):
127156
% (filename, MANIFEST), file=sys.stderr)
128157
return 1
129158

159+
if manifest.get('format') != FORMAT:
160+
print('%s was packed in format %s and this is format %d. '
161+
'Refusing to restore it, the examples will be run.'
162+
% (filename, manifest.get('format'), FORMAT),
163+
file=sys.stderr)
164+
return 1
165+
130166
current = digest()
131167
if manifest.get('digest') != current:
132168
print('The archive was built from %s and the examples digest '
@@ -135,6 +171,16 @@ def restore(filename):
135171
return 1
136172

137173
members = [m for m in tar.getmembers() if m.name != MANIFEST]
174+
175+
names = set(m.name for m in members)
176+
absent = [n for n in TOP_LEVEL_FIGURES
177+
if '/'.join(EXAMPLES.split(os.sep) + [n]) not in names]
178+
if absent:
179+
print('%s does not carry %s. Refusing to restore it, the '
180+
'examples will be run.' % (filename, ', '.join(absent)),
181+
file=sys.stderr)
182+
return 1
183+
138184
for m in members:
139185
if m.name.startswith('/') or '..' in m.name.split('/'):
140186
print('Refusing a path outside the tree: %s' % m.name,
@@ -152,7 +198,7 @@ def restore(filename):
152198

153199
def name():
154200
"""Print the archive name for this working tree."""
155-
print('microstructpy-examples-%s.tar.gz' % digest())
201+
print('microstructpy-examples-v%d-%s.tar.gz' % (FORMAT, digest()))
156202
return 0
157203

158204

‎docs/source/sphinx_gallery/plot_demos.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
'basalt_circle/trimesh.png',
3434
'foam/trimesh.png',
3535
'two_phase_3D/trimesh.png',
36-
'colormap/trimesh.png']
36+
'periodic_tiling/tiled_2D.png']
3737

3838
def main():
3939
# Copy Supporting Files

‎docs/source/welcome.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ MicroStructPy is a microstructure mesh generator written in Python.
1212
Features of MicroStructPy include:
1313

1414
* 2D and 3D microstructures
15+
* Periodic microstructures
1516
* Grain size, shape, orientation, and position control
1617
* Polycrystals, amorphous phases, and voids
1718
* Mesh verification

0 commit comments

Comments
 (0)