Skip to content

Commit 10f85fc

Browse files
committed
enable blog
1 parent a00366a commit 10f85fc

File tree

8 files changed

+260
-45
lines changed

8 files changed

+260
-45
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 2-Clause License
22

3-
Copyright (c) 2024, pygfx
3+
Copyright (c) 2024-2025, pygfx
44

55
Redistribution and use in source and binary forms, with or without
66
modification, are permitted provided that the following conditions are met:

makesite.py

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import shutil
1010
import webbrowser
11+
import collections
1112

1213
import markdown
1314
import pygments
@@ -19,18 +20,47 @@
1920

2021
NAV = {
2122
"Main": "index",
23+
"Blog": "blog",
2224
"Sponsor": "sponsor",
23-
# "Blog": "blog",
24-
# "Archive": "archive",
25+
"Archive": "archive",
2526
# "Social": {
2627
# 'Twitter': 'https://twitter.com/pygfx',
2728
# },
2829
}
2930

30-
NEWS = {
31-
"Released pygfx v0.5.0": "https://github.com/pygfx/pygfx/releases/tag/v0.5.0",
32-
"Released wgpu-py v0.18.1": "https://github.com/pygfx/wgpu-py/releases/tag/v0.18.1",
33-
}
31+
NEWS = {} # generated front-end
32+
33+
EXTERNAL_BLOG_POSTS = [
34+
{
35+
"title": "Rendering thick lines with dashes",
36+
"date": "2024-02-23",
37+
"url": "https://almarklein.org/line_rendering.html",
38+
"thumbnail": "https://almarklein.org/thumbs/line_rendering.jpg",
39+
},
40+
{
41+
"title": "GPU triangle tricks",
42+
"date": "2024-02-22",
43+
"url": "https://almarklein.org/triangletricks.html",
44+
"thumbnail": "https://almarklein.org/thumbs/triangletricks.jpg",
45+
},
46+
{
47+
"title": "On WebGPU, wgpu-py, and pygfx",
48+
"date": "2023-02-04",
49+
"url": "https://almarklein.org/wgpu.html",
50+
"thumbnail": "https://almarklein.org/thumbs/wgpu.jpg",
51+
},
52+
{
53+
"title": "Gamma and sRGB in visualisation",
54+
"date": "2022-09-29",
55+
"url": "https://almarklein.org/gamma.html",
56+
"thumbnail": "https://almarklein.org/thumbs/gamma.jpg",
57+
},
58+
]
59+
60+
ExternalPage = collections.namedtuple("ExternalPage", ["title", "date", "url", "thumbnail"])
61+
EXTERNAL_BLOG_POSTS = [ExternalPage(**x) for x in EXTERNAL_BLOG_POSTS]
62+
EXTERNAL_BLOG_POSTS.sort(key=lambda p: p.date)
63+
3464

3565
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
3666
OUT_DIR = os.path.join(THIS_DIR, "output")
@@ -104,20 +134,16 @@ def create_menu(page):
104134
return menu
105135

106136

