Skip to content

Commit fac7dd6

Browse files
committed
add more models
1 parent 9f865a1 commit fac7dd6

5 files changed

Lines changed: 125 additions & 73 deletions

File tree

.github/badges/model-count.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schemaVersion": 1,
33
"label": "models uploaded",
4-
"message": "70",
4+
"message": "88",
55
"color": "brightgreen",
66
"namedLogo": "github"
77
}

.github/workflows/update-badge.yml

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,6 @@ name: Update Model Count Badge
33
on:
44
push:
55
branches: [main]
6-
paths:
7-
- 'Q-Series/**'
8-
- 'Q-Pro-Series/**'
9-
- 'Q-HE-Series/**'
10-
- 'K-Pro-Series/**'
11-
- 'K-Max-Series/**'
12-
- 'K-HE-Series/**'
13-
- 'V-Max-Series/**'
14-
- 'P-HE-Series/**'
15-
- 'L-Series/**'
16-
- 'Mice/**'
17-
- 'Keycap Profiles/**'
186
workflow_dispatch:
197

208
jobs:
@@ -30,29 +18,7 @@ jobs:
3018
- name: Count model directories
3119
id: count
3220
run: |
33-
# Only count actual device model directories (not Keycap Profiles)
34-
SERIES_DIRS=(
35-
"Q-Series"
36-
"Q-Pro-Series"
37-
"Q-HE-Series"
38-
"K-Pro-Series"
39-
"K-Max-Series"
40-
"K-HE-Series"
41-
"V-Max-Series"
42-
"P-HE-Series"
43-
"L-Series"
44-
"Mice"
45-
)
46-
47-
COUNT=0
48-
for dir in "${SERIES_DIRS[@]}"; do
49-
if [ -d "$dir" ]; then
50-
n=$(find "$dir" -mindepth 1 -maxdepth 1 -type d | wc -l)
51-
echo " $dir: $n models"
52-
COUNT=$((COUNT + n))
53-
fi
54-
done
55-
21+
COUNT=$(python3 scripts/repo_inventory.py count --device-only)
5622
echo "Total models: $COUNT"
5723
echo "count=$COUNT" >> "$GITHUB_OUTPUT"
5824

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Study real CAD. Remix plates and cases. Design compatible accessories. Learn fro
2626
If you're new, begin with one of these paths:
2727

2828
- **Browse keyboard files**
29-
Explore Q, Q Pro, Q HE, K Pro, K Max, K HE, V Max, and P HE models.
29+
Explore C Pro, Q, Q Pro, Q HE, Q Max, Q Ultra 8K, K Pro, K Max, K HE, V Max, and P HE models.
3030

3131
- **Browse mouse files**
3232
Explore shell and full-model files for M and G series mice.
@@ -61,25 +61,34 @@ If you're new, begin with one of these paths:
6161

6262
| Series | Type | Models | Components |
6363
|---|---|---|---|
64+
| **C Pro Series** | Keyboard | C3 Pro | Plate, Full Model |
6465
| **Q Series** | Keyboard | Q0 Plus, Q1–Q12, Q60, Q65 | Case, Plate, Encoder, Full Model, Stabilizer, OSA Keycap |
65-
| **Q Pro Series** | Keyboard | Q1 Pro–Q14 Pro | Case, Plate, Encoder, Full Model, Stabilizer, KSA Keycap |
66-
| **Q HE Series** | Hall Effect | Q1 HE, Q3 HE, Q5 HE, Q6 HE | Plate, Full Model |
66+
| **Q Pro Series** | Keyboard | Q1 Pro–Q14 Pro (10 models) | Case, Plate, Encoder, Full Model, Stabilizer, KSA Keycap |
67+
| **Q HE Series** | Hall Effect | Q1 HE, Q3 HE, Q5 HE, Q6 HE, Q12 HE | Plate, Full Model, selected case parts |
68+
| **Q Max Series** | Keyboard | Q6 Max | Case, Plate, Full Model, Stabilizer |
69+
| **Q Ultra 8K Series** | Keyboard | Q6 Ultra 8K | Plate |
6770
| **K Pro Series** | Keyboard | K1 Pro–K17 Pro (16 models) | Case, Plate, Full Model, Stabilizer |
68-
| **K Max Series** | Keyboard | K1 Max–K17 Max (11 models) | Case, Plate, Full Model, Stabilizer |
71+
| **K Max Series** | Keyboard | K0 Max, K1 Max–K17 Max (12 models) | Case, Plate, Full Model, Stabilizer, Keycap on selected models |
6972
| **K HE Series** | Hall Effect | K2 HE–K10 HE | Case, Plate, Full Model, Stabilizer, Keycap (K2 HE; other models pending) |
7073
| **L Series** | Keyboard | L1, L3 | Case, Plate, Knob, Full Model, Stabilizer |
7174
| **V Max Series** | Keyboard | V1 Max–V10 Max | Case, Plate, Encoder, Full Model, Stabilizer, OSA Keycap |
72-
| **P HE Series** | Hall Effect | P1 HE | Case, Plate, Full Model, Stabilizer |
75+
| **P HE Series** | Hall Effect | P1 HE | Case, Plate, Full Model, Stabilizer, Keycap |
7376
| **Mouse Series** | Mouse | M1–M7, G1, G2 (11 models) | Shell, Full Model |
7477

