Keychron Keyboards Hardware Design

作者 reason-machines2384a003145a無授權條款83 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 個月前更新

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.

AI 產生的概覽

指導使用 Keychron 開源的鍵盤與滑鼠 CAD 檔案,包含清單指令碼與 3D 列印。

功能
此技能說明如何取得與瀏覽 Keychron 的開源硬體設計儲存庫,其中包含鍵盤與滑鼠外殼、定位板、衛星軸、旋鈕與鍵帽的 STEP、DXF、DWG 及 PDF 檔案。它提供 Python 程式碼片段,用於建立檔案清單、尋找定位板或型號檔案、匯出 CSV 中繼資料、檢查命名規範、解析 STEP 檔頭,以及彙整 DXF 幾何資訊。此外也介紹在 FreeCAD 中開啟 STEP 檔案、將 STEP 轉換為 STL、使用 CadQuery 檢查幾何形狀,以及材料、公差與比例等 3D 列印建議。
適用情境
當你需要尋找、清點或檢查 Keychron CAD 設計檔案,或準備將這些模型用於雷射切割、CNC 加工或 3D 列印時使用。也適合希望在不開啟 CAD 軟體的情況下,透過指令碼從 STEP 或 DXF 檔案擷取中繼資料的使用者。
執行需求
需要 Keychron 硬體設計儲存庫,可透過 git clone 或稀疏檢出取得,並需要網路存取以完成複製。選用的 Python 套件包括 cadquery 與 ezdxf;STEP 匯入與 STL 轉換需要安裝含 Python 綁定的 FreeCAD。此技能未附帶指令碼,所有程式碼皆以範例形式內嵌。

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 — STEP (ISO 10303) 3D solid models, importable in FreeCAD, Fusion 360, SolidWorks, CATIA, etc.
  • .dxf — 2D Drawing Exchange Format, used for plates and cutouts in laser cutting or CNC workflows
  • .dwg — AutoCAD native drawing format
  • .pdf — Dimensioned engineering drawings for reference

Series covered:

SeriesNotable Models
Q SeriesQ1–Q12, Q0 Plus, Q60, Q65
Q Pro SeriesQ1 Pro–Q14 Pro
Q HE SeriesQ1 HE, Q3 HE, Q5 HE, Q6 HE
K Pro SeriesK1 Pro–K17 Pro
K Max SeriesK1 Max–K17 Max
K HE SeriesK2 HE–K10 HE
L SeriesL1, L3
V Max SeriesV1 Max–V10 Max
P HE SeriesP1 HE
MiceM1–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

bash
git clone https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.gitcd Keychron-Keyboards-Hardware-Design

Sparse checkout (single model, saves bandwidth)

bash
git clone --filter=blob:none --sparse https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.gitcd Keychron-Keyboards-Hardware-Designgit sparse-checkout set "Q-Series/Q1"

Sparse checkout for a full series

bash
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.stpQ-Pro-Series/  Q1 Pro/    Q1-Pro-Case.stp    Q1-Pro-Plate.dxf    ...K-Pro-Series/  K6 Pro/  K8 Pro/    K8-Pro-Keycap.stpV-Max-Series/  V1 Max/K-Max-Series/  K8 Max/    K8-Max-Keycap.stpK-HE-Series/  K2 HE/    K2-HE-Cherry-Keycap.stp    K2-HE-OSA-Keycap.stpMice/  M1/    M1-Shell.stp    M1-Full-Model.stpKeycap 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

python
"""inventory.py — Scan the repo and produce a structured inventory of all design files."""import osimport jsonfrom pathlib import Pathfrom collections import defaultdict
REPO_ROOT = Path(__file__).parent  # adjust if running from elsewhereSUPPORTED_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:

bash
python inventory.py

Find all plate files (DXF) for laser cutting

python
"""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

python
"""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 sysfrom 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

python
"""export_csv.py — Export a CSV of all design files with metadata."""import csvfrom 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

python
"""validate_naming.py — Check that files follow Keychron naming conventions.Expected pattern: <ModelName>-<Component>.<ext>Example: Q8-Plate.stp, K8-Pro-Case.dxf"""import refrom 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)

python
"""parse_step_header.py — Extract header metadata from STEP files.STEP files contain an ASCII header with product name, author, and schema info."""import refrom 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)

python
"""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 FreeCADimport 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)

python
"""step_to_stl.py — Batch convert STEP files to STL for 3D printing.Requires FreeCAD's Python bindings."""import sysfrom pathlib import Path
sys.path.append("/usr/lib/freecad/lib")  # adjust for your systemimport FreeCADimport Partimport 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

bash
pip install cadquery
python
"""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 cqfrom 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

python
"""dxf_summary.py — Parse DXF files and report basic geometry stats."""import ezdxf  # pip install ezdxffrom 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

ProblemSolution
STEP file won't openEnsure your CAD software supports AP214 or AP242. FreeCAD, Fusion 360, and SolidWorks all do.
DXF opens with wrong scaleCheck units — DXF may be in mm or inches. Set your software to mm.
File too large to openUse sparse checkout to get only the model you need. Large assemblies can be 50–200 MB.
3D print doesn't fitAdd 0.1–0.2mm tolerance — production files are exact CNC dimensions.
Missing files for a modelCheck the repo's open issues or docs/repo-inventory.md. Some models are still being uploaded.
Git clone is slowUse --filter=blob:none --sparse (see above) to avoid downloading all binary files.
cadquery import errorEnsure you have cadquery installed: pip install cadquery. On Apple Silicon, use conda: conda install -c cadquery cadquery.

Key Reference Docs in the Repository

  • docs/file-format-guide.md — How to open STEP, DWG, DXF, and PDF files
  • docs/getting-started.md — First-stop guide for browsing and remixing
  • docs/3d-printing-guide.md — Practical printing guidance
  • docs/repo-inventory.md — Auto-generated filesystem inventory
  • docs/license-faq.md — What you can and cannot do with these files
  • CONTRIBUTING.md — Workflow, file standards, and submission rules

License Summary

UseAllowed?
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.

來源與署名

來源:reason-machines/trending-skills位於skills/keychron-keyboards-hardware-design提交2384a00

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架