107-
def create_blog_relatated_pages(posts):
108-
""" Create blog overview page.
137+
def create_blog_related_pages(posts):
138+
""" Create blog overview page and archive.
109139
"""
110140

111-
# Filter and sort
112-
posts = [
113-
post for post in posts.values() if post.date and not post.name.startswith("_")
114-
]
115-
posts.sort(key=lambda p: p.date)
116-
117141
blogpages = {}
118142

119143
# Generate overview page
120144
html = ["<h1>Blog</h1>"]
145+
146+
html += ["\n\n<h2>Status updates</h2>"]
121147
for page in reversed(posts):
122148
text = page.md
123149
if "<!-- END_SUMMARY -->" in text:
@@ -127,16 +153,26 @@ def create_blog_relatated_pages(posts):
127153
else:
128154
summary = text.split("## ")[0]
129155
summary = summary.split("-->")[-1]
156+
datespan = f"<span class='post-date-tags'>{page.date}</span>"
130157

131-
# html.append("<hr />" + page.date_and_tags_html)
132-
html.append("<div style='border-top: 1px solid #ddd;'>" + page.date_and_tags_html + "</div>")
158+
html.append(f"<div style='border-top: 1px solid #ddd;'>{datespan}</div>")
133159
html.append(f'<a class="header" href="{page.name}.html"><h3>{page.title}</h3></a>')
134160
if page.thumbnail:
135161
html.append(f"<a href='{page.name}.html'><img src='{page.thumbnail}' class='thumb' /></a>")
136-
# html.append(f'<h2>{page.title}</h2>')
137-
html.append("<p>" + summary + "</p>")
138-
html.append(f"<a href='{page.name}.html'>read more ...</a><br /><br />")
139-
html.append("<div style='clear: both;'></div>")
162+
html.append("<p>" + summary )
163+
html.append(f"<a href='{page.name}.html'>Read more ...</a>")
164+
html.append("</p>")
165+
html.append("<br /><br /><div style='clear: both;'></div>")
166+
167+
html += ["\n\n<h2>External blog posts</h2>"]
168+
for page in reversed(EXTERNAL_BLOG_POSTS):
169+
datespan = f"<span class='post-date-tags'>{page.date}</span>"
170+
html.append(f"<div style='border-top: 1px solid #ddd;'>{datespan}</div>")
171+
if page.thumbnail:
172+
html.append(f"<a href='{page.url}'><img src='{page.thumbnail}' class='thumb' /></a>")
173+
html.append(f'<a class="header" href="{page.url}"><h3>{page.title}</h3></a>')
174+
html.append("<br /><br /><div style='clear: both;'></div>")
175+
140176
blogpages["overview"] = "\n".join(html)
141177

142178
# Generate archive page
@@ -176,14 +212,24 @@ def create_assets():
176212
pages[name] = Page(name, md)
177213

178214
# Collect blog posts
179-
posts = {}
215+
posts = []
180216
for fname in os.listdir(POSTS_DIR):
181217
if fname.lower().endswith(".md"):
182218
name = fname.split(".")[0].lower()
183219
assert name not in pages, f"blog post slug not allowed: {name}"
184220
with open(os.path.join(POSTS_DIR, fname), "rb") as f:
185221
md = f.read().decode()
186-
posts[name] = Page(name, md)
222+
posts.append(Page(name, md))
223+
224+
# Sort, select publishable, create shortlist
225+
posts.sort(key=lambda p: p.date)
226+
publishable_posts = [post for post in posts if post.date and not post.name.startswith("_")]
227+
228+
# Get recent posts
229+
recent_posts = []
230+
recent_posts += publishable_posts[-1:]
231+
recent_posts += EXTERNAL_BLOG_POSTS[-1:]
232+
recent_posts_str = ",".join(str({"title": p.title, "date": p.date, "url": p.url}) for p in recent_posts)
187233

188234
# Get template
189235
with open(os.path.join(THIS_DIR, "template.html"), "rb") as f:
@@ -196,7 +242,7 @@ def create_assets():
196242
)
197243

198244
# Generate posts
199-
for page in posts.values():
245+
for page in posts:
200246
page.prepare(pages.keys())
201247
title = page.title
202248
menu = create_menu(page)
@@ -215,11 +261,14 @@ def create_assets():
215261
title=title, style=css, body=page.to_html(), menu=menu
216262
)
217263
print("generating page", page.name + ".html")
264+
if page.name == "index":
265+
js = f"var recent_blog_posts = [{recent_posts_str}];"
266+
html = html.replace("var recent_blog_posts = [];", js)
218267
assets[page.name + ".html"] = html.encode()
219268

220269
# Generate special pages
221270
fake_md = "" # "##index\n## archive\n## tags"
222-
for name, html in create_blog_relatated_pages(posts).items():
271+
for name, html in create_blog_related_pages(publishable_posts).items():
223272
name = "blog" if name == "overview" else name
224273
print("generating page", name + ".html")
225274
assets[f"{name}.html"] = html_template.format(
@@ -267,6 +316,7 @@ def __init__(self, name, markdown):
267316
self.md = markdown
268317
self.parts = []
269318
self.headers = []
319+
self.url = f"{name}.html"
270320

271321
self.title = name
272322
if markdown.startswith("# "):
@@ -291,10 +341,6 @@ def __init__(self, name, markdown):
291341
for x in markdown.split("<!-- TAGS:")[-1].split("-->")[0].split(",")
292342
]
293343

294-
self.date_and_tags_html = ""
295-
if self.date:
296-
self.date_and_tags_html = f"<span class='post-date-tags'>{', '.join(self.tags)}&nbsp;&nbsp;-&nbsp;&nbsp;{self.date}</span>"
297-
298344
self.thumbnail = None
299345
for fname in ["thumbs/" + self.name + ".jpg"]:
300346
if os.path.isfile(os.path.join(THIS_DIR, "static", fname)):
@@ -386,7 +432,7 @@ def to_html(self):
386432
)
387433
ts = title_short.lower().replace(" ", "-")
388434
if part[0] == 1:
389-
htmlparts.append(self.date_and_tags_html)
435+
htmlparts.append(f"<span class='post-date-tags'>{self.date}</span>")
390436
htmlparts.append("<h1>%s</h1>" % title_html)
391437
elif part[0] == 2 and title_short:
392438
htmlparts.append(

pages/index.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
<center>
23
<img src='pygfx1024.png' width='96px' height='96px' />
34
<span style='font-size:100px; position: relative; top: -20px; left: 10px;'>Pygfx</span><br>
@@ -17,7 +18,6 @@ Pygfx (py-graphics) is built on WebGPU, enabling superior performance and reliab
1718
<a class='button' href='https://pygfx.readthedocs.io/stable/_gallery/index.html'><i class='fas'></i> Gallery</a>
1819
<a class='button' href='https://pygfx.readthedocs.io'><i class='fas'></i> Documentation</a>
1920
<a class='button yellow' href='sponsor.html'><i class='fas'></i> Support & Sponsoring</a>
20-
2121
</center>
2222

2323

@@ -36,10 +36,13 @@ Pygfx (py-graphics) is built on WebGPU, enabling superior performance and reliab
3636

3737
<script>
3838

39-
var release_infos = [];
39+
var recent_blog_posts = [];
40+
41+
var news_items = [];
42+
4043

4144
async function get_release_info(repo) {
42-
let url = "https://api.github.com/repos/" + repo + "/releases?per_page=2";
45+
let url = "https://api.github.com/repos/" + repo + "/releases?per_page=1";
4346
try {
4447
let response = await fetch(url);
4548
if (!response.ok) {
@@ -69,14 +72,18 @@ function show_news() {
6972
news_div.innerHTML = "";
7073
let ul = document.createElement("ul");
7174
news_div.appendChild(ul);
72-
for (let release of release_infos) {
73-
// let d = release.date.toUTCString().split(" ").slice(0, 4).join(" ");
74-
let d = release.date.toISOString().split("T")[0].split("-").reverse().join("-")
75+
for (let news_item of news_items) {
76+
// let d = news_item.date.toUTCString().split(" ").slice(0, 4).join(" ");
77+
let d = news_item.date.toISOString().split("T")[0].split("-").reverse().join("-")
7578
let li = document.createElement("li");
76-
li.innerHTML = "<code>" + d + "</code> Release " + release.name + " <a href='" + release.url + "'>" + release.tag + "</a>"
79+
if (news_item.tag) {
80+
li.innerHTML = "<code>" + d + "</code> Release " + news_item.name + " <a href='" + news_item.url + "'>" + news_item.tag + "</a>"
81+
} else {
82+
li.innerHTML = "<code>" + d + "</code> Blog post <a href='" + news_item.url + "'>" + news_item.title + "</a>"
83+
}
7784
ul.appendChild(li);
7885
}
79-
for (let i=release_infos.length; i<6; i++) {
86+
for (let i=news_items.length; i<6; i++) {
8087
let li = document.createElement("li");
8188
li.innerHTML = "..."
8289
ul.appendChild(li);
@@ -85,19 +92,23 @@ function show_news() {
8592

8693

8794
async function create_news() {
88-
let repos = ["pygfx/pygfx", "pygfx/wgpu-py", "pygfx/rendercanvas"];
89-
let releases = [];
95+
let repos = ["pygfx/pygfx", "pygfx/wgpu-py", "pygfx/rendercanvas", "pygfx/pylinalg"];
96+
let pending_news_items = [];
9097
for (let repo of repos) {
9198
let repo_releases = await get_release_info(repo);
92-
releases.push(...repo_releases);
99+
pending_news_items.push(...repo_releases);
100+
}
101+
for (let post of recent_blog_posts) {
102+
post.date = new Date(post.date);
103+
pending_news_items.push(post)
93104
}
94105

95-
releases.sort((a, b) => (a.date < b.date));
106+
pending_news_items.sort((a, b) => (a.date < b.date));
96107

97108
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
98109

99-
for (let release of releases) {
100-
release_infos.push(release);
110+
for (let news_item of pending_news_items) {
111+
news_items.push(news_item);
101112
show_news()
102113
await sleep(200);
103114
}

posts/report001.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Looking back at 2024
2+
3+
<!-- DATE: 2025-01-16 -->
4+
<!-- AUTHOR: Almar -->
5+
6+
In this first post I look back at what we achieved in 2024.
7+
8+
<!-- END_SUMMARY -->
9+
10+
11+
## Some history
12+
13+
Korijn and I (Almar) have been working on Pygfx since 2020.
14+
At the time, the development of WebGPU and ([wgpu-native](https://github.com/gfx-rs/wgpu-native)) was just getting started. Both WebGPU and Pygfx have come a long way since then.
15+
In an earlier [post](https://almarklein.org/wgpu.html) I describe the origins of Pygfx from a more technical perspective.
16+
17+
For several years, our work was funded by the company that Korijn was
18+
employed with. But this ended in 2024 when his division was disbanded for
19+
corporate reasons.
20+
21+
To keep Pygfx going, we came up with the sponsorship model presented at this website.
22+
Since we (the developers of Pygfx) now need to answer to multiple stakeholders,
23+
it seems like a good idea to write regular status reports.
24+
25+
The idea of posts like this, is to provide insight into the
26+
higher level perspective, design decisions, priorities, and
27+
achievements. I plan to write one every one or two months or so.
28+
29+
30+
## Overview of 2024
31+
32+
From the start of 2024, our goal was to move to a "1.0 release". With
33+
this we mean that we wanted to implement crucial features, and address
34+
the issues/features that would introduce backwards incompatibilities.
35+
We summarized this in [issue #932](https://github.com/pygfx/pygfx/issues/392).
36+
37+
I'll briefly discuss the biggest chunks of work that we did in 2024.
38+
39+
40+
### Buffer and texture updates
41+
42+
In [pr #795](https://github.com/pygfx/pygfx/pull/795) and later PR's, we
43+
refactored large parts of the buffer and texture objects for more efficient
44+
updates. Later we introduced a way to send data to a buffer/texture without
45+
it being stored on the CPU in [pr #879](https://github.com/pygfx/pygfx/pull/879).
46+
47+
48+
### Async support
49+
50+
Async is a tricky topic. The WebGPU API specifies some functions as being
51+
asynchronous. The main reason for wanting to adhere to this, is
52+
that it would allow us to use JavaScript's WebGPU API, which is necessary
53+
if we ever want to support Pyodide/PyScript and run in the browser.
54+
55+
The tricky part is that async code tends to force other code to be async too.
56+
And we did not want to force async on our users.
57+
In [wgpu-py](https://github.com/pygfx/wgpu-py/) we addressed this by providing both
58+
sync and async flavours for such functions.
59+
In the [rendercanvas](https://github.com/pygfx/rendercanvas) we implemented an
60+
awesome event-loop mechanism that supports both sync and async callbacks.
61+
62+
What's left (to do in 2025) is to combine this in Pygfx.
63+
64+
65+
### Rendercanvas
66+
67+
This year we also moved the GUI subpackage out of wgpu-py, into its own library: `rendercanvas`.
68+
The subpackage was getting more complex, and giving it its own repo gives it room
69+
to grow.
70+
71+
The purpose or `rendercanvas` is to provide a canvas/window/widget to
72+
render to with WebGPU. Improvements made since it has its own repo include: support for
73+
Asyncio and Trio, more consistent behavior across backends, async
74+
support, and sigint handling.
75+
76+
77+
### Update propagation
78+
79+
In [issue #495](https://github.com/pygfx/pygfx/issues/495) we outlined a set of changes related to how things react to changes and user input. The plan touches the event system in `rendercanvas`, and how to use this in Pygfx.
80+
Part of this plan is to better separate rendering from handling updates. The renderer and world objects
81+
should not participate in the event system.
82+
83+
This is an ongoing effort, with most of the work done in `rendercanvas`,
84+
but quite some work ahead in Pygfx. The refactoring of the renderer / viewport as part of [issue #492](https://github.com/pygfx/pygfx/issues/492) is closely related to this work.
85+
86+
87+
### Plotting support
88+
89+
We implemented improvements to text, ticks (`Ruler` object), and grids.
90+
The viewport work is also a prerequisite for proper support for subplots.
91+
92+
93+
### Mesh rendering
94+
95+
While Korijn and I focussed on the above, Pan has made a lot of contributions
96+
related to advanced mesh rendering, like environment maps, PBR materials, animations,
97+
compliance with the gltf format, and more.
98+
99+
100+
## Up next
101+
102+
In the [next post](report002.html) I talk about what we plan to work on in 2025.

0 commit comments

Comments
 (0)