keychron-keyboards-hardware-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseKeychron Keyboards Hardware Design
Keychron键盘硬件设计
What This Project Is
项目介绍
Keychron's 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.
Keychron-Keyboards-Hardware-DesignFormats 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.
Keychron的仓库提供了Keychron键盘和鼠标的生产级工业设计文件。这些是真实的CAD文件而非近似模型,涵盖外壳、定位板、卫星轴、编码器、键帽和完整装配模型。
Keychron-Keyboards-Hardware-Design包含格式:
- /
.stp— STEP(ISO 10303)3D实体模型,可导入FreeCAD、Fusion 360、SolidWorks、CATIA等软件使用.step - — 2D图形交换格式,用于激光切割或CNC工作流中的定位板、开孔设计
.dxf - — AutoCAD原生绘图格式
.dwg - — 带标注的工程图纸,供参考使用
.pdf
覆盖系列:
| 系列 | 代表型号 |
|---|---|
| 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 |
许可证: 源码可获取,仅供个人、教育和非商业用途使用,严格禁止商用。
Getting the Files
获取文件
Clone the full repository
克隆完整仓库
bash
git clone https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.git
cd Keychron-Keyboards-Hardware-Designbash
git clone https://github.com/Keychron/Keychron-Keyboards-Hardware-Design.git
cd Keychron-Keyboards-Hardware-DesignSparse checkout (single model, saves bandwidth)
稀疏检出(单个型号,节省带宽)
bash
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"bash
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
稀疏检出完整系列
bash
git sparse-checkout set "K-Pro-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.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.mdQ-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.mdPython Scripts for Working With the Repository
仓库配套Python脚本
Inventory all design files
清点所有设计文件
python
"""
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:
bash
python inventory.pypython
"""
inventory.py — 扫描仓库,生成所有设计文件的结构化库存清单。
"""
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")运行命令:
bash
python inventory.pyFind all plate files (DXF) for laser cutting
查找所有适用于激光切割的定位板DXF文件
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}")python
"""
find_plates.py — 列出所有适合激光切割或CNC加工的DXF定位板文件。
"""
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 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}")python
"""
find_model.py — 查找指定键盘型号的所有文件。
用法:
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
导出库存CSV文件用于表格处理
python
"""
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)python
"""
export_csv.py — 导出所有设计文件及元数据为CSV格式。
"""
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
验证仓库文件命名规范
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 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.")python
"""
validate_naming.py — 检查文件是否符合Keychron命名规范。
预期格式: <型号名称>-<部件名>.<扩展名>
示例: 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)
解析STEP文件元数据(无需CAD软件)
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 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}")python
"""
parse_step_header.py — 从STEP文件中提取头部元数据。
STEP文件包含ASCII格式的头部,存储产品名称、作者、 schema信息等。
"""
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)
在FreeCAD中打开STEP文件(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 syspython
"""
open_in_freecad.py — 使用FreeCAD的Python API打开STEP文件。
需要先安装FreeCAD并配置好其Python路径。
"""
import sysAdd FreeCAD to path (adjust for your OS and FreeCAD version)
Add FreeCAD to path (adjust for your OS and FreeCAD version)
Linux:
Linux:
sys.path.append("/usr/lib/freecad/lib")
sys.path.append("/usr/lib/freecad/lib")
macOS:
macOS:
sys.path.append("/Applications/FreeCAD.app/Contents/lib")
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)
undefinedimport 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)
undefinedConvert STEP to STL for 3D printing (using FreeCAD headless)
将STEP转换为STL用于3D打印(使用FreeCAD无头模式)
python
"""
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)python
"""
step_to_stl.py — 批量将STEP文件转换为3D打印用的STL格式。
需要安装FreeCAD的Python绑定。
"""
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
使用cadquery检查STEP几何参数
bash
pip install cadquerypython
"""
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)bash
pip install cadquerypython
"""
inspect_step.py — 使用CadQuery加载并检查STEP文件。
cadquery无需GUI即可运行,非常适合脚本化几何参数检查。
"""
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定位板尺寸汇总
python
"""
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)python
"""
dxf_summary.py — 解析DXF文件并输出基础几何统计信息。
"""
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
3D打印指南
- 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
- 推荐材料: 外壳和定位板原型使用PLA或PETG;需要耐热的结构部件使用ABS/ASA
- 定位板文件: 激光切割1.2-1.5mm钢或铝定位板使用DXF文件;标准MX轴开孔尺寸为14mm × 14mm
- 外壳公差: 文件中的生产公差基于CNC加工设定,3D打印时需额外增加0.1-0.2mm的间隙
- 比例尺: 所有模型单位为毫米(1:1比例),导入切片软件时请确认比例尺,部分工具默认单位为厘米
- 打印方向: 外壳以内侧朝下的方向打印,可减少支撑材料使用
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 |
| Git clone is slow | Use |
| cadquery import error | Ensure you have |
| 问题 | 解决方案 |
|---|---|
| STEP文件无法打开 | 确保你的CAD软件支持AP214或AP242标准,FreeCAD、Fusion 360、SolidWorks均支持该标准。 |
| DXF打开后比例错误 | 检查单位设置,DXF可能使用毫米或英寸单位,将软件单位设置为毫米即可。 |
| 文件过大无法打开 | 使用稀疏检出仅获取你需要的型号文件,大型装配体文件可能达到50-200MB。 |
| 3D打印件不匹配 | 增加0.1-0.2mm公差,生产文件为精确的CNC加工尺寸。 |
| 某个型号的文件缺失 | 查看仓库的公开issue或 |
| Git克隆速度慢 | 使用 |
| cadquery导入错误 | 确保已安装 |
Key Reference Docs in the Repository
仓库核心参考文档
- — How to open STEP, DWG, DXF, and PDF files
docs/file-format-guide.md - — First-stop guide for browsing and remixing
docs/getting-started.md - — Practical printing guidance
docs/3d-printing-guide.md - — Auto-generated filesystem inventory
docs/repo-inventory.md - — What you can and cannot do with these files
docs/license-faq.md - — Workflow, file standards, and submission rules
CONTRIBUTING.md
- — 如何打开STEP、DWG、DXF和PDF文件
docs/file-format-guide.md - — 浏览和二次修改的入门指南
docs/getting-started.md - — 实用打印指导
docs/3d-printing-guide.md - — 自动生成的文件系统库存清单
docs/repo-inventory.md - — 文件使用权限的常见问题解答
docs/license-faq.md - — 贡献工作流、文件标准和提交规则
CONTRIBUTING.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 and in the repository.
LICENSEdocs/license-faq.md| 使用场景 | 是否允许 |
|---|---|
| 个人学习研究 | ✅ 是 |
| 非商业二次修改和客制化 | ✅ 是 |
| 学术和教育用途 | ✅ 是 |
| 销售基于这些文件衍生的产品 | ❌ 否 |
| 盈利性生产 | ❌ 否 |
| 未标注来源的衍生作品分发 | ❌ 否 |
完整条款请参考仓库中的文件和。
LICENSEdocs/license-faq.md