75-
**83 device models. 640+ design files. Source-available. Accessory-friendly.**
78+
**88 device models. 686+ design files. Source-available. Accessory-friendly.**
7679
![Keychron keyboards structure design](docs/assets/keychron-keyboards-structures.webp)
7780

7881
## Directory Structure
7982

8083
```
84+
C-Pro-Series/
85+
C3 Pro/ — Plate and full-model files for the C3 Pro
8186
Q-Series/
8287
Q1/ — Case, plate, encoder, stabilizer, full model
88+
Q-Max-Series/
89+
Q6 Max/ — Wireless Q-series case, plate, full model, and stabilizer files
90+
Q-Ultra-8K-Series/
91+
Q6 Ultra 8K/ — Ultra 8K plate files
8392
Q-Pro-Series/
8493
Q1 Pro/ — Wireless Q-series hardware files
8594
K-Pro-Series/

docs/repo-inventory.md

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,37 @@
22

33
Generated from the current filesystem using `scripts/repo_inventory.py`.
44

5-
- Total model directories: **89**
6-
- Total data files across model directories: **643**
5+
- Total model directories: **94**
6+
- Total data files across model directories: **686**
77

88
## Series Summary
99

1010
| Series | Models | Data Files |
1111
|---|---:|---:|
12+
| C-Pro-Series | 1 | 4 |
1213
| Q-Series | 15 | 147 |
1314
| Q-Pro-Series | 10 | 86 |
14-
| Q-HE-Series | 4 | 18 |
15+
| Q-HE-Series | 5 | 22 |
16+
| Q-Max-Series | 1 | 10 |
17+
| Q-Ultra-8K-Series | 1 | 4 |
1518
| K-Pro-Series | 16 | 129 |
16-
| K-Max-Series | 11 | 122 |
17-
| K-HE-Series | 5 | 11 |
19+
| K-Max-Series | 12 | 130 |
20+
| K-HE-Series | 5 | 19 |
1821
| V-Max-Series | 8 | 94 |
1922
| P-HE-Series | 1 | 9 |
2023
| L-Series | 2 | 16 |
2124
| Mice | 11 | 11 |
22-
| Keycap Profiles | 6 | 0 |
25+
| Keycap Profiles | 6 | 5 |
2326

2427
## Per-Model Manifests
2528

29+
### C-Pro-Series / C3 Pro
30+
31+
- Path: `C-Pro-Series/C3 Pro`
32+
- Data files: 4
33+
- README present: no
34+
- Extensions: `.dwg` x1, `.pdf` x1, `.stp` x2
35+
2636
### Q-Series / Q0 Plus
2737

2838
- Path: `Q-Series/Q0 Plus`
@@ -205,6 +215,13 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
205215
- README present: yes
206216
- Extensions: `.dwg` x2, `.md` x1, `.pdf` x2, `.stp` x2
207217

218+
### Q-HE-Series / Q12 HE
219+
220+
- Path: `Q-HE-Series/Q12 HE`
221+
- Data files: 4
222+
- README present: no
223+
- Extensions: `.stp` x4
224+
208225
### Q-HE-Series / Q3 HE
209226

210227
- Path: `Q-HE-Series/Q3 HE`
@@ -226,6 +243,20 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
226243
- README present: yes
227244
- Extensions: `.dwg` x1, `.md` x1, `.pdf` x1, `.stp` x2
228245

246+
### Q-Max-Series / Q6 Max
247+
248+
- Path: `Q-Max-Series/Q6 Max`
249+
- Data files: 10
250+
- README present: no
251+
- Extensions: `.dwg` x2, `.pdf` x2, `.stp` x5, `.zip` x1
252+
253+
### Q-Ultra-8K-Series / Q6 Ultra 8k
254+
255+
- Path: `Q-Ultra-8K-Series/Q6 Ultra 8k`
256+
- Data files: 4
257+
- README present: no
258+
- Extensions: `.dwg` x2, `.pdf` x2
259+
229260
### K-Pro-Series / K1 Pro
230261

231262
- Path: `K-Pro-Series/K1 Pro`
@@ -338,6 +369,13 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
338369
- README present: yes
339370
- Extensions: `.dwg` x1, `.md` x1, `.pdf` x1, `.stp` x3, `.zip` x1
340371

372+
### K-Max-Series / K0 Max
373+
374+
- Path: `K-Max-Series/K0 Max`
375+
- Data files: 8
376+
- README present: no
377+
- Extensions: `.dwg` x1, `.pdf` x1, `.stp` x6
378+
341379
### K-Max-Series / K1 Max
342380

343381
- Path: `K-Max-Series/K1 Max`
@@ -418,9 +456,9 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
418456
### K-HE-Series / K10 HE
419457

420458
- Path: `K-HE-Series/K10 HE`
421-
- Data files: 0
459+
- Data files: 8
422460
- README present: yes
423-
- Extensions: `.md` x1
461+
- Extensions: `.dwg` x1, `.md` x1, `.pdf` x1, `.stp` x6
424462

425463
### K-HE-Series / K2 HE
426464

@@ -607,16 +645,16 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
607645
### Keycap Profiles / Cherry Profile
608646

609647
- Path: `Keycap Profiles/Cherry Profile`
610-
- Data files: 0
648+
- Data files: 1
611649
- README present: yes
612-
- Extensions: `.md` x1
650+
- Extensions: `.md` x1, `.stp` x1
613651

614652
### Keycap Profiles / KSA Profile
615653

616654
- Path: `Keycap Profiles/KSA Profile`
617-
- Data files: 0
655+
- Data files: 1
618656
- README present: yes
619-
- Extensions: `.md` x1
657+
- Extensions: `.md` x1, `.stp` x1
620658

621659
### Keycap Profiles / LSA Profile
622660

@@ -628,21 +666,21 @@ Generated from the current filesystem using `scripts/repo_inventory.py`.
628666
### Keycap Profiles / MDA Profile
629667

630668
- Path: `Keycap Profiles/MDA Profile`
631-
- Data files: 0
669+
- Data files: 1
632670
- README present: yes
633-
- Extensions: `.md` x1
671+
- Extensions: `.md` x1, `.stp` x1
634672

635673
### Keycap Profiles / OEM Profile
636674

637675
- Path: `Keycap Profiles/OEM Profile`
638-
- Data files: 0
676+
- Data files: 1
639677
- README present: yes
640-
- Extensions: `.md` x1
678+
- Extensions: `.md` x1, `.stp` x1
641679

642680
### Keycap Profiles / OSA Profile
643681

644682
- Path: `Keycap Profiles/OSA Profile`
645-
- Data files: 0
683+
- Data files: 1
646684
- README present: yes
647-
- Extensions: `.md` x1
685+
- Extensions: `.md` x1, `.stp` x1
648686

scripts/repo_inventory.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212

1313

1414
REPO_ROOT = Path(__file__).resolve().parent.parent
15-
SERIES_ORDER = [
15+
PREFERRED_SERIES_ORDER = [
16+
"C-Pro-Series",
1617
"Q-Series",
1718
"Q-Pro-Series",
1819
"Q-HE-Series",
20+
"Q-Max-Series",
21+
"Q-Ultra-8K-Series",
1922
"K-Pro-Series",
2023
"K-Max-Series",
2124
"K-HE-Series",
@@ -25,21 +28,41 @@
2528
"Mice",
2629
"Keycap Profiles",
2730
]
31+
IGNORED_TOP_LEVEL_DIRS = {"docs", "scripts"}
2832

2933

30-
def iter_series():
31-
for name in SERIES_ORDER:
32-
path = REPO_ROOT / name
33-
if path.is_dir():
34-
yield path
34+
def iter_series() -> list[Path]:
35+
preferred_index = {
36+
name: index for index, name in enumerate(PREFERRED_SERIES_ORDER)
37+
}
38+
discovered = []
39+
for path in REPO_ROOT.iterdir():
40+
if not path.is_dir():
41+
continue
42+
if path.name.startswith(".") or path.name in IGNORED_TOP_LEVEL_DIRS:
43+
continue
44+
if any(child.is_dir() and not child.name.startswith(".") for child in path.iterdir()):
45+
discovered.append(path)
46+
return sorted(
47+
discovered,
48+
key=lambda path: (preferred_index.get(path.name, len(preferred_index)), path.name),
49+
)
3550

3651

3752
def series_model_dirs(series_path: Path) -> list[Path]:
38-
return sorted(path for path in series_path.iterdir() if path.is_dir())
53+
return sorted(
54+
path
55+
for path in series_path.iterdir()
56+
if path.is_dir() and not path.name.startswith(".")
57+
)
3958

4059

4160
def manifest_for_model(model_path: Path) -> dict[str, object]:
42-
files = sorted(path for path in model_path.iterdir() if path.is_file())
61+
files = sorted(
62+
path
63+
for path in model_path.iterdir()
64+
if path.is_file() and not path.name.startswith(".")
65+
)
4366
ext_counts = Counter(path.suffix.lower() or "[noext]" for path in files)
4467
readme_present = any(path.name.lower() == "readme.md" for path in files)
4568
data_files = [path for path in files if path.name.lower() != "readme.md"]
@@ -88,6 +111,14 @@ def collect_inventory() -> dict[str, object]:
88111
}
89112

90113

114+
def device_model_count(inventory: dict[str, object]) -> int:
115+
return sum(
116+
entry["model_count"]
117+
for entry in inventory["series"]
118+
if entry["series"] != "Keycap Profiles"
119+
)
120+
121+
91122
def render_summary_markdown(inventory: dict[str, object]) -> str:
92123
lines = [
93124
"# Repository Inventory",
@@ -136,11 +167,7 @@ def validate_readme(inventory: dict[str, object], readme_path: Path) -> list[str
136167
errors = []
137168

138169
# Count device models (exclude Keycap Profiles which are documentation-only)
139-
device_models = sum(
140-
entry["model_count"]
141-
for entry in inventory["series"]
142-
if entry["series"] != "Keycap Profiles"
143-
)
170+
device_models = device_model_count(inventory)
144171

145172
# Check badge -- either static URL or dynamic endpoint
146173
badge_match = re.search(r"models%20uploaded-(\d+)-", text)
@@ -220,6 +247,13 @@ def build_parser() -> argparse.ArgumentParser:
220247
help="README path to validate.",
221248
)
222249

250+
count = subparsers.add_parser("count", help="Print the current model count.")
251+
count.add_argument(
252+
"--device-only",
253+
action="store_true",
254+
help="Exclude Keycap Profiles from the total.",
255+
)
256+
223257
return parser
224258

225259

@@ -239,6 +273,11 @@ def main() -> int:
239273
sys.stdout.write(output)
240274
return 0
241275

276+
if args.command == "count":
277+
count = device_model_count(inventory) if args.device_only else inventory["total_models"]
278+
print(count)
279+
return 0
280+
242281
errors = validate_readme(inventory, args.readme)
243282
if errors:
244283
for error in errors:

0 commit comments

Comments
 (0)