Trending-skills keychron-keyboards-hardware-design
Work with Keychron's source-available CAD hardware design files (STEP/DXF/PDF) for keyboards and mice, including scripting, inventory management, and 3D printing workflows.
install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/keychron-keyboards-hardware-design" ~/.claude/skills/aradotso-trending-skills-keychron-keyboards-hardware-design && rm -rf "$T"
manifest:
skills/keychron-keyboards-hardware-design/SKILL.mdsource content
Keychron Keyboards Hardware Design
Skill by ara.so — Daily 2026 Skills collection.
What This Project Is
Keychron's
Keychron-Keyboards-Hardware-Design repository provides production-grade industrial design files for Keychron keyboards and mice. These are real CAD files — not approximations — covering cases, plates, stabilizers, encoders, keycaps, and full assembly models.
Formats included:
/.stp
— STEP (ISO 10303) 3D solid models, importable in FreeCAD, Fusion 360, SolidWorks, CATIA, etc..step
— 2D Drawing Exchange Format, used for plates and cutouts in laser cutting or CNC workflows.dxf
— AutoCAD native drawing format.dwg
— Dimensioned engineering drawings for reference.pdf
Series covered:
| Series | Notable Models |
|---|---|
| Q Series | Q1–Q12, Q0 Plus, Q60, Q65 |
| Q Pro Series | Q1 Pro–Q14 Pro |
| Q HE Series | Q1 HE, Q3 HE, Q5 HE, Q6 HE |
| K Pro Series | K1 Pro–K17 Pro |
| K Max Series | K1 Max–K17 Max |
| K HE Series | K2 HE–K10 HE |
| L Series | L1, L3 |
| V Max Series | V1 Max–V10 Max |
| P HE Series | P1 HE |
| Mice | M1–M7, G1, G2 |
License: Source-available. Personal, educational, and non-commercial use only. Commercial use is strictly prohibited.
Getting the Files
Clone the full repository
git clone https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.git cd Keychron-Keyboards-Hardware-Design
Sparse checkout (single model, saves bandwidth)
git clone --filter=blob:none --sparse https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.git cd Keychron-Keyboards-Hardware-Design git sparse-checkout set "Q-Series/Q1"
Sparse checkout for a full series
git sparse-checkout set "K-Pro-Series"
Directory Layout
Q-Series/ Q1/ Q1-Case.stp Q1-Plate.stp Q1-Encoder.stp Q1-Stabilizer.stp Q1-Full-Model.stp Q1-OSA-Keycap.stp Q-Pro-Series/ Q1 Pro/ Q1-Pro-Case.stp Q1-Pro-Plate.dxf ... K-Pro-Series/ K6 Pro/ K8 Pro/ K8-Pro-Keycap.stp V-Max-Series/ V1 Max/ K-Max-Series/ K8 Max/ K8-Max-Keycap.stp K-HE-Series/ K2 HE/ K2-HE-Cherry-Keycap.stp K2-HE-OSA-Keycap.stp Mice/ M1/ M1-Shell.stp M1-Full-Model.stp Keycap Profiles/ OSA Profile/ KSA Profile/ docs/ file-format-guide.md getting-started.md 3d-printing-guide.md repo-inventory.md license-faq.md
Python Scripts for Working With the Repository
Inventory all design files
""" inventory.py — Scan the repo and produce a structured inventory of all design files. """ import os import json from pathlib import Path from collections import defaultdict REPO_ROOT = Path(__file__).parent # adjust if running from elsewhere SUPPORTED_EXTENSIONS = {".stp", ".step", ".dxf", ".dwg", ".pdf"} def build_inventory(root: Path) -> dict: inventory = defaultdict(lambda: defaultdict(list)) for path in sorted(root.rglob("*")): if path.suffix.lower() in SUPPORTED_EXTENSIONS: # Series = top-level folder; model = second-level folder parts = path.relative_to(root).parts series = parts[0] if len(parts) > 0 else "Unknown" model = parts[1] if len(parts) > 1 else "Root" inventory[series][model].append({ "file": path.name, "format": path.suffix.lower().lstrip(".").upper(), "size_kb": round(path.stat().st_size / 1024, 1), "path": str(path.relative_to(root)), }) return inventory def print_summary(inventory: dict): total_files = 0 for series, models in inventory.items(): series_count = sum(len(files) for files in models.values()) total_files += series_count print(f"\n{series} ({len(models)} models, {series_count} files)") for model, files in models.items(): formats = sorted({f["format"] for f in files}) print(f" {model}: {len(files)} files [{', '.join(formats)}]") print(f"\nTotal: {total_files} design files across {len(inventory)} series") if __name__ == "__main__": inv = build_inventory(REPO_ROOT) print_summary(inv) # Optional: dump to JSON with open("inventory.json", "w") as f: json.dump(inv, f, indent=2) print("\nInventory saved to inventory.json")
Run it:
python inventory.py
Find all plate files (DXF) for laser cutting
""" find_plates.py — List all DXF plate files suitable for laser cutting or CNC. """ from pathlib import Path REPO_ROOT = Path(".") def find_plates(root: Path): results = [] for path in sorted(root.rglob("*.dxf")): name_lower = path.name.lower() if "plate" in name_lower: results.append(path) return results if __name__ == "__main__": plates = find_plates(REPO_ROOT) print(f"Found {len(plates)} plate DXF files:\n") for p in plates: print(f" {p}")
Search for a specific model's files
""" find_model.py — Find all files for a given keyboard model. Usage: python find_model.py "Q8" python find_model.py "K8 Pro" python find_model.py "M3" """ import sys from pathlib import Path REPO_ROOT = Path(".") def find_model_files(root: Path, query: str): query_lower = query.lower().replace(" ", "") matches = [] for path in sorted(root.rglob("*")): if path.is_file(): # Check folder name or filename normalized = str(path).lower().replace(" ", "").replace("-", "") if query_lower.replace("-", "") in normalized: matches.append(path) return matches if __name__ == "__main__": query = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Q8" results = find_model_files(REPO_ROOT, query) if not results: print(f"No files found matching '{query}'") else: print(f"Files matching '{query}':\n") for r in results: print(f" {r}")
Export an inventory CSV for spreadsheet use
""" export_csv.py — Export a CSV of all design files with metadata. """ import csv from pathlib import Path REPO_ROOT = Path(".") OUTPUT = Path("keychron_inventory.csv") SUPPORTED_EXTENSIONS = {".stp", ".step", ".dxf", ".dwg", ".pdf"} def export_csv(root: Path, output: Path): rows = [] for path in sorted(root.rglob("*")): if path.suffix.lower() in SUPPORTED_EXTENSIONS: parts = path.relative_to(root).parts series = parts[0] if len(parts) > 0 else "" model = parts[1] if len(parts) > 1 else "" component = _infer_component(path.name) rows.append({ "series": series, "model": model, "component": component, "filename": path.name, "format": path.suffix.lower().lstrip(".").upper(), "size_kb": round(path.stat().st_size / 1024, 1), "relative_path": str(path.relative_to(root)), }) with open(output, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=rows[0].keys()) writer.writeheader() writer.writerows(rows) print(f"Exported {len(rows)} rows to {output}") def _infer_component(filename: str) -> str: name = filename.lower() for keyword in ["case", "plate", "encoder", "stabilizer", "keycap", "full-model", "full_model", "shell", "knob"]: if keyword.replace("-", "") in name.replace("-", "").replace("_", ""): return keyword.replace("-", " ").title() return "Other" if __name__ == "__main__": export_csv(REPO_ROOT, OUTPUT)
Validate repo file naming conventions
""" validate_naming.py — Check that files follow Keychron naming conventions. Expected pattern: <ModelName>-<Component>.<ext> Example: Q8-Plate.stp, K8-Pro-Case.dxf """ import re from pathlib import Path REPO_ROOT = Path(".") SUPPORTED_EXTENSIONS = {".stp", ".step", ".dxf", ".dwg", ".pdf"} NAMING_PATTERN = re.compile( r"^[A-Z0-9][A-Za-z0-9\s\-]+-" r"(Case|Plate|Encoder|Stabilizer|Keycap|Full.Model|Shell|Knob|Knob.*)" r"\.(stp|step|dxf|dwg|pdf)$", re.IGNORECASE, ) issues = [] for path in sorted(REPO_ROOT.rglob("*")): if path.suffix.lower() in SUPPORTED_EXTENSIONS: if not NAMING_PATTERN.match(path.name): issues.append(str(path.relative_to(REPO_ROOT))) if issues: print(f"Files with non-standard names ({len(issues)}):\n") for i in issues: print(f" {i}") else: print("All files follow naming conventions.")
Parse STEP file metadata (no CAD software required)
""" parse_step_header.py — Extract header metadata from STEP files. STEP files contain an ASCII header with product name, author, and schema info. """ import re from pathlib import Path def parse_step_header(filepath: Path) -> dict: metadata = {} try: with open(filepath, "r", encoding="utf-8", errors="ignore") as f: header_lines = [] in_header = False for line in f: if "ISO-10303-21" in line or "HEADER;" in line: in_header = True if in_header: header_lines.append(line.strip()) if "ENDSEC;" in line and in_header: break header_text = " ".join(header_lines) # FILE_DESCRIPTION desc_match = re.search(r"FILE_DESCRIPTION\s*\(\s*\('([^']+)'", header_text) if desc_match: metadata["description"] = desc_match.group(1) # FILE_NAME — product name, timestamp, author name_match = re.search(r"FILE_NAME\s*\(\s*'([^']+)'", header_text) if name_match: metadata["file_name"] = name_match.group(1) # FILE_SCHEMA schema_match = re.search(r"FILE_SCHEMA\s*\(\s*\('([^']+)'", header_text) if schema_match: metadata["schema"] = schema_match.group(1) except Exception as e: metadata["error"] = str(e) return metadata if __name__ == "__main__": import sys target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".") step_files = list(target.rglob("*.stp")) + list(target.rglob("*.step")) for sf in step_files[:10]: # limit to first 10 for demo meta = parse_step_header(sf) print(f"\n{sf.name}") for k, v in meta.items(): print(f" {k}: {v}")
Common Workflows
Open STEP files in FreeCAD (Python API)
""" open_in_freecad.py — Open a STEP file using FreeCAD's Python API. Requires FreeCAD to be installed and its Python path configured. """ import sys # Add FreeCAD to path (adjust for your OS and FreeCAD version) # Linux: sys.path.append("/usr/lib/freecad/lib") # macOS: # sys.path.append("/Applications/FreeCAD.app/Contents/lib") import FreeCAD import Import # FreeCAD's STEP importer module def open_step(filepath: str, output_doc_name: str = "KeychronModel"): doc = FreeCAD.newDocument(output_doc_name) Import.insert(filepath, output_doc_name) print(f"Loaded: {filepath}") print(f"Objects in document: {[obj.Label for obj in doc.Objects]}") return doc if __name__ == "__main__": step_file = "Q-Series/Q1/Q1-Case.stp" doc = open_step(step_file)
Convert STEP to STL for 3D printing (using FreeCAD headless)
""" step_to_stl.py — Batch convert STEP files to STL for 3D printing. Requires FreeCAD's Python bindings. """ import sys from pathlib import Path sys.path.append("/usr/lib/freecad/lib") # adjust for your system import FreeCAD import Part import Mesh def step_to_stl(step_path: Path, stl_path: Path, tolerance: float = 0.1): doc = FreeCAD.newDocument("Conversion") Part.insert(str(step_path), "Conversion") shape_objects = [obj for obj in doc.Objects if hasattr(obj, "Shape")] if not shape_objects: print(f"No shape objects found in {step_path.name}") return False shape = shape_objects[0].Shape mesh = doc.addObject("Mesh::Feature", "Mesh") mesh.Mesh = Mesh.Mesh(shape.tessellate(tolerance)) Mesh.export([mesh], str(stl_path)) FreeCAD.closeDocument("Conversion") print(f"Converted: {step_path.name} -> {stl_path.name}") return True if __name__ == "__main__": repo = Path(".") output_dir = Path("stl_output") output_dir.mkdir(exist_ok=True) for step_file in repo.rglob("*.stp"): stl_file = output_dir / (step_file.stem + ".stl") step_to_stl(step_file, stl_file)
Using cadquery to inspect STEP geometry
pip install cadquery
""" inspect_step.py — Load and inspect a STEP file using CadQuery. cadquery works without a GUI and is ideal for scripted geometry inspection. """ import cadquery as cq from pathlib import Path def inspect_step(filepath: str): result = cq.importers.importStep(filepath) bb = result.val().BoundingBox() print(f"File: {filepath}") print(f" Bounding box (mm):") print(f" X: {bb.xmin:.2f} → {bb.xmax:.2f} (width: {bb.xmax - bb.xmin:.2f})") print(f" Y: {bb.ymin:.2f} → {bb.ymax:.2f} (depth: {bb.ymax - bb.ymin:.2f})") print(f" Z: {bb.zmin:.2f} → {bb.zmax:.2f} (height: {bb.zmax - bb.zmin:.2f})") print(f" Faces: {result.faces().size()}") print(f" Edges: {result.edges().size()}") return result if __name__ == "__main__": import sys filepath = sys.argv[1] if len(sys.argv) > 1 else "Q-Series/Q1/Q1-Plate.stp" inspect_step(filepath)
Export DXF plate dimensions summary
""" dxf_summary.py — Parse DXF files and report basic geometry stats. """ import ezdxf # pip install ezdxf from pathlib import Path def summarize_dxf(filepath: Path): try: doc = ezdxf.readfile(str(filepath)) msp = doc.modelspace() entity_counts = {} for entity in msp: t = entity.dxftype() entity_counts[t] = entity_counts.get(t, 0) + 1 print(f"\n{filepath.name}") for etype, count in sorted(entity_counts.items()): print(f" {etype}: {count}") except Exception as e: print(f"Error reading {filepath.name}: {e}") if __name__ == "__main__": repo = Path(".") for dxf_file in sorted(repo.rglob("*.dxf")): summarize_dxf(dxf_file)
3D Printing Guidance
- Recommended material: PLA or PETG for prototyping cases and plates; ABS/ASA for structural parts requiring heat resistance
- Plate files: Use DXF for laser cutting 1.2–1.5mm steel or aluminum plates; typical MX switch cutout is 14mm × 14mm
- Case tolerances: Production tolerances in these files assume CNC machining; add 0.1–0.2mm clearance when 3D printing
- Scale: All models are in millimeters (1:1 scale). Verify scale when importing into slicers — some tools default to cm
- Orientation: Print cases with the inside face down to minimize support material
Troubleshooting
| Problem | Solution |
|---|---|
| STEP file won't open | Ensure your CAD software supports AP214 or AP242. FreeCAD, Fusion 360, and SolidWorks all do. |
| DXF opens with wrong scale | Check units — DXF may be in mm or inches. Set your software to mm. |
| File too large to open | Use sparse checkout to get only the model you need. Large assemblies can be 50–200 MB. |
| 3D print doesn't fit | Add 0.1–0.2mm tolerance — production files are exact CNC dimensions. |
| Missing files for a model | Check the repo's open issues or . Some models are still being uploaded. |
| Git clone is slow | Use (see above) to avoid downloading all binary files. |
| cadquery import error | Ensure you have installed: . On Apple Silicon, use conda: . |
Key Reference Docs in the Repository
— How to open STEP, DWG, DXF, and PDF filesdocs/file-format-guide.md
— First-stop guide for browsing and remixingdocs/getting-started.md
— Practical printing guidancedocs/3d-printing-guide.md
— Auto-generated filesystem inventorydocs/repo-inventory.md
— What you can and cannot do with these filesdocs/license-faq.md
— Workflow, file standards, and submission rulesCONTRIBUTING.md
License Summary
| Use | Allowed? |
|---|---|
| Personal study and learning | ✅ Yes |
| Non-commercial remixing and modding | ✅ Yes |
| Academic and educational use | ✅ Yes |
| Selling products derived from these files | ❌ No |
| Manufacturing for profit | ❌ No |
| Distribution of derivatives without attribution | ❌ No |
Full terms: see
LICENSE and docs/license-faq.md in the repository.